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;
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};
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();
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);
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();
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();
Will the following fragments be compiled?
2. auto lmbd = [](auto i){...};