What is the difference between auto deduction and template type deduction?
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.
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:
auto x = { 11, 23, 9 }; // x's type is
//std::initializer_list<int>
template<typename T> // template with parameter
void f(T param); // template with parameter
However, if you specify in the template that param is a std::initializer_list<T>
for some unknown T, template type deduction will deduce what T is:
template<typename T>
void f(std::initializer_list<T> initList);
f({ 11, 23, 9 }); // T deduced as int, and initList's
// type is std::initializer_list<int>
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.
Why is there a special type deduction rule for auto and braced initializers in C++11/C++14?
The rationale is in N2640, which wanted to ban deduction of a plain type parameter from a braced initializer list in general:
template<class T>
void inc(T, int); // (1)
template<class T>
void inc(std::initializer_list<T>, long); // (2)
inc({1, 2, 3}, 3); // Calls (2). (If deduction had succeeded
// for (1), (1) would have been called — a
// surprise.)
But carved out a special exception for auto
:
On the other hand, being able to deduce an
initializer_list<X>
for
T
is attractive to allow:auto x = { 1, 1, 2, 3, 5 };
f(x);
g(x);
which was deemed desirable behavior since the very beginning of the
EWG discussions about initializer lists. Rather than coming up with a
clever deduction rule for a parameter typeT
matched with a {}-list
(an option we pursued in earlier sketches and drafts of this paper),
we now prefer to handle this with a special case for "auto" variable
deduction when the initializer is a {}-list. I.e., for the specific
case of a variable declared with an "auto" type specifier and a
{}-list initializer, the "auto" is deduced as for a function
f(initializer_list<T>)
instead of as for a functionf(T)
.
Why is there a special type deduction rule for auto and braced initializers in C++11/C++14?
The rationale is in N2640, which wanted to ban deduction of a plain type parameter from a braced initializer list in general:
template<class T>
void inc(T, int); // (1)
template<class T>
void inc(std::initializer_list<T>, long); // (2)
inc({1, 2, 3}, 3); // Calls (2). (If deduction had succeeded
// for (1), (1) would have been called — a
// surprise.)
But carved out a special exception for auto
:
On the other hand, being able to deduce an
initializer_list<X>
for
T
is attractive to allow:auto x = { 1, 1, 2, 3, 5 };
f(x);
g(x);
which was deemed desirable behavior since the very beginning of the
EWG discussions about initializer lists. Rather than coming up with a
clever deduction rule for a parameter typeT
matched with a {}-list
(an option we pursued in earlier sketches and drafts of this paper),
we now prefer to handle this with a special case for "auto" variable
deduction when the initializer is a {}-list. I.e., for the specific
case of a variable declared with an "auto" type specifier and a
{}-list initializer, the "auto" is deduced as for a function
f(initializer_list<T>)
instead of as for a functionf(T)
.
return and auto deduce std::initializer_list
Well, because the Standard says so, and because a braced-init-list is not an expression. Per paragraph 5.1.2/4 of the C++11 Standard:
[...] If
a lambda-expression does not include a trailing-return-type, it is as if the trailing-return-type denotes the
following type:— if the compound-statement is of the form
{
attribute-specifier-seq(opt)return
expression; }
the type of the returned expression after lvalue-to-rvalue conversion (4.1), array-to-pointer conversion
(4.2), and function-to-pointer conversion (4.3);— otherwise,
void
.
The above makes it clear that the return type will be deduced to be anything else then void
if and only if the return
statement is followed by an expression, and a braced-init-list is not in itself an expression - it does not have a type, and it does not yield a value. It is just a language construct that can be used in the context of initialization.
The above paragraph also provides an example:
[ Example:
auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return { 1, 2 }; }; // error: the return type is void (a
// braced-init-list is not an expression)
—end example ]
Finally, if the question is:
"Why a special rule was introduced for deducing the type of an auto
variable initialized from a braced-init-list, while a similar rule was not introduced for deducing the return type of a lambda when return
is followed by a braced-init-list?"
Then the question is not constructive. Also notice, that type deduction for templates does not work with braced-init-lists either:
template<typename T>
void foo(T);
foo({1, 2}); // ERROR! T is NOT deduced to be std::initializer_list<int>
Using auto to deduce the type of a nested initializer list
I have a program where these initializer lists could be of arbitrary sizes and depths so hardcoding the types is not practical.
Then you need to fix that problem.
You should not think of braced-init-lists as a quick-and-dirty way of making arrays of values without having to think about their types. That's not their purpose. Their purpose is to initialize values. The type std::initializer_list
is intended to be an intermediary phase in the process of initializing some type (which is why constructors that take a single initializer_list
are given special meaning in list initialization).
If you want to have arrays of arrays of arrays of various depths and such, then you're going to need to figure out which type that construct needs to be and type it out. auto
can only deduce a single level of braced-init-list; you'll need to specify the type(s) explicitly if you need deeper levels.
there is a special exception made for type deduction using
auto
Yes, there is. But it only applies to deducing a list for auto
itself, not for anything that auto
's deduction would require.
In order for auto list = {{1, 2, 3}};
to work, the compiler has to deduce two types: the type to be used for {1, 2, 3}
and the type to be used for list
. The deduction of the type for list
requires deducing the type for the nested braced-init-list. But you can't deduce the type of a braced-init-list. Hence it does not work.
It should also be noted that, even if it did work, it wouldn't actually work. The reason being that the initializer_list
inside of list
would refer to a temporary array. A temporary array that would be destroyed at the end of the initializing expression. It's basically the same reason why string_view sv = std::string("foo");
doesn't produce something useful.
Why does auto x{3} deduce an initializer_list?
To make long story short:
- a braced initializer expression
{}
has no type by itself auto
has to infer type informationint{3}
obviously means "create anint
var with value taken from initializer list", thus its type is justint
and can be used in any wider context (int i = int{3}
will work andauto i = int{3}
can deduce type, because right side is obviously of typeint
){3}
by itself has no type (it can't beint
, because it's not a value but an initializer list), soauto
wouldn't work — but, because committee considered thatauto
should still work in this case, they decided that the "best" type for (yeah, typeless by definition) initializer list would be...std::initializer_list
, as you already probably guessed.
But, as you pointed out, this made the whole behaviour of auto
quite semantically inconsistent. That's why there were proposals to change it — namely N3681, N3912 and N3922 — submitted to the committee. Former proposal was REJECTED as FI3 due to no committee consensus on this matter, http://isocpp.org/files/papers/n3852.html#FI3 , current (N3922) got adopted ca. Q1 of 2015;
tl;dr you may assume that standards-compliant compilers1 with bleeding-edge C++ support2 either have the new, more sane-ish semantics already in place, or will have it shortly.
The Standardization Committee acknowledged the problem by adopting N3922 into draft C++17.
— so it's
auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>
auto x2 = { 1, 2.0 }; // error: cannot deduce element type
auto x3{ 1, 2 }; // error: not a single element
auto x4 = { 3 }; // decltype(x4) is std::initializer_list<int>
auto x5{ 3 }; // decltype(x5) is int
now, for better or worse.
further reading:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3681.html
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3912.html
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3922.html
http://scottmeyers.blogspot.com/2014/03/if-braced-initializers-have-no-type-why.html
http://herbsutter.com/2014/11/24/updates-to-my-trip-report/
1GCC 5.1 (& up) apparently uses N3922 even in C++11/C++14 mode
2Clang 3.8, with the caveat
This is a backwards-incompatible change that is applied to all language versions that allow type deduction from auto (per the request of the C++ committee).
Related Topics
How to Tell If the C Function Atoi Failed or If It Was a String of Zeros
How to Make a Portable Isnan/Isinf Function
Universal Less<> for Pointers in C++ Standard
Why Doesn't Std::String Provide Implicit Conversion to Char*
What Is the Proper Way of Doing Event Handling in C++
When Is a Type in C++11 Allowed to Be Memcpyed
Why Do Un-Named C++ Objects Destruct Before the Scope Block Ends
Why Is It Not Good to Use Recursive Inheritance for Std::Tuple Implementations
Complete C++ I18N Gettext() "Hello World" Example
In C++, How to Forward Declare a Class as Inheriting from Another Class
Easiest Way to Rotate by 90 Degrees an Image Using Opencv
How to Replace All Instances of a String with Another String
How to Directly Bind a Member Function to an Std::Function in Visual Studio 11
Converting Epoch Time to "Real" Date/Time
Getting a Vector<Derived*> into a Function That Expects a Vector<Base*>
Move Semantics == Custom Swap Function Obsolete