Files
cppdraft_translate/cppdraft/dcl/init/list.md
2025-10-25 03:02:53 +03:00

21 KiB
Raw Blame History

[dcl.init.list]

9 Declarations [dcl]

9.5 Initializers [dcl.init]

9.5.5 List-initialization [dcl.init.list]

1

#

List-initialization is initialization of an object or reference from abraced-init-list.

Such an initializer is called an initializer list, and the comma-separatedinitializer-clauses of the initializer-list ordesignated-initializer-clauses of the designated-initializer-list are called the elements of the initializer 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 z{1,2};new std::vectorstd::string{"once", "upon", "a", "time"}; // 4 string elements f( {"Nicholas","Annemarie"} ); // pass list of two elementsreturn { "Norah" }; // return list of one elementint* e {}; // initialization to zero / null pointer x = double{1}; // explicitly construct a double std::mapstd::string,int anim = { {"bear",4}, {"cassowary",2}, {"tiger",7} }; — end example]

— end note]

2

#

A constructor is an initializer-list constructor if its first parameter is of type std::initializer_list or reference tocv std::initializer_list 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 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.

3

#

List-initialization of an object or reference of type cv T is defined as follows:

  • (3.1)

    If the braced-init-list contains a designated-initializer-list andT is not a reference type,T shall be an aggregate class. The ordered identifiers in the designators of the designated-initializer-list shall form a subsequence of the ordered identifiers in the direct non-static data members of T. Aggregate initialization is performed ([dcl.init.aggr]). [Example 2: struct A { int x; int y; int z; }; A a{.y = 2, .x = 1}; // error: designator order does not match declaration order A b{.x = 1, .z = 2}; // OK, b.y initialized to 0 — end example]

  • (3.2)

    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).

  • (3.3)

    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.

  • (3.4)

    Otherwise, if T is an aggregate, aggregate initialization is performed ([dcl.init.aggr]). [Example 3: double ad[] = { 1, 2.0 }; // OKint ai[] = { 1, 2.0 }; // error: narrowingstruct S2 {int m1; double m2, m3;}; S2 s21 = { 1, 2, 3.0 }; // OK S2 s22 { 1.0, 2, 3 }; // error: narrowing S2 s23 { }; // OK, default to 0,0,0 — end example]

  • (3.5)

    Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.

  • (3.6)

    Otherwise, if T is a specialization of std::initializer_list, the object is constructed as described below.

  • (3.7)

    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); // #1 S(std::initializer_list); // #2 S(std::initializer_list); // #3 S(); // #4// ...}; S s1 = { 1.0, 2.0, 3.0 }; // invoke #1 S s2 = { 1, 2, 3 }; // invoke #2 S s3{s2}; // invoke #3 (not the copy constructor) S s4 = { }; // invoke #4 — 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 {// no initializer-list constructors S(int, double, double); // #1 S(); // #2// ...}; S s1 = { 1, 2, 3.0 }; // OK, invoke #1 S s2 { 1.0, 2, 3 }; // error: narrowing S s3 { }; // OK, invoke #2 — end example]

  • (3.8)

    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 v to U, the program is ill-formed. [Example 7: enum byte : unsigned char { }; byte b { 42 }; // OK byte c = { 42 }; // error byte d = byte{ 42 }; // OK; same value as b byte e { -1 }; // errorstruct A { byte b; }; A a1 = { { 42 } }; // error A a2 = { byte{ 42 } }; // OKvoid f(byte); f({ 42 }); // errorenum class Handle : uint32_t { Invalid = 0 }; Handle h { 42 }; // OK — end example]

  • (3.9)

    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}; // OKint x2 {2.0}; // error: narrowing — end example]

  • (3.10)

    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); // #1 S(const std::string&); // #2// ...};const S& r1 = { 1, 2, 3.0 }; // OK, invoke #1const S& r2 { "Spinach" }; // OK, invoke #2 S& r3 = { 1, 2, 3 }; // error: initializer is not an lvalueconst int& i1 = { 1 }; // OKconst int& i2 = { 1.1 }; // error: narrowingconst int (&iar)[2] = { 1, 2 }; // OK, iar is bound to temporary arraystruct A { } a;struct B { explicit B(const A&); };const B& b2{a}; // error: cannot copy-list-initialize B temporary from Astruct C { int x; }; C&& c = { .x = 1 }; // OK — end example]

  • (3.11)

    Otherwise, if the initializer list has no elements, the object is value-initialized. [Example 10: int** pp {}; // initialized to null pointer — end example]

  • (3.12)

    Otherwise, the program is ill-formed. [Example 11: struct A { int i; int j; }; A a1 { 1, 2 }; // aggregate initialization A a2 { 1.2 }; // error: narrowingstruct B { B(std::initializer_list);}; B b1 { 1, 2 }; // creates initializer_list and calls constructor B b2 { 1, 2.0 }; // error: narrowingstruct C { C(int i, double j);}; C c1 = { 1, 2.2 }; // calls constructor with arguments (1, 2.2) C c2 = { 1.1, 2 }; // error: narrowingint j { 1 }; // initialize to 1int k { }; // initialize to 0 — end example]

4

#

Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions ([temp.variadic]), are evaluated in the order in which they appear.

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-clause that 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]

5

#

An object of type std::initializer_list 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 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]

6

#

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 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);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}}; // backing array f(std::initializer_list(__a, __a+3));}void h() {static constexpr double __b[3] = {double{1}, double{2}, double{3}}; // backing array f(std::initializer_list(__b, __b+3));}void r() {const A __c[3] = {A{1}, A{2}, A{3}}; // backing array q(std::initializer_list(__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 cmplx; std::vector v1 = { 1, 2, 3 };

void f() { std::vector v2{ 1, 2, 3 }; std::initializer_list i3 = { 1, 2, 3 };}struct A { std::initializer_list i4; A() : i4{ 1, 2, 3 } {} // ill-formed, would create a dangling reference};

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]

7

#

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; // x is not a constant expressionconst int y = 999;const int z = 99;char c1 = x; // OK, though it potentially narrows (in this case, it does narrow)char c2{x}; // error: potentially narrowschar c3{y}; // error: narrows (assuming char is 8 bits)char c4{z}; // OK, no narrowing neededunsigned char uc1 = {5}; // OK, no narrowing neededunsigned char uc2 = {-1}; // error: narrowsunsigned int ui1 = {-1}; // error: narrowssigned int si1 ={ (unsigned int)-1 }; // error: narrowsint ii = {2.0}; // error: narrowsfloat f1 { x }; // error: potentially narrowsfloat f2 { 7 }; // OK, 7 can be exactly represented as a floatbool b = {"meow"}; // error: narrowsint f(int);int a[] = { 2, f(2), f(2.0) }; // OK, the double-to-int conversion is not at the top level — end example]