📜 ⬆️ ⬇️

Auto and decltype secrets

The new standard of the language was 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 ++, the use of these new tools is not without nuances. Some of them I will try to highlight in this article.

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

1. What type will the ri1..riN variables appear 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 fragments be compiled?
 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<typename T> 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 the life of programmers does not seem to be honey, all these 3 mechanisms deduce types in their own way. The mechanism used by auto exactly copies the template mechanism, except for the type std :: initializer_list .

 auto var = {1, 2, 3}; // Ok, var    std::initializer_list<int> template<typename T> void foo(T t); foo({1, 2, 3}); //   


There are few explanations for this behavior and all of them do not differ in intelligibility. Scott Meyers, for example, writes on this subject as follows: “I’m not a concept. If you know, please tell me! ” . In C ++ 14, they are not going to change this mechanism. For an explanation, you can try to accept the fact that such amazing things work, for example:
 template<typename T> void fill_from_list(T& cont, const T& l); std::vector<int> v; fill_from_list(v, {1, 2, 3}); 

')
Auto

So, how does the `auto` type output? Unfortunately, there is no simple rule for all occasions, except, perhaps, the fact that `auto`, in its type inference, generally throws away cv qualifiers and references. Below I will list the most important points.

one.
 auto var = some_expression; 

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


2
 auto& var = some_expression; 

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

3
 auto&& var = some_expression; 

The rule of “universal links” invented (or at least sounded by Scott Meyers) is in effect here. It lies in the fact that the type of var will depend on what value category in some_expression . If rvalue , then the type of var will be T && , if lvalue , then T & . Cv qualifiers are preserved.

Auto as a function parameter

auto cannot be used as a parameter of a function and changes in this behavior are not expected. 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 resolve the 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 on the one hand is more complicated (if you look at formal rules), on the other hand it is simpler (if you highlight the main points). I will formulate these rules as I understood them.
So, it is necessary to distinguish 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 - expression. In this case, the decltype (expr) type will be a type that could return this expression , with the proviso that decltype (expr) will be of type T & ( const T & ) if expr returns lvalue , T if expr returns 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 explanatory 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, the expression will return, rvalue or xvalue , and we want to use the type, we can use the standard std :: remove_reference template to “clear” the type from the links.

Decltype (auto)

This is a new “feature” of the language, which will be included in C ++ 14. It is needed to preserve decltype semantics when declaring auto variables and will be used in cases when we are not satisfied with auto discarding references and cv qualifiers and, possibly, in conjunction with the new C ++ 14 feature - output 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 there was an expression for 2 lines instead of foo () , and such in C ++ is not uncommon.

Answers

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

one.
 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 fragments be compiled?

 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<int> 6. template<typename T> void foo(T t){} foo({1, 2, 3}); //  

Source: https://habr.com/ru/post/206458/


All Articles