Why Does Auto X{3} Deduce an Initializer_List

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 information
  • int{3} obviously means "create an int var with value taken from initializer list", thus its type is just int and can be used in any wider context (int i = int{3} will work and auto i = int{3} can deduce type, because right side is obviously of type int)
  • {3} by itself has no type (it can't be int, because it's not a value but an initializer list), so auto wouldn't work — but, because committee considered that auto 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).

Why auto is deduced differently?


#include <initializer_list>
#include <type_traits>

using namespace std;

int main(){
int x{};
auto x2 = x;
auto x3{x};

static_assert(is_same<int, decltype(x)>::value, "decltype(x) is the same as int");
static_assert(is_same<int, decltype(x2)>::value, "decltype(x2) is the same as int");
static_assert(is_same<std::initializer_list<int>, decltype(x3)>::value, "decltype(x3) is the same as int");
}

This will compile. x3 is deduced to be a std::initializer_list<int> due to:

Let T be the type that has been determined for a variable identifier d. Obtain P from T [...] if the initializer is a braced-init-list (8.5.4), with std::initializer_list<U>.

auto with parentheses and initialiser list

This is ill-formed. In short, braced-init-list can't be deduced in template argument deduction, it's considered as non-deduced context.

6) The parameter P, whose A is a braced-init-list, but P is not std::initializer_list or a reference to one:

Firstly, auto type deduction uses the rules of template argument deduction from a function call. [dcl.type.auto.deduct]/4

(emphasis mine)

If the placeholder is the auto type-specifier, the deduced type T'
replacing T is determined using the rules for template argument
deduction. Obtain P from T by replacing the occurrences of auto with
either a new invented type template parameter U or, if the
initialization is copy-list-initialization, with
std​::​initializer_­list<U>
. Deduce a value for U using the rules of
template argument deduction from a function call, where P is a
function template parameter type and the corresponding argument is e.
If the deduction fails, the declaration is ill-formed. [ Example:

const auto &i = expr;

The type of i is the deduced type of the parameter u in the call f(expr) of the following invented function template:

template <class U> void f(const U& u);

— end example ]

Note that auto x({1, 2, 3, 4}); is direct initialization, not copy initialization, then the invented type template parameter is just U, not std​::​initializer_­list<U>, and the corresponding argument is {1, 2, 3, 4}.

And in template argument deduction from a function call, template parameter can't be deduced from braced-init-list. [temp.deduct.call]/1

Template argument deduction is done by comparing each function template parameter type (call it P) that contains template-parameters that participate in template argument deduction with the type of the corresponding argument of the call (call it A) as described below. If removing references and cv-qualifiers from P gives std​::​initializer_­list or P'[N] for some P' and N and the argument is a non-empty initializer list ([dcl.init.list]), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument, and in the P'[N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context ([temp.deduct.type]). [ Example:

template<class T> void g(T);
g({1,2,3}); // error: no argument deduced for T

— end example ]

why this variable isn't deduced as initializer_list in g++ in C++14?


There is a proposal for C++1z that implements new type deduction rules for brace initialization

Not exactly. If you follow the link to the actual paper, it reads:

Direction from EWG is that we consider this a defect in C++14.

Which is enough to get implementors to also treat it as a defect, and hence change the compiler behaviour even in C++14 mode.

Why does g++5 deduces object instead of initializer_list in auto type deduction

There is a proposal for C++1z that implements new type deduction rules for brace initialization (N3922), and I guess gcc implemented them:

For direct list-initialization:

1. For a braced-init-list with only a single element, auto deduction will deduce from that entry;

2. For a braced-init-list with more than one element, auto deduction will be ill-formed.

[Example:

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.

-- end example]

Here is the gcc patch concerning the new changes with regards to "Unicorn initialization."

Why auto cannot accept braced-init-list when deducing return value?

std::initializer_list is a reference to an anonymous array.

The array itself lives at block scope where the initializer_list was created.

std::initializer_list<int> f() {
return {1,2,3};
}

is almost completely useless, because the array's lifetime is the body of f, and the initializer_list that refers to it exists in a nearly completely disjoint period of code.

Using that initializer_list is going to almost certainly be undefined behavior. You could probably ask its size and if it is empty, and that might be defined behavior (don't know, don't care enough to check), but you definitely cannot examine its contents.

Initializer lists are references to the data, not copies of the data.

If:

auto f() {
return {1,2,3};
}

deduced itself to be the above, it would be almost never useful.

The exception, that auto x = {1,2,3}; works, is the one and only case where initailizer_list<int> can be deduced from a set of {}. (In C++11, the same was true of auto x{1};, but that was depricated).

initializer_list with auto contains multiple expressions

The rule of auto type deduction changed since N3922. (This is considered as a defect in C++14).

In direct-list-initialization (but not in copy-list-initalization),
when deducing the meaning of the auto from a braced-init-list, the
braced-init-list must contain only one element, and the type of auto
will be the type of that element:

auto x1 = {3}; // x1 is std::initializer_list<int>
auto x2{1, 2}; // error: not a single element
auto x3{3}; // x3 is int
// (before N3922 x2 and x3 were both std::initializer_list<int>)

So before N3922, all the variables in your sample work fine and have type std::initializer_list<int>. But since N3922, for direct initialization (i.e. for x11 and x22) the braced-initializer must contain only one element (and their type would be the type of the element), then the code become ill-formed.

See N3922 and N3681 for more.

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 type T 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 function f(T).



Related Topics



Leave a reply



Submit