Deducing Types
1.1 Template Type Deduction
Template Type Deduction:
The process by which the compiler determines template parameter types from function arguments.
T:
The template type parameter whose type is deduced from the argument expression.
ParamType:
The declared type of the function parameter, which determines how T is deduced.
Argument Expression (expr):
The expression passed to a function template and used by the compiler for type deduction.
1) Reference and Pointer Parameters
Reference/Pointer Deduction:
When ParamType is a reference or pointer but not a universal reference, the argument's reference part is ignored and the remaining type is matched against ParamType.
Reference Removal:
If the argument expression has a reference type, its reference qualifier is ignored when deducing T.
const Preservation:
When an argument is passed through a reference or pointer, the const qualification of the referenced or pointed-to object can be preserved as part of the deduced type.
Reference-to-const:
When ParamType already contains const, the argument's const does not need to become part of T.
2) Universal References
Universal Reference:
A deduced parameter of the form T&& that can bind to both lvalues and rvalues.
Lvalue Deduction:
When an lvalue is passed to a universal reference, T is deduced as an lvalue reference.
Rvalue Deduction:
When an rvalue is passed to a universal reference, T is deduced as a non-reference type and the parameter becomes an rvalue reference.
Lvalue/Rvalue Distinction:
Universal reference deduction is special because it distinguishes between lvalue and rvalue arguments.
3) Pass-by-Value
Pass-by-Value Deduction:
When ParamType is neither a pointer nor a reference, the parameter receives a new copied or moved value.
Reference Removal:
Reference qualifiers of the argument are ignored during by-value deduction.
Top-level const Removal:
Top-level const on the argument is ignored when deducing a by-value parameter.
Top-level volatile Removal:
Top-level volatile is also ignored during by-value deduction.
Low-level const Preservation:
const belonging to an object reached through a pointer is preserved even when the pointer itself is passed by value.
4) Array and Function Arguments
Array-to-Pointer Decay:
An array argument normally decays to a pointer to its first element when passed by value.
Array Reference Deduction:
When an array is passed by reference, its actual array type and size are preserved.
Array Size Deduction:
A reference-to-array template parameter can deduce the number of elements in an array at compile time.
Function-to-Pointer Decay:
A function argument decays to a function pointer when passed by value.
Function Reference Deduction:
When a function is passed by reference, its function type is preserved.
Template Type Deduction Rules
Reference Parameter:
Ignore the argument's reference qualifier and preserve relevant const qualification.
Universal Reference Parameter:
Treat lvalue arguments specially and preserve their lvalue-reference nature.
Value Parameter:
Remove references and top-level const or volatile.
Array/Function by Value:
Decay to pointers.
Array/Function by Reference:
Preserve the original type.
1.2 auto Type Deduction
auto:
A placeholder type specifier that lets the compiler deduce a type from an initializer.
auto Type Deduction:
Type deduction for auto follows template type deduction rules in most situations.
Initializer:
The expression from which the compiler determines the type represented by auto.
1) auto and Template Deduction
Template Deduction Relationship:
auto plays a role similar to the template parameter T, while the complete declaration containing auto plays a role similar to ParamType.
Value auto:
Plain auto generally follows pass-by-value template deduction rules.
Reference auto:
auto& follows reference deduction rules.
Universal Reference auto:
auto&& can behave as a universal reference when its type is deduced from an initializer.
2) auto and const
Top-level const Removal:
Plain auto normally removes top-level const from the initializer.
Low-level const Preservation:
const that belongs to an object referenced or pointed to by the deduced type is preserved.
const auto:
Adds top-level const to the type deduced for auto.
auto&:
Preserves reference-related type information that plain auto would discard.
3) Braced Initializers
Braced Initializer:
An initializer written using {}.
std::initializer_list:
A standard library type representing a sequence of values supplied through list initialization.
auto Braced-Initializer Rule:
When applicable, auto treats a braced initializer as a std::initializer_list.
Element Type Deduction:
All elements must permit a single element type to be deduced for the std::initializer_list.
Deduction Failure:
If a single element type cannot be deduced, the declaration is rejected.
4) auto vs. Template Type Deduction
Major Difference:
auto has special deduction behavior for braced initializers, while ordinary template type deduction does not automatically treat them as std::initializer_list.
Function Return auto:
C++14 function return types declared with auto use template type deduction rules.
Lambda Parameter auto:
C++14 generic lambda parameters declared with auto also use template type deduction rules.
auto Type Deduction Rules
Plain auto:
Usually follows pass-by-value template deduction.
auto&:
Uses reference deduction.
auto&&:
Can act as a universal reference.
Braced Initializer:
May deduce std::initializer_list.
Function Return auto:
Uses template type deduction rather than the special braced-initializer rule for variable auto.
1.3 decltype
decltype:
A type specifier that determines the type associated with a name or expression.
Exact Type Preservation:
Unlike ordinary template and auto deduction, decltype normally preserves const and reference information.
Unevaluated Expression:
The expression inspected by decltype is used to determine its type without being evaluated.
1) decltype of Names
Unparenthesized Name:
For an unparenthesized name, decltype normally reports the declared type of that name.
Declared Type:
The type explicitly or implicitly associated with the named entity.
Reference Preservation:
If the declared type is a reference, decltype preserves that reference.
const Preservation:
If the declared type contains const, decltype preserves it.
2) decltype of Expressions
Expression Type:
decltype can determine a type based on an expression and its value category.
Lvalue Expression:
For an lvalue expression of type T that is not covered by the special name rule, decltype yields T&.
Parenthesized Variable:
Parenthesizing a variable turns the operand into a general expression for decltype purposes.
decltype(x):
For an ordinary unparenthesized variable name, yields its declared type.
decltype((x)):
For an ordinary variable x, yields an lvalue reference because (x) is an lvalue expression.
3) Trailing Return Type
Trailing Return Type:
A function syntax that specifies the return type after the parameter list.
Parameter-dependent Return Type:
A trailing return type allows function parameters to be used when determining the function's return type.
decltype Return Type:
decltype can preserve the exact type produced by an expression used as a function result.
4) decltype(auto)
decltype(auto):
A placeholder type that performs type deduction using decltype rules.
Reference Preservation:
decltype(auto) can preserve references that ordinary auto deduction would remove.
const Preservation:
decltype(auto) preserves const according to decltype rules.
Return Type Deduction:
decltype(auto) is useful when a function must return exactly the type produced by an expression.
5) decltype(auto) and Parentheses
Parenthesis Sensitivity:
Parentheses can change the type deduced by decltype(auto) because they can change which decltype rule applies.
Return-by-Value:
Returning an unparenthesized local variable name may deduce the variable's declared value type.
Return-by-Reference:
Returning a parenthesized local variable may cause an lvalue-reference return type to be deduced.
Dangling Reference:
A reference that refers to an object whose lifetime has already ended.
Local Reference Hazard:
A function must not return a reference to a local object that is destroyed when the function returns.
6) Universal References and std::forward
std::forward:
Preserves the original value category of an argument when forwarding a universal reference.
Perfect Forwarding:
Passing an argument to another function while preserving important properties such as whether it was an lvalue or rvalue.
Universal Reference Return Access:
A universal-reference parameter combined with std::forward can preserve the value category of the original argument when accessing or forwarding it.
decltype Rules
decltype(name):
Returns the declared type for an unparenthesized name.
decltype(lvalue-expression):
Returns an lvalue-reference type.
decltype((name)):
Usually returns an lvalue reference for an ordinary variable.
decltype(auto):
Deduces a type using decltype rules.
Main Purpose:
Preserve exact type information when ordinary auto deduction would discard it.
1.4 Viewing Deduced Types
Type Inspection:
The process of determining what type the compiler has deduced for an expression, variable, or template parameter.
1) IDE Type Inspection
IDE Type Display:
Development environments can often display a deduced type when inspecting or hovering over an entity.
IDE Limitation:
Displayed types may become difficult to understand or may not expose type information in the most useful form for complex types.
2) Compiler Diagnostics
Compiler Diagnostic:
An error or warning message generated by the compiler.
Type Display through Errors:
A deliberately incomplete template can force the compiler to print a deduced type as part of an error message.
Type Displayer:
A debugging technique that instantiates an undefined or incomplete class template with the type being inspected.
3) typeid
typeid:
An operator that obtains runtime type information represented by std::type_info.
std::type_info:
A standard library type containing runtime type information.
std::type_info::name():
Returns an implementation-dependent textual representation of a type.
typeid Limitation:
The reported type may omit reference and cv-qualification information needed to understand the exact original type.
4) Boost.TypeIndex
Boost.TypeIndex:
A Boost library for obtaining readable type information.
type_id_with_cvr:
A Boost.TypeIndex facility that preserves const, volatile, and reference qualifiers when reporting a type.
CVR Qualifiers:
Collective term for const, volatile, and reference qualifiers.
5) Understanding Deduction Rules
Type Inspection Tools:
IDEs, compiler diagnostics, runtime type information, and libraries can help verify deduced types.
Deduction Rule Knowledge:
Type inspection tools do not replace understanding the language rules that determine the deduced type.
Notes
Template Type Deduction:
The foundation of modern C++ type deduction.
Pass-by-Value:
Removes references and top-level const or volatile.
Reference Deduction:
Preserves relevant const information of the referenced object.
Universal Reference:
Treats lvalue and rvalue arguments differently.
Array/Function Decay:
Arrays and functions decay to pointers during by-value deduction.
auto:
Usually follows template type deduction.
Braced Initializer with auto:
Has special std::initializer_list deduction behavior.
decltype:
Normally preserves the exact declared or expression-derived type.
decltype(x):
Returns the declared type of an ordinary unparenthesized variable.
decltype((x)):
Returns an lvalue-reference type for an ordinary variable.
decltype(auto):
Performs automatic deduction using decltype rules.
std::forward:
Preserves the value category of a universal-reference argument.
Type Inspection:
Use IDEs, compiler diagnostics, or Boost.TypeIndex to verify deductions, but understand the deduction rules themselves.