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 theauto
type-specifier, the deduced type is determined using the rules for template argument deduction. If the initialization is direct-list-initialization [...]. [...] Otherwise, obtainP
fromT
by replacing the occurrences ofauto
with either a new invented type template parameterU
or, if the initialization is copy-list-initialization, withstd::initializer_list<U>
. Deduce a value forU
using the rules of template argument deduction from a function call (14.8.2.1), whereP
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 deducedU
intoP
.
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 itA
) as described below. IfP
is a dependent type, removing references and cv-qualifiers fromP
givesstd::initializer_list<P'>
[...] for someP'
[...] and the argument is a non-empty initializer list (8.5.4), then deduction is performed instead for each element of the initializer list, takingP'
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 asconst std::initializer_list<const int>
rather thanconst 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 ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With