List-initialization (since C++11)
inline specifier noexcept specifier (C++11) typedef declaration Initializes an object from a brace-enclosed initializer list.
Contents
[edit] Syntax
[edit] Direct-list-initialization
{ arg1, arg2, ... };
T object{.des1 = arg1 , .des2 { arg2 } ... };
{ arg1, arg2, ... }
T {.des1 = arg1 , .des2 { arg2 } ... }
new T { arg1, arg2, ... }
new T {.des1 = arg1 , .des2 { arg2 } ... }
{ T member { arg1, arg2, ... }; };
Class { T member {.des1 = arg1 , .des2 { arg2 } ... }; };
::Class() : member { arg1, arg2, ... } {...
Class::Class() : member {.des1 = arg1 , .des2 { arg2 } ...} {...
[edit] Copy-list-initialization
= { arg1, arg2, ... };
T object = {.des1 = arg1 , .des2 { arg2 } ... };
({ arg1, arg2, ... })
function ({.des1 = arg1 , .des2 { arg2 } ... })
return { arg1, arg2, ... };
return {.des1 = arg1 , .des2 { arg2 } ... };
[{ arg1, arg2, ... }]
object [{.des1 = arg1 , .des2 { arg2 } ... }]
= { arg1, arg2, ... }
object = {.des1 = arg1 , .des2 { arg2 } ... }
({ arg1, arg2, ... })
U ({.des1 = arg1 , .des2 { arg2 } ... })
{ T member = { arg1, arg2, ... }; };
Class { T member = {.des1 = arg1 , .des2 { arg2 } ... }; };
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:
- If the brace-enclosed initializer list contains a designated initializer list and
Tis not a reference type,Tmust be an aggregate class. The ordered identifiers in the designators of the designated initializer list must form a subsequence of the ordered identifiers in the direct non-static data members ofT. Aggregate initialization is performed.
- If
Tis 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
Tis 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
Tis an aggregate type, aggregate initialization is performed.
- Otherwise, if the brace-enclosed initializer list is empty and
Tis a class type with a default constructor, value-initialization is performed.
- Otherwise, if
Tis a specialization of std::initializer_list , the object is initialized as described below.
- Otherwise, if
Tis a class type, the constructors ofTare 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
Tparticipate 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
- Otherwise, if
Tis an enumeration type that with fixed underlying typeU, the brace-enclosed initializer list has only one initializer v, and all following conditions are satisfied, then the enumeration is initialized with the result of converting v toU:- The initialization is direct-list-initialization.
- v is of scalar type.
- v is implicitly convertible to
U. - The conversion from v to
Uis non-narrowing.
- Otherwise (if
Tis not a class type), if the brace-enclosed initializer list has only one initializer clause and eitherTis 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,Tis direct-initialized (in direct-list-initialization) or copy-initialized (in copy-list-initialization), except that narrowing conversions are not allowed.
- Otherwise, if
Tis a reference type that is not compatible with the type of the initializer clause:
- a prvalue temporary of the type referenced by
Tis copy-list-initialized, and the reference is bound to that temporary (this fails if the reference is a non-const lvalue reference).
- a prvalue temporary of the type referenced by
- a prvalue is generated. The prvalue initializes its result object by copy-list-initialization. The prvalue is then used to direct-initialize the reference (this fails if the reference is a non-const lvalue reference). The type of the temporary is the type referenced by
T, unlessTis "reference to array of unknown bound ofU", in which case the type of the temporary is the type of x in the declaration U x[] H, whereHis the initializer list(since C++20).
- a prvalue is generated. The prvalue initializes its result object by copy-list-initialization. The prvalue is then used to direct-initialize the reference (this fails if the reference is a non-const lvalue reference). The type of the temporary is the type referenced by
- Otherwise, if the brace-enclosed initializer list has no initializer clause,
Tis 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 N const 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) { const double __a[3] = {double{1}, double{x}, double{3}}; // backing array f(std::initializer_list <double>(__a, __a + 3)); } void h() { static constexpr double __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
Tto 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 member std::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 call std::string s2{s1, 2, 2}; // regular constructor call std::string s3{0x61, 'a'}; // initializer-list ctor is preferred to (int, char) int n2 = {1}; // copy-list-initialization double d = double{1.2}; // list-initialization of a prvalue, then copy-init auto 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'; const int (&ar)[2] = {1, 2}; // binds an lvalue reference to a temporary array int&& 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 conversion unsigned char 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 |