List-initialization (since C++11)
Initializes an object from a brace-enclosed initializer list.
Contents |
[edit]Syntax
[edit]Direct-list-initialization
T object{ arg1, arg2, ...};
| (1) | ||||||||
T{ arg1, arg2, ...}
| (2) | ||||||||
new T{ arg1, arg2, ...}
| (3) | ||||||||
Class{ T member{ arg1, arg2, ...}; };
| (4) | ||||||||
Class:: Class() : member{ arg1, arg2, ...} {...
| (5) | ||||||||
[edit]Copy-list-initialization
T object= { arg1, arg2, ...};
| (6) | ||||||||
function({ arg1, arg2, ...})
| (7) | ||||||||
return { arg1, arg2, ...};
| (8) | ||||||||
object[{ arg1, arg2, ...}]
| (9) | ||||||||
object= { arg1, arg2, ...}
| (10) | ||||||||
U({ arg1, arg2, ...})
| (11) | ||||||||
Class{ T member= { arg1, arg2, ...}; };
| (12) | ||||||||
List initialization is performed in the following situations:
- direct-list-initialization (both explicit and non-explicit constructors are considered)
- copy-list-initialization (both explicit and non-explicit constructors are considered, but only non-explicit constructors may be called)
operator[]
, where list-initialization initializes the parameter of the overloaded operatorU
in this example is not the type that is being list-initialized; U
's constructor's parameter is)[edit]Explanation
The effects of list-initialization of an object of type (possibly cv-qualified) T
are:
| (since C++20) |
- If
T
is an aggregate class and the brace-enclosed initializer list, which does not contain a designated initializer list,(since C++20) has a single initializer clause of the same or derived type (possibly cv-qualified), the object is initialized from that initializer clause (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization). - Otherwise, if
T
is a character array and the brace-enclosed initializer list has a single initializer clause that is an appropriately-typed string literal, the array is initialized from the string literal as usual.
- Otherwise, if
T
is an aggregate type, aggregate initialization is performed.
- Otherwise, if the brace-enclosed initializer list is empty and
T
is a class type with a default constructor, value-initialization is performed.
- Otherwise, if
T
is a specialization of std::initializer_list, the object is initialized as described below.
- Otherwise, if
T
is a class type, the constructors ofT
are considered, in two phases:
- All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list.
- If the previous stage does not produce a match, all constructors of
T
participate in overload resolution against the set of arguments that consists of the initializer clauses of the brace-enclosed initializer list, with the restriction that only non-narrowing conversions are allowed. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all).
- If the previous stage does not produce a match, all constructors of
| (since C++17) |
- Otherwise (if
T
is not a class type), if the brace-enclosed initializer list has only one initializer clause and eitherT
is not a reference type or is a reference type whose referenced type is same as or is a base class of the type of the initializer clause,T
is direct-initialized (in direct-list-initialization) or copy-initialized (in copy-list-initialization), except that narrowing conversions are not allowed.
- Otherwise, if
T
is a reference type that is not compatible with the type of the initializer clause:
| (until C++17) |
| (since C++17) |
- Otherwise, if the brace-enclosed initializer list has no initializer clause,
T
is value-initialized.
[edit]List-initializing std::initializer_list
An object of type std::initializer_list<E> is constructed from an initializer list as if the compiler generated and materialized(since C++17) a prvalue of type “array of Nconst E”, where N is the number of initializer clauses in the initializer list; this is called the initializer list’s backing array.
Each element of the backing array is copy-initialized with the corresponding initializer clause of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. A constructor or conversion function selected for the copy is required to be accessible in the context of the initializer list. If a narrowing conversion is required to initialize any of the elements, the program is ill-formed.
The backing array has the same lifetime as any other temporary object, except that initializing an std::initializer_list object from the backing array extends the lifetime of the array exactly like binding a reference to a temporary.
void f(std::initializer_list<double> il); void g(float x){ f({1, x, 3});} void h(){ f({1, 2, 3});} struct A { mutable int i;}; void q(std::initializer_list<A>); void r(){ q({A{1}, A{2}, A{3}});} // The initialization above will be implemented in a way roughly equivalent to below,// assuming that the compiler can construct an initializer_list object with a pair of// pointers, and with the understanding that `__b` does not outlive the call to `f`. void g(float x){constdouble __a[3]={double{1}, double{x}, double{3}};// backing array f(std::initializer_list<double>(__a, __a +3));} void h(){staticconstexprdouble __b[3]={double{1}, double{2}, double{3}};// backing array f(std::initializer_list<double>(__b, __b +3));} void r(){const A __c[3]={A{1}, A{2}, A{3}};// backing array q(std::initializer_list<A>(__c, __c +3));}
Whether all backing arrays are distinct (that is, are stored in non-overlapping objects) is unspecified:
bool fun(std::initializer_list<int> il1, std::initializer_list<int> il2){return il2.begin()== il1.begin()+1;} bool overlapping = fun({1, 2, 3}, {2, 3, 4});// the result is unspecified:// the back arrays can share// storage within {1, 2, 3, 4}
[edit]Narrowing conversions
List-initialization limits the allowed implicit conversions by prohibiting the following:
- conversion from a floating-point type to an integer type
- conversion from a floating-point type
T
to another floating-point type whose floating-point conversion rank is neither greater than nor equal to that ofT
, except where the conversion result is a constant expression and one of the following conditions is satisfied:- The converted value is finite, and the conversion does not overflow.
- The values before and after the conversion are not finite.
- conversion from an integer type to a floating-point type, except where the source is a constant expression whose value can be stored exactly in the target type
- conversion from integer or unscoped enumeration type to integer type that cannot represent all values of the original, except where
- the source is a bit-field whose width w is less than that of its type (or, for an enumeration type, its underlying type) and the target type can represent all the values of a hypothetical extended integer type with width w and with the same signedness as the original type, or
- the source is a constant expression whose value can be stored exactly in the target type
- conversion from a pointer type or pointer-to-member type to bool
[edit]Notes
Every initializer clause is sequenced before any initializer clause that follows it in the brace-enclosed initializer list. This is in contrast with the arguments of a function call expression, which are unsequenced(until C++17)indeterminately sequenced(since C++17).
A brace-enclosed initializer list is not an expression and therefore has no type, e.g. decltype({1, 2}) is ill-formed. Having no type implies that template type deduction cannot deduce a type that matches a brace-enclosed initializer list, so given the declaration template<class T>void f(T); the expression f({1, 2, 3}) is ill-formed. However, the template parameter can otherwise be deduced, as is the case for std::vector<int> v(std::istream_iterator<int>(std::cin), {}), where the iterator type is deduced by the first argument but also used in the second parameter position. A special exception is made for type deduction using the keyword auto, which deduces any brace-enclosed initializer list as std::initializer_list in copy-list-initialization.
Also because a brace-enclosed initializer list has no type, special rules for overload resolution apply when it is used as an argument to an overloaded function call.
Aggregates copy/move initialize directly from brace-enclosed initializer list of a single initializer clause of the same type, but non-aggregates consider std::initializer_list constructors first:
struct X {};// aggregate struct Q // non-aggregate{ Q()=default; Q(Q const&)=default; Q(std::initializer_list<Q>){}}; int main(){ X x; X x2 = X{x};// copy-constructor (not aggregate initialization) Q q; Q q2 = Q{q};// initializer-list constructor (not copy constructor)}
Some compilers (e.g., gcc 10) only consider conversion from a pointer or a pointer-to-member to bool narrowing in C++20 mode.
Feature-test macro | Value | Std | Feature |
---|---|---|---|
__cpp_initializer_lists | 200806L | (C++11) | List-initialization and std::initializer_list |
[edit]Example
#include <iostream>#include <map>#include <string>#include <vector> struct Foo {std::vector<int> mem ={1, 2, 3};// list-initialization of a non-static memberstd::vector<int> mem2; Foo(): mem2{-1, -2, -3}{}// list-initialization of a member in constructor}; std::pair<std::string, std::string> f(std::pair<std::string, std::string> p){return{p.second, p.first};// list-initialization in return statement} int main(){int n0{};// value-initialization (to zero)int n1{1};// direct-list-initialization std::string s1{'a', 'b', 'c', 'd'};// initializer-list constructor callstd::string s2{s1, 2, 2};// regular constructor callstd::string s3{0x61, 'a'};// initializer-list ctor is preferred to (int, char) int n2 ={1};// copy-list-initializationdouble d =double{1.2};// list-initialization of a prvalue, then copy-initauto s4 =std::string{"HelloWorld"};// same as above, no temporary// created since C++17 std::map<int, std::string> m =// nested list-initialization{{1, "a"}, {2, {'a', 'b', 'c'}}, {3, s1}}; std::cout<< f({"hello", "world"}).first// list-initialization in function call<<'\n'; constint(&ar)[2]={1, 2};// binds an lvalue reference to a temporary arrayint&& r1 ={1};// binds an rvalue reference to a temporary int// int& r2 = {2}; // error: cannot bind rvalue to a non-const lvalue ref // int bad{1.0}; // error: narrowing conversionunsignedchar uc1{10};// okay// unsigned char uc2{-1}; // error: narrowing conversion Foo f; std::cout<< n0 <<' '<< n1 <<' '<< n2 <<'\n'<< s1 <<' '<< s2 <<' '<< s3 <<'\n';for(auto p : m)std::cout<< p.first<<' '<< p.second<<'\n';for(auto n : f.mem)std::cout<< n <<' ';for(auto n : f.mem2)std::cout<< n <<' ';std::cout<<'\n'; [](...){}(d, ar, r1, uc1);// has effect of [[maybe_unused]]}
Output:
world 0 1 1 abcd cd aa 1 a 2 abc 3 abcd 1 2 3 -1 -2 -3
[edit]Defect reports
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
DR | Applied to | Behavior as published | Correct behavior |
---|---|---|---|
CWG 1288 | C++11 | list-initializing a reference with a brace-enclosed initializer list of a single initializer clause always bound the reference to a temporary | bind to that initializer clause if valid |
CWG 1290 | C++11 | the lifetime of the backing array was not correctly specified | specified same as other temporary objects |
CWG 1324 | C++11 | initialization considered first for initialization from {} | aggregate initialization considered first |
CWG 1418 | C++11 | the type of the backing array lacked const | const added |
CWG 1467 | C++11 | same-type initialization of aggregates and character arrays was prohibited; initializer-list constructors had priority over copy constructors for single-clause lists | same-type initialization allowed; single-clause lists initialize directly |
CWG 1494 | C++11 | when list-initializing a reference with an initializer clause of an incompatible type, it was unspecified whether the temporary created is direct-list-initialized or copy-list-initialized | it depends on the kind of initialization for the reference |
CWG 2137 | C++11 | initializer-list constructors lost to copy constructors when list-initializing X from {X} | non-aggregates consider initializer-lists first |
CWG 2252 | C++17 | enumerations could be list-initialized from non-scalar values | prohibited |
CWG 2267 | C++11 | the resolution of CWG issue 1494 made clear that temporaries could be direct-list-initialized | they are copy-list-initialized when list-initializing references |
CWG 2374 | C++17 | direct-list-initialization of an enum allowed too many source types | restricted |
CWG 2627 | C++11 | a narrow bit-field of a larger integer type can be promoted to a smaller integer type, but it was still a narrowing conversion | it is not a narrowing conversion |
CWG 2713 | C++20 | references to aggregate classes could not be initialized by designated initializer lists | allowed |
CWG 2830 | C++11 | list-initialization did not ignore the top-level cv-qualification | ignores |
CWG 2864 | C++11 | floating-point conversions that overflow were not narrowing | they are narrowing |
P1957R2 | C++11 | conversion from a pointer/pointer-to-member to bool was not narrowing | considered narrowing |
P2752R3 | C++11 | backing arrays with overlapping lifetime could not overlap | they may overlap |