Effective Modern C ++ translation-Clause 3: Understanding decltype

Source: Internet
Author: User

Effective Modern C ++ translation-Clause 3: Understanding decltype

Clause 3: Understand decltype

Decltype is a very interesting monster. If a name or expression is provided, the decltype keyword will tell you the name or type of the expression. Generally, the results are consistent with your expectations. However, in some cases, the results produced by decltype lead you to seek answers in reference books or online Q &.

Let's start from the general situation-there are no surprises here. The decltype keyword is the same as that of the parrot, there is no change in the derivation of variable names or expression types, template type derivation, and auto type derivation:

const int i = 0;           //decltype(i) is const intbool f(const Widget& w);   //decltype(w) is const Widget&                           //decltype(f) is bool(const Widget&)struct Point{  int x, y;                //decltype(Point::x) is int};                         //decltype(Point::y) is intWidget w;                  //decltype(w) is Widgetif (f(W)) ...              //decltype(f(w)) is booltemplate
  
          //simplified version fo std::vectorclass vector{public:  ...  T& operator[](std::size_t index);  ...};vector
   
    v;               //decltype(v) is vector
    
     ...if(v[0] == 0)...            //decltype(v[i]) is int&
    
   
  

No surprise.

In C ++ 11, the main purpose of decltype is to declare a function template. The return type of a function depends on its parameter type. For example, if we want to write a function that supports adding an index through square brackets (that is, using "[]"), then performing authentication, and then re-opening the index results, user container operations. The return type of this function should be the same as that returned by the index operation.

[] When the operator acts on a container with T as the element, it usually returns T &, std: deque, and std: vector is almost the same. The only exception isstd::vecotr , [] Operator does not return a bool &. On the contrary, it returns a brand new object. Clause 6 explains why, but it is important to remember that the return type of the [] operator acting on the container depends on the container itself.

Decltype makes it easy. Here is the first version we wrote, showing the method for deriving the return type using decltype. This template can be simplified, but we will not do this for the time being:

template
  
        //works, butauto autoAnadAccess(Container& c, Index i)       //requires  -> decltype(c[i])                              //refinement{  authenticateUser();  return c[i];}
  

The auto before the function name is irrelevant to the type derivation result. It implies that the trailing return type semantics of C ++ 11 is being used. For example, the return type of a function will be declared after the parameter list (after-> ). The advantage of tracing the return type is that function parameters can be used in the declaration of the return type. For example, in authAndAccess, we use c and I to specify the return type of the function. If we want to declare the return type before the function name, just like a traditional function, c and I cannot be used because they have not been declared.

Use this statement, as we expected, to return the type of the [] operator that the authAndAccess returns when it acts on the container.

C ++ 11 allows you to deduce the lambda return type of a single statement. C ++ 14 extends this so that lambda and all functions (including functions containing multiple statements) can be deduced. This means that in C ++ 14, we can omit the trailing return type, leaving only auto. In the declaration in this form, auto means the type derivation will happen. Specifically, it means that the compiler will deduce the function return type from the implementation of the function:

template
  
      //C++14 only, andauto authAndAccess(Container&c, Index i)       //not quite{  authenticateUser();  return c[i];                                 //return type deduced from c[i]}
  

But which C ++ type deduction rule will be used? Is the template type deduction rule auto or decltype?

Perhaps surprising, functions with auto return types use the template type derivation rules. Although it seems that the auto type deduction rules will better match this semantics, the template type deduction rules are almost identical with the auto type deduction rules, the only difference is that the template type deduction rules will fail when facing the initialization formula of braces.

In this case, it is a problem to use the template type derivation rule to derive the return type of authAndAccess, but the auto type derivation rule is not much better. The difficulty is that they process the left expression.

As we have discussed earlier, most [] operators return a T & when acting on a container with T as the element, but Clause 1 explains that during the template type derivation, the reference part of the initialization expression will be ignored. The following Customer Code uses authAndAccess with the auto return type (derived from the template type to derive its return type:

std::deque
  
    d;...authAndAccess(d, 5) = 10;    //authenticate user, return d[5], then assign 10 to it;                             //this won't compile
  

Here, d [5] will return int &, but using auto type derivation to derive the function authAndAccess will remove the reference and return an int type. In this way, the int return value becomes the return value of the function, which is a right value, and the code above tries to assign 10 to a right value. In C ++, such assignment is rejected, so the above Code will not be compiled successfully.

The problem is that we use the template type deduction rule, which discards the reference qualifier in the initialization expression. In this case, we want the decltype rule. The decltype type derivation allows us to ensure that the type returned by authAndAccess is exactly the same as that returned by expression c [I.

The C ++ rule maker expects that the decltype type deduction rule must be used for type deduction under certain circumstances, so the decltype (auto) specifier appears in C ++ 14, at the beginning, it seems that there may be some conflicts (decltype and auto ?). But in fact they are completely reasonable. auto indicates that the type needs to be deduced. decltype indicates that the decltype type derivation should be used in the derivation. Therefore, the authAndAccess code will be as follows:

template
  
       //C++14 only;decltype(auto)                                  //works, butautoAndAccess(Container&c, Index i)             //still requires{                                               //refinement  authenticateUser();  return c[i];}
  

In this way, the autoAndAccess function returns the same type as c [I. In particular, when c [I] returns a T &, authAndAccess also returns a T &, and when c [I] returns an object, authAndAccess also returns an object.

The use of decltype (auto) is not limited to the return type of the function. When you want to use decltype derivation to derive the initialization type, you can easily use it to declare a variable:

Widget w;const Widget& cw = w;auto myWidget1 = cw;            //auto type deduction;                                //myWidget1's type is Widgetdecltype(auto) myWidget2 = cw;  //decltype type deduction:                                //myWidget2's type is                                // const Widget&

But I know that two things are bothering you. One is why I mentioned earlier that authAndAccess still needs to be improved. Now let's add this section.

Let's take another look at the authAndAccess function declaration in C ++ 14:

template
  
    decltype(auto) authAndAccess(Container& c, Index i);
  

The container is passed in with a constant reference of the Left value, because the reference of elements in a container is returned, which allows us to modify the container, however, this means that it is impossible to pass a container with the right value to this function. the right value cannot be bound to a reference with the left value (unless it is a constant left value reference, but this is not the case in this example)

It is undeniable that passing a right-value container to authAndAccess is a boundary condition, a right-value container, A temporary object will be destroyed after the statement containing the authAndAccess function is called. This means that the reference of an element in the container (usually returned by the authAndAccess function) will be suspended at the end of the call statement. However, it makes sense to pass a temporary object to authAndAccess. A customer may just want to copy an element in the Temporary container, for example:

std::deque
  
    makeStringDeque();     //factory function//make copy of 5th element of deque returned//from makeStringDequeauto s = authAndAccess(makeStringDeque(), 5);
  

To support this method, we need to modify the declaration of c so that it can accept both the left and right values. This means that c needs to become a universal reference (see article 26)

template
  
    decltype(auto) authAndAccess(Container&& c, Index i);
  

In the above template, we do not know what type of container we operate on, and it also means that we ignore the type of elements corresponding to the container subscript. Passing an unknown object using the value transfer method usually has to endure the issue of unnecessary copying, object segmentation (see article 17), and ridicule from colleagues. However, according to the examples in the standard library (such as std: string, std: vector and std: deque), this situation seems reasonable, so we still insist on passing by value.

What we need to do now is to update the implementation of the template. According to the warning in article 27, use std: forward to implement the universal reference:

template
  
          // finaldecltype(auto)                                     // C++14  authAndAccess(Container&& c, Index i)              // version  {   authenticateUser();   return std::forward
   
    (c)[i]; }
   
  

The above code completes what we want, but the premise is that we need to support the C ++ 14 compiler. If you do not support the C ++ 14 compiler, you should use the template type in C ++ 11. Except that you need to specify the return type, the other types are no different from those in C ++ 14:

template
  
          // final auto                                               // C++11  authAndAccess(Container&& c, Index i)              // version   -> decltype(std::forward
   
    (c)[i]) {   authenticateUser();   return std::forward
    
     (c)[i]; }
    
   
  

Another question worth nagging you is already mentioned at the beginning of this clause. It is not surprising that the result of decltype is almost the same as what you expect. To be honest, unless you want to implement a very large library, you are almost unlikely to encounter exceptions with this rule,

To fully understand the behavior of decltype, You need to familiarize yourself with some special situations. Most of them prove obscure in this book, but one of them gives us a better understanding of the use of decltype.

But as I said, the type of a variable whose name is declared using decltype is generated. The left-value expression with a name does not affect the behavior of decltype. Decltype ensures that the type to be exported is always a reference of the Left value. This means that if a left value expression is different from the variable name type T, the type of decltype will be T &. This is hardly affected, because the type of most left-value expressions usually contains a qualifier referenced by the left-value. For example, a function that returns the left value always returns a reference.

Here is something worth noting:

int x=0;

So the result of decltype (x) is int. However, name x is enclosed in parentheses, and "(x)" produces a more complex expression than the name. As a variable name, x is a left value, C ++ also defines that (x) is also a left value, so the result of decltype (x) is int &. Wrap a variable in parentheses to change the initial result of decltype.

In C ++ 11, this is just a bit strange, but with the support for decltype (auto) in C ++ 14, some simple changes to the returned statement will affect the final export result of the function:

decltype(auto) f1()  {   int x = 0;  …  return x;        // decltype(x) is int, so f1 returns int } decltype(auto) f2() {   int x = 0;  …   return (x);      // decltype((x)) is int&, so f2 returns int& }

Note that f2 and f1 are not only different return types. f2 returns a reference to a local variable !, The consequence of this Code is that it will lead to undefined behavior, which is certainly not what you want to happen.

Decltype (auto) is used here ). However, it should be noted that some seemingly insignificant details will affect the result of decltype (auto) export. To ensure that the exported type is as expected, you can use the technology described in Clause 4.

At the same time, do not lose your attention to the overall situation. The derivation result of decltype (whether used independently or with auto) may be occasionally surprising, but this does not happen frequently. In general, the result of decltype is the same as the expected type, especially when decltype is applied to the variable name, because in this case, decltype provides the declared type of the variable.

Remember:

? In general, decltype always returns the variable name or expression type without any modification.

? For a left-value expression different from a variable name, the result of decltype is always T &.

? C ++ 14 provides support for decltype (auto). For example, auto derives the type from its initialization formula, but uses the decltype deduction rule.

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.