[dcl.init.list] (original) (raw)
9 Declarations [dcl]
9.5 Initializers [dcl.init]
9.5.5 List-initialization [dcl.init.list]
An initializer list may be empty.
List-initialization can occur in direct-initialization or copy-initialization contexts; list-initialization in a direct-initialization context is calleddirect-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization.
Direct-initialization that is not list-initialization is calleddirect-non-list-initialization.
[Note 1:
List-initialization can be used
- as the initializer in a variable definition ([dcl.init]),
- as the initializer in a new-expression ([expr.new]),
- in a return statement ([stmt.return]),
- as a for-range-initializer ([stmt.iter]),
- as a function argument ([expr.call]),
- as a template argument ([temp.arg.nontype]),
- as a subscript ([expr.sub]),
- as an argument to a constructor invocation ([dcl.init], [expr.type.conv]),
- as an initializer for a non-static data member ([class.mem]),
- in a mem-initializer ([class.base.init]), or
- on the right-hand side of an assignment ([expr.assign]).
[Example 1: int a = {1}; std::complex<double> z{1,2};new std::vectorstd::string\{"once", "upon", "a", "time"}; f( {"Nicholas","Annemarie"} ); return { "Norah" }; int* e {}; x = double{1}; std::mapstd::string,int\ anim = { {"bear",4}, {"cassowary",2}, {"tiger",7} }; — _end example_]
— _end note_]
A constructor is an initializer-list constructor if its first parameter is of type std::initializer_list<E> or reference tocv std::initializer_list<E> for some type E, and either there are no other parameters or else all other parameters have default arguments ([dcl.fct.default]).
[Note 2:
Initializer-list constructors are favored over other constructors in list-initialization ([over.match.list]).
Passing an initializer list as the argument to the constructor template template<class T> C(T) of a class C does not create an initializer-list constructor, because an initializer list argument causes the corresponding parameter to be a non-deduced context ([temp.deduct.call]).
— _end note_]
The templatestd::initializer_list is not predefined; if a standard library declaration ([initializer.list.syn], [std.modules]) of std::initializer_list is not reachable from ([module.reach]) a use of std::initializer_list — even an implicit use in which the type is not named ([dcl.spec.auto]) — the program is ill-formed.
List-initialization of an object or reference of type cv T is defined as follows:
- [Example 2: struct A { int x; int y; int z; }; A a{.y = 2, .x = 1}; A b{.x = 1, .z = 2}; — _end example_]
- If T is an aggregate class and the initializer list has a single element of type cv1 U, where U is T or a class derived from T, the object is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization).
- Otherwise, if T is a character array and the initializer list has a single element that is an appropriately-typed string-literal ([dcl.init.string]), initialization is performed as described in that subclause.
- Otherwise, if T is an aggregate, aggregate initialization is performed ([dcl.init.aggr]).
[Example 3: double ad[] = { 1, 2.0 }; int ai[] = { 1, 2.0 }; struct S2 { int m1;double m2, m3;}; S2 s21 = { 1, 2, 3.0 }; S2 s22 { 1.0, 2, 3 }; S2 s23 { }; — _end example_] - Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
- Otherwise, if T is a specialization of std::initializer_list, the object is constructed as described below.
- Otherwise, if T is a class type, constructors are considered.
The applicable constructors are enumerated and the best one is chosen through overload resolution ([over.match], [over.match.list]).
If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
[Example 4: struct S { S(std::initializer_list<double>); S(std::initializer_list<int>); S(std::initializer_list<S>); S(); }; S s1 = { 1.0, 2.0, 3.0 }; S s2 = { 1, 2, 3 }; S s3{s2}; S s4 = { }; — end example_]
[_Example 5: struct Map { Map(std::initializer_list<std::pairstd::string,int\>);}; Map ship = {{"Sophie",14}, {"Surprise",28}}; — end example_]
[_Example 6: struct S { S(int, double, double); S(); }; S s1 = { 1, 2, 3.0 }; S s2 { 1.0, 2, 3 }; S s3 { }; — _end example_] - Otherwise, if T is an enumeration with a fixed underlying type ([dcl.enum]) U, the initializer-list has a single element v of scalar type,v can be implicitly converted to U, and the initialization is direct-list-initialization, the object is initialized with the value T(v) ([expr.type.conv]); if a narrowing conversion is required to convert vto U, the program is ill-formed.
[Example 7: enum byte : unsigned char { }; byte b { 42 }; byte c = { 42 }; byte d = byte{ 42 }; byte e { -1 }; struct A { byte b; }; A a1 = { { 42 } }; A a2 = { byte{ 42 } }; void f(byte); f({ 42 }); enum class Handle : uint32_t { Invalid = 0 }; Handle h { 42 }; — _end example_] - Otherwise, if the initializer list is not a designated-initializer-list and has a single element of type E and eitherT is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization); if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.
[Example 8: int x1 {2}; int x2 {2.0}; — _end example_] - Otherwise, if T is a reference type, a prvalue is generated.
The prvalue initializes its result object by copy-list-initialization from the initializer list.
The prvalue is then used to direct-initialize the reference.
The type of the prvalue is the type referenced by T, unless T is “reference to array of unknown bound of U”, in which case the type of the prvalue is the type of x in the declaration U x[] H, where H is the initializer list.
[Note 3:
As usual, the binding will fail and the program is ill-formed if the reference type is an lvalue reference to a non-const type.
— end note_]
[_Example 9: struct S { S(std::initializer_list<double>); S(const std::string&); };const S& r1 = { 1, 2, 3.0 }; const S& r2 { "Spinach" }; S& r3 = { 1, 2, 3 }; const int& i1 = { 1 }; const int& i2 = { 1.1 }; const int (&iar)[2] = { 1, 2 }; struct A { } a;struct B { explicit B(const A&); };const B& b2{a}; struct C { int x; }; C&& c = { .x = 1 }; — _end example_] - Otherwise, if the initializer list has no elements, the object is value-initialized.
[Example 10: int** pp {}; — _end example_] - Otherwise, the program is ill-formed.
[Example 11: struct A { int i; int j; }; A a1 { 1, 2 }; A a2 { 1.2 }; struct B { B(std::initializer_list<int>);}; B b1 { 1, 2 }; B b2 { 1, 2.0 }; struct C { C(int i, double j);}; C c1 = { 1, 2.2 }; C c2 = { 1.1, 2 }; int j { 1 }; int k { }; — _end example_]
That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clausethat follows it in the comma-separated list of the initializer-list.
[Note 4:
This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of theinitializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call.
— _end note_]
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation generated and materialized ([conv.rval]) a prvalue of type “array of N const E”, where N is the number of elements 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 element of the initializer list, and thestd::initializer_list<E> object is constructed to refer to that array.
[Note 5:
A constructor or conversion function selected for the copy needs to be accessible ([class.access]) in the context of the initializer list.
— _end note_]
If a narrowing conversion is required to initialize any of the elements, the program is ill-formed.
[Note 6:
Backing arrays are potentially non-unique objects ([intro.object]).
— _end note_]
The backing array has the same lifetime as any other temporary object ([class.temporary]), except that initializing aninitializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary.
[Example 12: 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 will be implemented in a way roughly equivalent to this:void g(float x) { const double __a[3] = {double{1}, double{x}, double{3}}; f(std::initializer_list<double>(__a, __a+3));} void h() { static constexpr double __b[3] = {double{1}, double{2}, double{3}}; f(std::initializer_list<double>(__b, __b+3));} void r() { const A __c[3] = {A{1}, A{2}, A{3}}; q(std::initializer_list<A>(__c, __c+3));} assuming that the implementation can construct an initializer_list object with a pair of pointers, and with the understanding that __b does not outlive the call to f.
— _end example_]
[Example 13: typedef std::complex<double> cmplx; std::vector<cmplx> v1 = { 1, 2, 3 };void f() { std::vector<cmplx> v2{ 1, 2, 3 }; std::initializer_list<int> i3 = { 1, 2, 3 };} struct A { std::initializer_list<int> i4; A() : i4{ 1, 2, 3 } {} };
For v1 and v2, the initializer_list object is a parameter in a function call, so the array created for{ 1, 2, 3 } has full-expression lifetime.
For i3, the initializer_list object is a variable, so the array persists for the lifetime of the variable.
For i4, the initializer_list object is initialized in the constructor's ctor-initializer as if by binding a temporary array to a reference member, so the program is ill-formed ([class.base.init]).
— _end example_]
A narrowing conversion is an implicit conversion
- from a floating-point type to an integer type, or
- from a floating-point type T to another floating-point type whose floating-point conversion rank is neither greater than nor equal to that of T, except where the result of the conversion is a constant expression and either its value is finite and the conversion did not overflow, or the values before and after the conversion are not finite, or
- from an integer type or unscoped enumeration type to a floating-point type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type, or
- from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, 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 after integral promotions will fit into the target type, or
- from a pointer type or a pointer-to-member type to bool.
[Note 7:
As indicated above, such conversions are not allowed at the top level in list-initializations.
— _end note_]
[Example 14: int x = 999; const int y = 999;const int z = 99;char c1 = x; char c2{x}; char c3{y}; char c4{z}; unsigned char uc1 = {5}; unsigned char uc2 = {-1}; unsigned int ui1 = {-1}; signed int si1 = { (unsigned int)-1 }; int ii = {2.0}; float f1 { x }; float f2 { 7 }; bool b = {"meow"}; int f(int);int a[] = { 2, f(2), f(2.0) }; — _end example_]