Secrets auto and decltype

    The new language standard has been adopted relatively long ago and now, probably, there is no programmer who has not heard about the new keywords auto and decltype . But as with almost every aspect of C ++, using these new tools is not without its nuances. I will try to highlight some of them in this article.

    To warm up, I suggest starting with a small test.
    Test

    1. What type will ri1..riN variables be after executing the following code?
    int foo();
    int& foo1();
    const int foo2();
    const int& foo3();
    int main()
    {
      auto ri = foo();
      auto ri1 = foo1();
      auto ri2 = foo2();
      auto ri3 = foo3();
      auto& ri4 = foo();
      auto& ri5 = foo1();
      auto& ri6 = foo2();
      auto& ri7 = foo3();
      auto&& ri8 = foo();
      auto&& ri9 = foo1();
      auto&& ri10 = foo2();
      auto&& ri11 = foo3();
      int k = 5;
      decltype(k)&& rk = k;
      decltype(foo())&& ri12 = foo();
      decltype(foo1())&& ri13 = foo1();
      int i = 3;
      decltype(i) ri14;
      decltype((i)) ri15;
    }
    

    Will the following snippets compile?
    2. auto lmbd = [](auto i){...};
    3. void foo(auto i); 
    4. decltype(auto) var = some_expression; //WTF?!
    5. auto var = {1, 2, 3}; //Если да, какой тип будет у var?
    6. template void foo(T t){}
       foo({1, 2, 3});
    

    Theory

    Two new mechanisms have been added to the type inference mechanism used in templates in C ++ 11: auto and decltype . And so that life does not seem to programmers to be honey, all these 3 mechanisms derive types in their own way. The mechanism used by auto exactly copies the template engine, except for the type std :: initializer_list .

    auto var = {1, 2, 3};  //  Ok, var будет иметь тип std::initializer_list
    template void foo(T t);
    foo({1, 2, 3}); // Не компилируется
    


    There are few explanations for such behavior and they all do not differ in intelligibility. Scott Meyers, for example, writes on this subject: “I have no idea why type deduction for auto and for templates is not identical. If you know, please tell me! ” . In C ++ 14, this mechanism is not going to change. For the explanation, one can take the fact that such amazing things work, for example:
    template
    void fill_from_list(T& cont, const T& l);
    std::vector v;
    fill_from_list(v, {1, 2, 3});
    


    Auto

    So how does `auto` deduce a type? Unfortunately, there is no simple rule for all occasions, except, perhaps, that `auto`, when type is output, generally discards cv qualifiers and links. Below I will list the most important points.

    1.
    auto var = some_expression;
    

    If the type some_expression is T * or const T * , then the type var will also be T * or const T *, respectively. So far, no surprises. More interesting. Perhaps the most important from a practical point of view, the rule is that if the type some_expression - T , const T , T & or const T & , the type of var will be T . However, if you think about it, it’s quite logical, because in this case the value returned by some_expression is copied to var and you can safely write like this:
    void foo(const std::list& l)
    {
      auto w = l.front();
      l.pop();
      //  work with `w` here
    }
    


    2.
    auto& var = some_expression;
    

    In this case, it is expected that the type some_expression is T or const T , this will not compile, since the lvalue link cannot be initialized with rvalue . If the type of some_expression is T & , then var will also be of type T & . The important point here is that if the type of some_expression is const T & , then the type of var will be const T & .

    3.
    auto&& var = some_expression;
    

    The rule of "universal links" invented (or at least voiced) by Scott Meyers applies here. It is that type of var will depend on what value category at some_expression . If rvalue , then the type var will be T && , if lvalue , then T & . Cv qualifiers are saved.

    Auto as function parameter

    auto cannot be used as a function parameter and no changes are expected in this behavior. Obviously, the point here is that if this were allowed, then it turns out that any ordinary function could be declared essentially implicitly template. And it becomes unclear how to allow overload. Imagine this situation:
    auto foo(auto v1, auto v2) -> decltype(v1+v2) ; 
    int foo(auto v1, bool v2); 
    foo(“C++ is cool?”, true);
    

    However, in c ++ 14 it will be possible to use auto parameters in lambdas.

    decltype

    With decltype, the situation is more complicated on the one hand (if you look at the formal rules), on the other hand, it’s easier (if you highlight the main points). I will formulate these rules as I understood them.
    So, we should distinguish between two main cases of decltype .
    1. decltype (var) when var is a declared variable (for example, in a function or as a member of a class). In this case, decltype (var) will have exactly the type with which the variable is declared.
    2. decltype (expr) , expr is an expression. In this case, the type decltype (expr) will be the type that could return this expression, with the proviso that decltype (expr) will be of type T & ( const T & ) if expr returns an lvalue , T if expr returns an rvalue of type T ( const T ) and T && ( const T && ) if expr returns xvalue ( rvalue reference )

    What does “could return” mean? This means that decltype does not evaluate the expression passed to it as an argument.
    A few illustrative examples:
    int i;
    decltype(i); // int
    decltype(i + 1); // int
    decltype((i)); // int&
    decltype(i = 4); //int&
    const int foo();
    decltype(foo()) ;// int
    int&& foo1();
    decltype(foo1()) ;// int&&
    


    In the event that we do not know lvalue, an expression, rvalue or xvalue will return us , and we would like to use the type, you can use the standard std :: remove_reference template to “clear” the type of links.

    Decltype (auto)

    This is a new “feature” of the language, which will be included in C ++ 14. It is needed to preserve the semantics of decltype when declaring auto variables and will be used in cases where we are not satisfied with the fact that auto discards links and cv qualifiers and, possibly, in conjunction with the new C ++ 14 feature - outputting the type of the value returned by the function .
    const int&& foo();
    auto i = foo(); //  i будет иметь тип int
    dectype(auto) i2 = foo(); //  i2 будет иметь тип const int&&
    

    In the latter case, we could write decltype (foo ()) , but imagine if instead of foo () there was an expression with 2 lines, and such are not uncommon in C ++.

    The answers

    Well, now, having loaded the theory into the cache, you can try to answer the test questions.

    1.
    int foo();
    int& foo1();
    const int foo2();
    const int& foo3();
    int main()
    {
      auto ri = foo(); // int
      auto ri1 = foo1(); // int
      auto ri2 = foo2(); // int
      auto ri3 = foo3(); // int
      auto& ri4 = foo(); // Не скомпилируется
      auto& ri5 = foo1(); // int&
      auto& ri6 = foo2(); // Не скомпилируется
      auto& ri7 = foo3(); // const int&
      auto&& ri8 = foo(); // int&&
      auto&& ri9 = foo1(); // int&
      auto&& ri10 = foo2(); // const int&&
      auto&& ri11 = foo3(); // const int&
      int k = 5;
      decltype(k)&& rk = k; // Не скомпилируется
      decltype(foo())&& ri12 = foo(); // int&&
      decltype(foo1())&& ri13 = foo1(); // int&
      int i = 3;
      decltype(i) ri14; // int
      decltype((i)) ri15; // int&
    }
    

    Will the following snippets compile?

    2. auto lmbd = [](auto i){...}; // Сейчас - нет, но в С++14 - да
    3. void foo(auto i);  // Нет
    4. decltype(auto) var = some_expression; // Да, в С++14
    5. auto var = {1, 2, 3}; // Да, тип = std::initializer_list
    6. template void foo(T t){}
       foo({1, 2, 3}); // Нет
    

    Also popular now: