Chapter One type derivation
C++98 has a single type derivation rule: used to derive function templates, c++11 slightly modified these rules and added two, one for auto, one for Decltype, then c++14 to expand the context that auto and decltype can use, The universal application of type derivation frees programmers from the tyranny of obvious, superfluous types that must be spelled out, making C + + software development more resilient, because changing a type somewhere will automatically propagate to other places through type derivation.
However, it may make the resulting code more difficult to observe, because the compiler infers the type that might not be as obvious as we thought.
To make efficient programming in modern C + +, you must have a solid understanding of the type derivation operation, because there are too many situations you will use it, in the function template call, in the most scenes of auto appearing, in the Decltype expression, in c++14, the mysterious Decltype ( Auto) When the construction is applied.
This chapter provides some basic information about type derivation that every C + + developer needs to understand, explaining how template type derivation works, how auto builds its own rules on this basis, and how Decltype works according to its own independent rules. It even explains how you force the compiler to make the result of the type deduction visible, allowing you to determine that the compiler's results are what you want.
Article 1 Understanding template type derivation
Imitation is said to be the most sincere compliment, but the joy of ignorance is also heartfelt praise, when using a complex system, ignoring how its system is designed, how it works, yet you will still be happy with what it's done, in this way, C + + The type derivation of the template has become a huge success, with millions of of programmers passing parameters to template functions and getting completely satisfying answers, although many programmers are forced to pay more than a hazy description of how these functions are deduced. (even though many of those programmers would being hard-pressed to give more than the haziest description of how the types us Ed by those functions were deduced.)
If the above mentioned millions of of the programmers include you, I have a good news also has a bad news, the good news is that the type deduction rules and templates for auto declared variables are essentially the same, so when it comes to auto, you get familiar with it, The bad news is that when the template type derivation rules are applied to auto, you are likely to be surprised at what's going on, and if you want to use auto (you should certainly use auto as opposed to the exact type declaration, see clause 5), you need to have a reasonable and correct understanding of the template type derivation rules, They are usually straightforward, so it's not too much of a challenge, and the way you work in c++98 is the same, and you probably don't need to think too much about it.
If you are willing to ignore a small amount of pseudo-code, we can think directly about the code of the template function below.
void
function calls like this
// call F with some expressions
During compilation, the compiler uses expr to derive two types: one is T and one is Paramtype, and these two types are often different, because paramtype often includes modifiers such as const or referential qualification, for example, if the template is declared like this:
void F (const//int0; f (x); // call function f with an int type
T is deduced to be int, but Paramtype is deduced as const int&
It is natural to expect that the type of T to be deduced is consistent with the type passed to the function argument, for example, the type of T is the type of expr, and in the above example, the type of x is int,t is deduced as int, when it is not always so yes, the type of t that is pushed out, It depends not only on the type of expr, but also on the form of Paratype, with a total of three cases;
- Paramtype is a pointer or a reference, but not a universal reference (Universal Reference), (universal reference is described in clause 26, now you just have to know that he is there).
- Paramtype is a universal reference (universal Reference).
- Paramtype is neither a pointer nor a reference.
Therefore, we will have three types of deduced scenarios, each of which will be based on our common template form:
void// derive the type of T and Paramtype from expr
First case: Paramtype is a pointer or a reference, but not a universal reference (Universal Reference)
The simplest case is when paramtype is a pointer or a reference, but not a universal reference (universal Reference), in which case the model is deduced as follows:
- If the type of expr is a reference, ignore the referenced symbol
- The type of the Paramtype is determined by the pattern matching the type of expr (pattern-match expr ' s type against Paramtype to determine T)
For example, if our template functions are like this
void f (t& param);
We have such a variable declaration
int ; // x is of type int Const int cx = x; // constint& rx = x; // Rx is a constant reference to X (Rx is a read-only view of x)
When a function is called, the types of param and T are deduced as follows
f (x); // F (CX); // //F (RX); // // The type of param is const int&
In the second and third function calls, note that because CX and RX are assigned as const, T is deduced as const int, so the resulting parameter type is const INT&, which is very important to the caller, When they pass a const object to a reference type parameter, they expect that the object will still not be modified, for example, the type of the parameter is deduced as a reference to a const, which is why it is safe to pass a const object to a template with a t& parameter. The constant nature of the object (constness) becomes part of the deduced type T.
In the third example, note that although RS is a reference type, T is deduced as a non-reference type because the reference (reference-ness) of RS is ignored in the derivation process, if not (for example, T is deduced to be const int&), The type of param will be const int&&, and a reference to the referenced reference in C + + is not allowed, and the only way to avoid them is to ignore the reference to the expression when type deduction (reference-ness).
These examples are reference parameters to Lvalue, but these type deduction rules apply to Rvalue reference parameters, and of course, only the right value of an actual delegate is passed to a reference of an rvalue type, but this has no effect on the type deduction.
If we change the parameter type of F from t& to Const T&, things will send a little bit of change, but not surprisingly, the constants of CX and Rx are still satisfied, but since we now assume that Param is a constant reference, The const does not need to be deduced as part of T.
Template<typename t>voidFConstt& param);//param is now a reference to a constantintx = -;//the same as before .Const intCX = x;//the same as before .Const int& rx = x;//the same as before .f (x);//T is int, the type of param is const int&F (CX);//T is int, the type of param is const int&F (RX);//t is int, param type is const int&
As before, the RS reference (reference-ness) is ignored in the type deduction.
If Param is a pointer (or a constant pointer (point to const)) instead of a reference, the rule still applies
template<typename t> void f (t* param); // param is now a pointer int x = 27 ; // same as before const int *px = &x; // px is a constant reference to X (Rx is a read-only view of x) F (&x); // f (px); // T is a const int, //
At this point, you may find yourself yawning and nodding, and it is natural to infer rules for C + + types for reference and pointer types, and it is a dull thing to see them written one by one, because they are so obvious, as you would expect in a type deduction.
The second case: Paramtype is a universal reference (Universal Reference)
When it comes to the Universal reference (Universal Reference) as the parameter of the template (for example, the t&& parameter), things become less clear, because the rules have special treatment for lvalue parameters, and the complete story is described in clause 26, But here's a summary version.
- If expr is an lvalue, T and Paramtype are deduced as a reference to an Lvalue
- If expr is an rvalue, use the usual type deduction rules
For example
Template<typename t>voidF (t&& param);//param is now a universal reference (Universal Reference)intx = -;//the same as before .Const intCX = x;//the same as before .Const int& rx = x;//the same as before .f (x);//x is a left value, so T is Int&//the type of param is also int&F (CX);//CX is a left value, so T is a const int& //the type of param is also const int&f (RX); //Rx is a lvalue, so T is a const int& //the type of param is also const int&F -);//27 is a rvalue, so t is int, //param type is int&&
Clause 26 gives an accurate description of why these examples will be, but the key is that the type deduction for the template parameters is universal reference (Univsersal references) and the parameters are Lvalue or rvalue when the rule is different when using universal references (Univsersal references) , the type deduction rules distinguish between Lvalue and rvalue, and this never occurs on references that are not universal (for example, Normal).
The third case: the type of paramtype is neither a pointer nor a reference
When the type of paramtype is neither a pointer nor a reference, we are handling it in the same way as the value passed.
void f (T param); // param is now passed by value.
This means that param will be a copy of the object passed over, a completely new object, and in fact, Param is a completely new object control that derives the rules derived from expr from the T
- As before, how the type of expr is a reference, ignoring the referenced part
- If expr is ignored after the reference is omitted, expr has a const modifier, ignoring the const, and, if with a volatile modifier, also ignored (volatile objects are unusual objects, they are usually used only to implement device drivers, more details can be referred to clause 42)
So
int x = 27 ; // same as before const int cx = x; // same as before const int & rx = x; // same as before F (x); // T and Param are both int f (CX); // T and param are int f (RX); // T and param are int
Note that even if CX and Rx represent constants of the object, param is not constant, which is said, because Parm is completely independent of the Cx,rx object, it is a copy of CX and RX, in fact CX and RX can not be modified and param can be modified without any relationship, This is why the constant nature of expr is ignored when deriving the Param type, because expr cannot be modified and does not imply that its copy cannot be modified.
It is important to note that Const is ignored only in parameters passed by value, as we have seen, for references and pointers to constants, the constant nature of expr is preserved at the time of type deduction, but in the following case, expr is a constant pointer to a const object. And expr is passed to a parameter by value,
template<typename t> void f (T param); // param is passed by value const char * const ptr = " fun with pointers " // PTR is a constant pointer to a constant object PTR is the const pointer to const object f (PTR); // The argument type is const char * const
Here, the const on the right of multiplication sign declares PTR to be const, which means that PTR cannot point to a different location, nor can it be set to null (const on the left of multiplication sign refers to the string that PTR points to is const, so the string cannot be modified), and when PTR is not passed to F, The pointer is copied to the Param, so the pointer itself (PTR) will be passed by value, and the constants of PTR will be ignored based on the type deduction rules passed by value, the Param type is deduced to be const char*, a pointer to the position can be modified, but the pointed string cannot be modified, The constants referred to by PTR are preserved at the time of type derivation, but the constant nature of PTR itself is ignored when creating new pointer param by copying.
Array parameters
These already cover the main part of the template type derivation, but there are some side corners where we know that the types of arrays and pointers are different, and even if they sometimes seem to be interchangeable, the main contribution of this illusion is that, in many environments, The array is degraded to a pointer to the first element of the array, which allows the following code to be compiled.
Const Char " J. P. Briggs"// type of name is //constChar * ptrtoname = name; // Pointer to array
Here, the const *char pointer ptrtoname is instantiated by name, and the type of name is const CHAR[13], a constant array of 13 elements, the type of the two (const char* and const CHAR[13]) is different, But because there is a degenerate rule between arrays and pointers, the above code can be compiled.
But what happens if an array is passed to a template through a value-passing method?
void f (T param); // A template is a parameter that is passed by value f (name); // What is the derivation of T?
We should first note that there is no parameter of the array type in the parameter of the function, and yes, the following syntax is legal
void myFunc (int param[]);
But the declaration of this array is treated as a pointer to the declaration, which means that the myfunc and the following declarations are equivalent
void myFunc (int// and above functions are the same
The equivalence of arrays and pointers on parameters stems from the illusion that C + + is created based on C and that the array and pointer are equivalent in type.
Because the declaration of an array parameter is treated as a pointer declaration, an array passed by value to a template parameter is deduced as a pointer type, which means that the type of the parameter T is deduced as a const char* in the call of the template function f below.
// name is an array, but T is deduced as const char*
But now there is a curve ball, although the function cannot declare a real array type parameter, but they can declare a reference to the array, so if we change the template F to pass the argument by reference
template< TypeName t>void// template parameters are passed by reference
Now we pass the array past
// passing an array to F
The type of type T is inferred as the type of the array, which includes the size of the arrays, so in the above example, T is deduced as the type of the const CHAR[13],F parameter (a reference to an array) is a const char (&) [13], yes, This syntax may seem poisonous (looks toxic), but in a positive way, knowing these will reward you for those rare points that others won't get (knowing it'll score you Mondo points with those rare souls CARE).
Interestingly, declaring a reference to an array allows us to create a template to return the length of the array.
Template<typename T, std::size_t n> // constexpr std::size_t arraySize during compilation (t (&) [N]) // returns an array of { // size return N; // N is a constant }
Note that the use of constexpr (see Clause 14) allows the result of the function to be obtained during compilation, which allows us to declare that the length of an array is the same as the length of another array
int 1 3 7 9 One A * // Keyvals has // 7 element int mappedvals[arraysize (keyvals)]; mappedvals // This is also true
Of course, as a modern C + + developer, you should naturally use Std::array instead of built-in arrays
std::array<int// mappedvals ' // size is 7
function parameters
The array is not the only entity in C + + that can degenerate into a pointer, and the function type can also degenerate into a pointer, and any of the rules we discuss about type derivation and array-related things also apply to the type deduction of a function, and the function type degrades to a pointer to a function, so
voidSomeFunc (int,Double);//SomeFunc is a function; //type is void (int, double)Template<typename t>voidF1 (T param);//in the function F1, the arguments are passed by valueTemplate<typename t>voidF2 (t& param);//in the function F2, the arguments are passed by referenceF1 (SomeFunc);the//parameter is deduced as a pointer to a function // type is void (*) (int, double)F2 (SomeFunc);//argument is deduced as a reference to a function //type is void (&) (int, double)
In fact, this and the array are not different, but if you are learning the degradation of arrays to pointers, you should also understand the function-to-pointer degradation is better.
So, here you should know the rules of template type derivation, at the very beginning I said they were so simple and clear, in fact, for most of the rules, it is true, and the only thing that can spark a splash is when using universal references (Universal references), The Lvalue has special treatment, even the degenerate rules of arrays and functions to pointers can make water muddy, sometimes you may simply grab your compiler, "tell me what type of export you are pushing", and you can look at clause 4, because clause 4 is about how to persuade your compiler to do so.
Please remember:
- When the template parameter is a pointer or a reference, but not a universal reference (universal Reference), the instantiated expression is a reference that is ignored.
- When the template parameter is a universal reference (universal Reference), the argument of the lvalue produces a reference to the Lvalue, and the argument to the right value produces an rvalue reference.
- The parameters of the template are passed by value, and the instantiation and the constants of the instantiated expression are ignored.
- During type deduction, arrays and functions are degraded to pointer types unless they are instantiated as corresponding references.
Effective modern C + + translator (2)-clause 1