Note: the answer here was borrowed from efficient modern C ++ with (very) few additions of my own
This is one of those questions that are easy to ask but difficult to answer! I remember reading the whole chap. After deducing the type of the template and for the novice reader, the answer is not clear in any reading. Nevertheless, I will try to clarify this here.
It should be noted that there is something called Universal References (which do not match references or references to r-values) that affects template type inference, and I assume that readers are aware of references to l-value and r-value,
Any definition of the ubiquitous function template is as follows:
template <typename T> returnType function(paramType param);
The function call will look something like this:
function(expression);
The compiler uses an expression to determine the type of T and the type of paramType . This is because most often paramType contains decorations such as const , const & , const & amp; etc. Beginners will be tempted to believe that the type T output by the compiler will be the same as the type of the expression , that is, the argument passed to the function, but this is not always the case. A residue of type T depends on both the expression and paramType . Depending on what the paramType function parameter is , there are three things to consider when inferring a template type:
- paramType is a pointer or reference, but not a universal reference
- paramType is a universal link
- paramType is neither a pointer nor a reference.
Let's look at each case one by one
Case 1: paramType is a pointer or link, but not a universal reference
Call me crazy, but this is the simplest case that you can meet. In this case, the type deduction works like this: (i) If the expression is a reference, ignore the reference part (ii) then match the expression template with paramType to determine T
Let's look at an example:
template <typename T> returnType function(T ¶m);
We have the following variable declarations:
int x = 23; // x is int const int const_x = x; // const_x is const int const int& ref_x = x; // ref_x is a reference to x as const int
The derived call for T and param in various calls is as follows:
f(x); //T is int, param type is int& f(const_x); //T is const int, param type is const int& f(ref_x); //T is const int, param type is const int&
Two points should be noted here:
(i) the -ness link is ignored by the compiler to determine the type here
(ii) const -ness becomes part of type T when passing a const object or a reference to a const object and, therefore, passing const objects or references to a const object in functions that take the T & amp; safely.
If we change the function parameter with T & amp; on const T & amp; , because in this case we assume that param is a reference to const, then const -ness does not need to be output as part of T. The following is an example:
template <typename T> returnType function(const T& param); // param is now a ref-to-const int x = 23; // same as previous const int const_x = x; // same as previous const int& ref_x = x; // same as previous f(x); // T is int, paramType is const int& f(const_x); // T is int, paramType is const int& f(ref_x); // T is int, paramType is const int&
Note : the variable 'x' is not a constant argument for 'f ()', but until then it is output as a constant parameter
If paramType is a pointer, then everything will work basically the same as with links, just instead of links there will be pointers. For example, for completeness, below:
template <typename T> returnType function( T* paramType); //paramType is now a pointer int x = 23; // same as before const int *pointer_x = &x; // pointer_x is pointer to x as const int f(&x); // T is int, paramType is int* f(&pointer_x); // T is const int, paramType is const int*
For completeness, I can also post the case if paramType was a pointer to a constant object, as shown below:
template <typename T> returnType function(const T* paramType); int x = 23; // same as before const int *pointer_x = &x; // pointer_x is pointer to x as const int f(&x); // T is int, paramType is const int* f(&pointer_x); // T is int, paramType is const int*
That is, again const -ness is no longer output as part of T
In the case of references to r-values, residues of type T and paramType follow basically the same rules as in the case of references to l-values.
This covers most of this for the first case. Let's look at our case 2.
Case 2: paramType is a universal reference
Universal references are declared as references to r-values, but accept an l-value, but their behavior is different in that the arguments of the function receive references to an l-value. Here's how type inference works in this case:
(i) If the expression is an l-value, both T and paramType are output as an l-value. (This looks odd considering the way the code looks, because although paramType is declared using r-value reference syntax, its inferred type has a reference to l-value.) It should be noted that this is the only case when T is output as a link.
The example below explains my explanation:
template <typename T> returnType function(T&& paramType); //param becomes universal reference if // argument to function call is an l-value int x = 23 // same as previous const int const_x = x; // same as previous const int& ref_x = x; // same as previous f(x); // x is l-value therefore T is int& // paramType is int& f(const_x); // const_x is l-value therefore T is const int& //paramType is also const int& f(ref_x); // ref_x is l-value therefore T is const int& //paramType is also const int& f(23); // 27 is r-value so T is int // paramType is now int&&
I want to be honest here and say that this does not explain why universal links work the way they are, but I think this post will get too long if I continue to justify it here.
Case 3: paramType is neither a pointer nor a reference ##
Here, the transfer by value in the template occurs, which means that param will be a copy of everything that is passed into the argument of the calling function, that is, a completely new object, and this motivates the rules that control the derivation of type T from the expression . Two points should be noted here:
(i) ignore the -ness link in the expression , if any.
(ii) after ignoring ref -ness, ignore also const -ness or volatile -ness, i.e. if he is present
template <typename T> returnType function(T paramType); int x = 23; const int const_x = x; const int& ref_x = x; f(x);
Note that although const_x and ref_x are const objects that cannot be changed, this does not mean that copies of them cannot be changed. It looks simple, but it gets harder when we pass a constant pointer to a constant object. Let's look at another example:
template <typename T> returnType function(T param); const double *const dPtr = 23;
When the const pointer is passed by value, const -ness is lost, and the pointer is copied by value, which synchronizes with the type inference rules for passing by value, but the const -ness of what the to pointer points to is saved and, therefore, paramType will be const * double .
This can cause you to feel dizzy, as it happened to me when I started learning about it. The best way would be to re-read it and try to encode.