Template Type Derivation
The following is a common function template.
When we call it as func(x)
, the compiler will automatically derive the types T
and ParamType
for us, and they may not be the same because ParamType
may have a const
or &
reference modifier.
Parameter type is a reference or pointer
This case does not include Universal Reference (&&
), and the derivation follows.
- if the passed parameter
x
is a reference, then the reference part is ignored. - afterwards match the parameter
ParamType
and decideT
.
for example for the following different parameters.
This case is derived in the most natural way and there are no special cases.
The argument type is a generic reference
The Universal Reference is present in the template parameters (T&&
) and follows the following rules.
- If the parameter
x
is left-valued, then bothT
andParamType
are derived as left-valued references. - If the parameter
x
is right-valued, then the previous rule can be applied, i.e., the most general case.
There is something weird about this article that although &&
right-valued references are used, the derivation leads to left-valued references, which is why Meyers introduced the concept of generic references. Generic references exist in the following two contexts.
The similarity between these two is that they both use type derivation, where &&
is a generic reference when the exact type is unknown, and the above rules apply to type derivation, while for specific types it is a normal right-valued reference.
It is worth mentioning that for example
push_back(T&&)
of vector, although the argument is a type-derived right-valued reference, this function is actually instantiated when vector is instantiated, which means that when you use the function, the function signature is determined, and actually no type derivation is used, so it is not a generic reference. But foremplace_back(Args&&... args)
the function can only be instantiated when you call the function with parameters, so here the type derivation is used, it is a global reference.
The generic reference has two meanings, one is the most basic right-valued reference, and the other represents a left-valued or right-valued reference. This feature makes it possible to bind to both left and right values in code that looks like T&&
.
Although the concept of generic references looks good and is not biased in any way for understanding. The real reason for this derivation is Reference Collapsing. By default, C++ does not allow references to references like int& &x
, but in template derivation it does, with the following rules
- right-valued references to right-valued references collapse to right-valued references
- All other cases collapse left-valued references
The right-valued reference is given in the template parameters, which is collapsed to a right-valued reference only when we give a right-valued reference, and collapsed to a left-valued reference in all other cases. This explains why the term generic reference is only available in the template derivation and why both references can be bound. This feature is the key to achieving features such as perfect forwarding and effectively reducing the amount of code of this type.
Other cases
At this point the template parameter is neither a pointer nor a reference, then it is a value pass.
At this point a new copied out object is passed anyway, so the derivation rule is
- if the parameter
x
is a reference, then discard the reference part - If the argument
x
is const or volatile, it is also discarded
It is worth noting that references to constants and pointers to constants are not constant values, i.e., they can point to other things, but the content of the pointer is still constant and unchangeable.
When the passed argument is an array or a function, the derivation degrades it to a pointer (a pointer to the first element of the array or a function pointer), unless the target is a reference.
auto
type derivation
auto
has the same derivation rules as the template, the following two are the same case.
In the auto
type derivation auto
plays the role of T
, the modifier plays the role of ParamType
, and the initialization value given is the argument passed to the function.
The only difference is the treatment of std::initializer_list
, which can be explicitly initialized to initializer_list
with =
in auto
. Without =
from C++17 onwards, the derivation of an initializer list containing only a single variable is reserved for the type of that element (the initializer list is treated as a constructor argument to that element). Initialization value lists passed to function templates will not be compiled.
|
|
Since C++14 auto
can also derive the return value type of functions, or when used in lambda expressions, the template type derivation rule is applied, so the following code does not compile.
decltype
type derivation
decltype
gets almost the actual type given, without degradation or modification, and can be used for both expressions and variables. The type is modified when using auto
, and in conjunction with decltype
it is possible to have the compiler derive and get the exact type.
If the argument is an expression of type T
and
- if the value class of the expression is deceased, then the result is
T&&
; * if the value class of the expression is left-valued, then the result isT&
; - if the value class of the expression is left-valued, the result is
T&
; * if the value class of the expression is left-valued, the result isT&
; and - If the value class of the expression is purely right-valued, the result is
T
.
Including a variable in parentheses is treated as an expression with a left-valued result, so that for a variable x
of type int
, decltype(x)
results in int
and decltype((x))
results in int&
.