Effective Modern C ++ translation-Clause 1: Understanding template type Derivation

Source: Internet
Author: User

Effective Modern C ++ translation-Clause 1: Understanding template type Derivation

The translation of Chapter 1 is started at 13:47:17 on January 1, January 9, 2016 in Beijing. Chapter 1 "type inference" is divided into four terms: 1. Understanding template type derivation 2. Understanding auto automatic type derivation 3. Understanding decltype Operator 4. How to treat the derivation type

Chapter 1 type Derivation

C ++ 98 has a set of single type derivation rules for deriving function templates. C ++ 11 slightly modified these rules and added two derivation rules, one for auto and the other for decltype. Then C ++ 14 expands the context that can be used by auto and decltype. The general application of Type derivation frees programmers from the apparently redundant types that must be spelled out, making C ++ software more flexible, because changing a type at one point will automatically spread to another place through type derivation. It makes the C ++ software more adaptive, because changing a type Automatically deducts data to other places from one point in the source code. However, it can make the code more difficult to reason, because the compiler may infer that this type is not as obvious as we think.

To implement efficient programming in modern C ++, you must have a solid understanding of Type derivation operations. Because there are too many situations that you will use: In function template calls; in most scenarios where auto appears; In decltype expressions; when the mysterious decltype (auto) Construction in C ++ 14 is applied.

This chapter provides basic information about type derivation that every C ++ developer needs to understand. It explains 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 Type derivation visible, so that you can determine that the compiler result is what you want.

Clause 1: Understanding template type Derivation
Some people say that imitation is the most sincere form of flattering, but the ignorance of happiness can be the same sincere praise. When a user uses a complex system and ignores how it is designed and operated, you will still be very happy with what it has done, in this way, the type derivation of the template in C ++ has become a huge success. Millions of programmers pass parameters to the template function and get a completely satisfactory answer, although many programmers are forced to pay more than a vague description of how these functions are derived.

If the people mentioned above include you, I have good news and bad news. The good news is that the type deduction rules of variables declared by auto are essentially the same as those of the template, so when auto is involved, you will be familiar with it (see Article 2 ). The bad message is that when the rule for template type derivation is applied to auto, you may be surprised at what happened. If you want to use auto (compared to the exact type declaration, of course you should use auto, see clause 5). You need to have a reasonable and correct understanding of the template type deduction rules. They are usually straightforward, so this will not be a big challenge, similar to working in C ++ 98, 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 directly think about the code of the following template function:

   void f(ParamType param);

It can be called as follows:

f(expr);  //call f with some expression

During the compilation period, the compiler derives two types based on expr: T and ParamType. These two types are often different, because ParamType often includes modifiers such as const or referenced limitation. For example, if the template is declared as follows:

   void f(const T& param); //ParamType is const T&

We call this method as follows:

int x = 0;f(x); //call f with an int

T is deduced as int type, but ParamType is deduced as const int & type.

Naturally, it is expected that the type derivation T is the same as the type of the parameter passed to the function, just as T is the expr type. In the preceding example, x is of the int type, and T is deduced as of the int type. But this is not always the case. T is derived not only based on expr, but also related to ParamType. There are three scenarios:

? ParamType is a pointer or reference type, but not a universal reference (universal reference is described in article 26. Currently, you only need to know the existence of the universal reference ).

? ParamType is a universal reference.

? ParamType is neither a pointer nor a reference.

Therefore, we have three types of derivation scenarios, each of which is based on our common template form:

   void f(ParamType param);f(expr);   //deduce T and ParamType from expr

First case: ParamType is a pointer or reference type, but not a universal reference

The simplest case is when ParamType is a pointer or reference type, but not a universal reference. In this case, type derivation works like this:

? If expr is of the reference type, the reference part is ignored.

? The type of ParamType is determined by the pattern matching expr type to determine the type of T

For example, if our template is as follows:

Void f (T & param); // param is a reference

In addition, we declare the variables as follows:

int x = 27;        //x is an intconst int cx = x;  //cx is a const intconst int& rx = x; //rx is a read-only view of x

During function calling, the types of Param and T to be exported are as follows:

f(x);              //T is int, param's type is int&f(cx);             //T is const int, param's type is const int&f(rx);             //T is const int, param's type is const int&

In the second and third function calls, it is noted that because cx and rx are assigned as const, T is deduced as const int, so the generated parameter type is const int &. This is very important for callers. When they pass a const object to a parameter of the reference type, they expect this object to remain unmodifiable. For example, the type of this parameter is deduced as a reference pointing to the const. This is why it is safe to pass a const object to a template with a T & parameter, and the constant nature of the object becomes part of the type T of the push and export.

In the third example, it is noted that although rs is a reference type, T is deduced as a non-reference type because of the reference-ness of rs) this is ignored during the derivation process. If this is not the case (for example, T is deduced as const int &), The param type will be const int &&, A referenced reference is not allowed in C ++, so that their unique method does not ignore the function of the expression during type derivation.

These examples are reference parameters of the Left value, but these types of derivation rules apply to reference parameters of the right value at the same time. Of course, only the real parameters of the right value will be passed to a reference of the right value type, but this has no effect on the Type derivation.

If we change the f parameter type from T & to const T &, a slight change will be sent, but it will not be surprising. The constants of cx and rx are still satisfied, but since we now assume that param is a constant reference, const does not need to be deduced as part of T:

   void f(const T& param); //param is now a ref-to-constint x = 27;             //as beforeconst int cx = x;       //as beforeconst int& rx = x;      //as beforef(x);                   //T is int, param's type is const int&f(cx);                   //T is int, param's type is const int&f(rx);                   //T is int, param's type is const int&

As in the previous section, rx's accessibility is ignored during type derivation.

If param is a pointer type (or a pointer to a constant) instead of a reference type, type derivation is performed in the same way.

   void f(T* param); //param is now a pointerint x = 27;             //as beforeconst int *px = &x;     //px is a ptr to a read-only view of xf(&x);                   //T is int, param's type is int*f(px);                   //T is const int, param's type is const int*

At this moment, you may find yourself yawning and nodding constantly, because the C ++ type derivation rules are so common for parameters of the reference and pointer types, it is boring to see them written one by one. Because they are so obvious, they are the same as what you expect in type derivation.

The second case: ParamType is a universal reference

When universal reference is involved as a template parameter (for example, T & parameter), it is not so clear, because the rule has special treatment for the left value parameter. The complete content will be described in Clause 26, but here is an outline of the version:

? If expr is a left value, both T and ParamType are deduced as a reference to the left value.

? If expr is a right value, use a common type derivation rule

For example:

   void f(T&& param);      //param is now a universal referenceint x = 27;             //as beforeconst int cx = x;       //as beforeconst int& rx = x;      //as beforef(x);                   //x is lvalue, so T is int&, param's type is also int&f(cx);                  //cx is lvalue, so T is const int&,                         //param's type is also const int&f(rx);                  //rx is lvalue, so T is const int&,                        //param's type is also const int&f(27);                  //27 is rvalue, so T is int, param's type is therefore int&&

Clause 26 explains in detail why, but now it is important that type derivation rules for template parameters are different when univsersal references and parameters are left or right values. When univsersal references is used, the type derivation rule will distinguish the left and right values, which will never happen in the reference of nivsersal references.

Case 3: ParamType is neither a pointer nor a reference

When ParamType is neither a pointer nor a reference, we process it by passing the value:

   void f(T param);     //param is now passed by value

This means that param will become a copy of it-a new object. In fact, param is a brand new object control that exports T's rule derived from expr:

? As before, if the expr type is reference, the reference part is ignored.

? If expr has const modification and const is ignored after the import of expr, if it has volatile modification, it is also ignored (the volatile object is an unusual object, they are generally only used to implement device drivers. For more details, refer to article 42 ).


int x = 27;             //as beforeconst int cx = x;       //as beforeconst int& rx = x;      //as beforef(x);                   //T and param are both intf(cx);                  //T and param are again both intf(rx);                  //T and param are still both int

Note that although cx and rx represent constants, param is not constants. This makes sense. Because parm is a completely independent object from cx and rx, it is a copy of cx and rx. In fact, cx and rx cannot be modified, and there is no relationship between them and whether param can be modified. That is why the constants of expr are ignored when deriving the param type; because expr cannot be modified, it does not mean that its copy cannot be modified.

It is important to note that const is ignored only in parameters passed by value. As we can see, for reference and pointer to constants, the constants of expr are retained during type derivation. However, in the following cases, expr is a constant pointer to the const object, and expr is passed to a parameter by value:

   void f(T param);                             //param is still passed by valueconst char* const ptr = "Fun with pointers"; //ptr is const pointer to const objectf(ptr);                                      //pass arg of type const char* const

Here, the const on the right side of the multiplication mark declares ptr as const, meaning that ptr cannot point to a different position, it cannot be set to null (the const on the left side of the multiplication sign indicates that the ptr points to the const string, so the string cannot be modified ). When the ptr is passed to f, the pointer is copied to param in bits. Therefore, the pointer itself (ptr) will be passed by value. According to the Type derivation Rule passed by value, the constants of ptr will be ignored, the param type is deduced as const char *, a pointer that can be modified to the specified position, but the string that points to cannot be modified. The constants referred to by ptr are retained during type derivation, but the constants of ptr are ignored when a new pointer param is created through copying.

Array as a parameter

This almost covers the derivation of its mainstream template types, but it is worth understanding when there is a side stream. Although the array and pointer types are sometimes interchangeable, they are still different. One of the main contributors to this illusion is that, in many cases, an array degrades into the first element pointing to it. This degradation allows such code to be compiled:

const char name[] = "J. P. Briggs"; //name's type is const char[13]const char* ptrToName = name;       //array decays to pointer

Here, the string pointer ptrToName of the constant type is initialized to name, and name is a constant array, which is equivalent to an array with 13 constant character elements. These types (const char * and const char [13]) are different, but such code can be compiled because of the degradation from arrays to pointers.

However, what if the template parameter is an array type passed by value? So what will happen?

   void f(T param);       //template with by-value parameterf(name);               //what types are deduced for T and param?

First, we should note that the function parameters do not contain array-type parameters. Yes, the following syntax is legal.

void myFunc(int param[]);

However, the array declaration is seen as the pointer declaration at this time, which means that the function myFunc can be declared as follows:

void myFunc(int* param); //same function as above

The equivalence of arrays and pointers in parameters is derived from the fact that C ++ is created based on C, which produces the illusion that arrays and pointers are equivalent in type.

Because the declaration of array parameters is treated according to the pointer declaration, the array passed to a template parameter by value will be deduced as a pointer type, this means that in the following template function f call, the type of the parameter T is derived as const char *:

f(name);                //name is array, but T deduced as const char*

But now we have a curve ball. Although the function cannot declare an array-type parameter in the true sense, they can declare a reference pointing to an array, therefore, if we change template f to pass Parameters by reference:

   void f(T& param);     //template with by-reference paremeter

Then, we pass an array to him:

f(name);              //pass array to f

The T type is derived as the array type. This type includes the size of the array, so in the above example, T is deduced as the type of the const char [13], f parameter (a reference to the array) is const char (&) [13]. Yes, this syntax seems harmful, but from a good perspective, knowing this will reward you with rare scores that others don't get (knowing this is good for you ).

Interestingly, declaring a reference pointing to an array allows us to create a template to return the length of the array, that is, the number of elements in the array:

          //return size ofconstexpr std::size_t arraySize(T (&)[N]) //an array as a{                                         //compile-time   return N;                              //constant}

Note that the use of constexpr (see clause 14) allows the function results to be obtained during compilation. This allows us to declare that the length of an array is the same as that of another array.

int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; // keyVals has                                            // 7 elementsint mappedVals[arraySize(keyVals)];         // so does                                             // mappedVals

Of course, as a modern C ++ developer, you should be more accustomed to using std: array instead of the built-in array:

    mappedVals; // mappedVals'                                                // size is 7

Function as a parameter

In C ++, arrays are not the only entities that can degrade to pointers. Function types can also be degraded into function pointers, and any rule we discuss about type derivation and array-related events are also applicable to function type derivation, the function type degrades to the pointer of the function. Therefore:

void someFunc(int, double); // someFunc is a function;                            // type is void(int, double)template
   void f1(T param);           // in f1, param passed by valuetemplate
    void f2(T& param);          // in f2, param passed by reff1(someFunc);               // param deduced as ptr-to-func;                                                    // type is void(*)(int, double)f2(someFunc);               // param deduced as ref-to-func;                            // type is void(&)(int, double)

This is actually no different from array. However, if you want to learn about the degradation from arrays to pointers, you should also understand the degradation from functions to pointers.

So here you should know the rule of template type derivation. At the very beginning, I said they were so simple and clear. In fact, this is true for most of the Rules. The only thing that will stimulate the starting point is that the Left value has special treatment when using the universal references, even degradation rules from arrays and functions to pointers can make water turbid. Sometimes, you may just capture your compiler, "tell me what type you export". When this happens, turn to Clause 4 because it is specifically used to trick the compiler into doing so.


? When the template parameter is a pointer or reference, but not a universal reference, whether the initialized expression is a reference is ignored.

? When the parameter of the template is a universal reference, the real parameter of the Left value generates a reference of the Left value, and the real parameter of the right value generates a reference of the right value.

? When the parameters in the template are passed by value, the reference and constant of the instantiated expression will be ignored.

? During type derivation, arrays and functions will degrade to pointer types unless they are initialized as references.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.