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
xis a reference, then the reference part is ignored. - afterwards match the parameter
ParamTypeand 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
xis left-valued, then bothTandParamTypeare 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 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
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.
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&.