auto&&uref3 = 27; // 27 is int and rvalue,
// so uref3's type is int&&
Item 1concludes with a discussion of how array and function names decay into pointers for non-reference type specifiers. That happens in auto
type deduction, too:
const char name[] = // name's type is const char[13]
"R. N. Briggs";
autoarr1 = name; // arr1's type is const char*
auto&arr2 = name; // arr2's type is
// const char (&)[13]
void someFunc(int, double); // someFunc is a function;
// type is void(int, double)
autofunc1 = someFunc; // func1's type is
// void (*)(int, double)
auto&func2 = someFunc; // func2's type is
// void (&)(int, double)
As you can see, auto
type deduction works like template type deduction. They're essentially two sides of the same coin.
Except for the one way they differ. We'll start with the observation that if you want to declare an int
with an initial value of 27, C++98 gives you two syntactic choices:
intx1 = 27;
intx2(27);
C++11, through its support for uniform initialization, adds these:
intx3 = { 27 };
intx4{ 27 };
All in all, four syntaxes, but only one result: an int
with value 27.
But as Item 5explains, there are advantages to declaring variables using auto instead of fixed types, so it'd be nice to replace int
with auto
in the above variable declarations. Straightforward textual substitution yields this code:
autox1 = 27;
autox2(27);
autox3 = { 27 };
autox4{ 27 };
These declarations all compile, but they don't have the same meaning as the ones they replace. The first two statements do, indeed, declare a variable of type int
with value 27. The second two, however, declare a variable of type std::initializer_list
containing a single element with value 27!
auto x1 = 27; // type is int , value is 27
auto x2(27); // ditto
auto x3 = { 27 }; // type is std::initializer_list ,
// value is { 27 }
auto x4{ 27 }; // ditto
This is due to a special type deduction rule for auto
. When the initializer for an auto
-declared variable is enclosed in braces, the deduced type is a std::initializer_list
. If such a type can't be deduced (e.g., because the values in the braced initializer are of different types), the code will be rejected:
auto x5 = { 1, 2, 3.0}; // error! can't deduce T for
// std::initializer_list
As the comment indicates, type deduction will fail in this case, but it's important to recognize that there are actually two kinds of type deduction taking place. One kind stems from the use of auto
: x5
's type has to be deduced. Because x5
's initializer is in braces, x5
must be deduced to be a std::initializer_list
. But std::initializer_list
is a template. Instantiations are std::initializer_list
for some type T
, and that means that T
's type must also be deduced. Such deduction falls under the purview of the second kind of type deduction occurring here: template type deduction. In this example, that deduction fails, because the values in the braced initializer don't have a single type.
The treatment of braced initializers is the only way in which auto
type deduction and template type deduction differ. When an auto
-declared variable is initialized with a braced initializer, the deduced type is an instantiation of std::initializer_list
. But if the corresponding template is passed the same initializer, type deduction fails, and the code is rejected:
autox = { 11, 23, 9 }; // x's type is
// std::initializer_list
template // template with parameter
void f( Tparam); // declaration equivalent to
// x's declaration
f( { 11, 23, 9 }); // error! can't deduce type for T
However, if you specify in the template that param
is a std::initializer_list
for some unknown T
, template type deduction will deduce what T
is:
template
void f( std::initializer_list<T >initList);
f( { 11, 23, 9 }); // T deduced as int, and initList's
// type is std::initializer_list
So the only real difference between auto
and template type deduction is that auto
assumes that a braced initializer represents a std::initializer_list
, but template type deduction doesn't.
You might wonder why auto
type deduction has a special rule for braced initializers, but template type deduction does not. I wonder this myself. Alas, I have not been able to find a convincing explanation. But the rule is the rule, and this means you must remember that if you declare a variable using auto
and you initialize it with a braced initializer, the deduced type will always be std::initializer_list
. It's especially important to bear this in mind if you embrace the philosophy of uniform initialization — of enclosing initializing values in braces as a matter of course. A classic mistake in C++11 programming is accidentally declaring a std::initializer_list
variable when you mean to declare something else. This pitfall is one of the reasons some developers put braces around their initializers only when they have to. (When you have to is discussed in Item 7.)
For C++11, this is the full story, but for C++14, the tale continues. C++14 permits auto
to indicate that a function's return type should be deduced (see Item 3), and C++14 lambdas may use auto
in parameter declarations. However, these uses of auto
employ template type deduction, not auto
type deduction. So a function with an auto
return type that returns a braced initializer won't compile:
autocreateInitList() {
return { 1, 2, 3 }; // error: can't deduce type
} // for { 1, 2, 3 }
The same is true when auto
is used in a parameter type specification in a C++14 lambda:
std::vector v;
…
auto resetV =
[&v](const auto& newValue) { v = newValue; }; // C++14
…
resetV( { 1, 2, 3 }); // error! can't deduce type
// for { 1, 2, 3 }
Things to Remember
• auto
type deduction is usually the same as template type deduction, but auto type deduction assumes that a braced initializer represents a std::initializer_list
, and template type deduction doesn't.
• auto
in a function return type or a lambda parameter implies template type deduction, not auto type deduction.
Читать дальше