Template Type Derivation
The following is a common function template.
When we call it as
func(x), the compiler will automatically derive the types
ParamType for us, and they may not be the same because
ParamType may have a
& reference modifier.
Parameter type is a reference or pointer
This case does not include Universal Reference (
&&), and the derivation follows.
- if the passed parameter
xis a reference, then the reference part is ignored.
- afterwards match the parameter
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
xis left-valued, then both
ParamTypeare derived as left-valued references.
- If the parameter
xis 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 for
emplace_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
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.
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
xis a reference, then discard the reference part
- If the argument
xis 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.
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
= 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.
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
- 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 is
- 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 is
- If the value class of the expression is purely right-valued, the result is
Including a variable in parentheses is treated as an expression with a left-valued result, so that for a variable
x of type
decltype(x) results in
decltype((x)) results in