I am trying to understand what the correct behavior of C++11 should be when combining initialization lists and const auto
. I am getting different behavior between GCC and Clang for the following code and would like to know which is the correct one:
#include <iostream>
#include <typeinfo>
#include <vector>
int main()
{
const std::initializer_list<int> l1 = { 1, 2, 3 };
const auto l2 = { 1, 2, 3 };
std::cout << "explicit: " << typeid(l1).name() << std::endl;
std::cout << "auto: " << typeid(l2).name() << std::endl;
}
Compiled with g++ the output is:
explicit: St16initializer_listIiE
auto: St16initializer_listIKiE
While the clang++ compiled version produces:
explicit: St16initializer_listIiE
auto: St16initializer_listIiE
It seems that GCC is turning the auto
line into a std::initializer_list<const int>
while Clang produces std::initializer_list<int>
. The GCC version creates a problem when I use it to initialize a std::vector
. So the following works under Clang but produces a compiler error for GCC.
// Compiles under clang but fails for GCC because l4
std::vector<int> v2 { l2 };
If GCC is producing the correct version then it seems to suggest that the various STL containers should be extended to include another list initializer overload for these cases.
Note: this behavior seems to be consistent across multiple versions of GCC (4.8, 4.9, 5.2) and Clang (3.4 and 3.6).
GCC bug. [dcl.spec.auto]/p7 (quoting N4527):
When a variable declared using a placeholder type is initialized,
[...] the deduced return type or variable type is determined from the
type of its initializer. [...] Otherwise, let T
be the declared type
of the variable [...]. If the placeholder is the auto
type-specifier, the deduced type is determined using the rules for template argument deduction. If the initialization is
direct-list-initialization [...]. [...] Otherwise, 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 (14.8.2.1), where P
is a function template parameter
type and the corresponding argument is the initializer [...]. If the
deduction fails, the declaration is ill-formed. Otherwise, the type
deduced for the variable or return type is obtained by substituting
the deduced U
into P
.
Thus, in const auto l2 = { 1, 2, 3 };
, deduction is performed as if for the function template
template<class U> void meow(const std::initializer_list<U>);
given the call meow({1, 2, 3})
.
Now consider the const-less case auto l3 = { 1, 2, 3 };
(which GCC correctly deduces as std::initializer_list<int>
). Deduction in this case is performed as if for the function template
template<class U> void purr(std::initializer_list<U>);
given the call purr({1, 2, 3})
.
Since top-level cv-qualification of function parameters are ignored, it should be obvious that the two deduction should yield the same type.
[temp.deduct.call]/p1:
Template argument deduction is done by comparing each function
template parameter type (call it P
) with the type of the
corresponding argument of the call (call it A
) as described below.
If P
is a dependent type, removing references and cv-qualifiers from
P
gives std::initializer_list<P'>
[...] for some P'
[...] and
the argument is a non-empty initializer list (8.5.4), 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.
Deducing P'
(which is U
) against 1
, 2
, or 3
, all literals of type int
, obviously yields int
.
There is a gcc bug report wrong auto deduction from braced-init-list about this and similar cases and Richard Smith indicates it is a gcc bug:
Even simpler testcase:
#include <initializer_list>
const auto r = { 1, 2, 3 };
using X = decltype(r);
using X = const std::initializer_list<int>;
fails because decltype(r)
is deduced as const std::initializer_list<const int>
rather than const std::initializer_list<int>
.
The section of the draft C++ standard would be section 7.1.6.4
[dcl.spec.auto] which says:
When a variable declared using a placeholder type is initialized, or a return statement occurs in a function
declared with a return type that contains a placeholder type, the deduced return type or variable type
is determined from the type of its initializer. [...] Let T be the declared type of the variable or return type of the function. If the
placeholder is the auto type-specifier, the deduced type is determined using the rules for template argument
deduction. [...] Otherwise, obtain P from T by replacing the occurrences of auto with either a
new invented type template parameter U or, if the initializer is a braced-init-list, with std::initializer_-
list. Deduce a value for U using the rules of template argument deduction from a function call (14.8.2.1),
where P is a function template parameter type and the initializer is the corresponding argument [...] [ Example:
auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>
auto x2 = { 1, 2.0 }; // error: cannot deduce element type
—end example ] [ 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 ]