8 Declarators [dcl.decl]

8.5 Initializers [dcl.init]

A declarator can specify an initial value for the identifier being declared. The identifier designates a variable being initialized. The process of initialization described in the remainder of [dcl.init] applies also to initializations specified by other syntactic contexts, such as the initialization of function parameters with argument expressions ([expr.call]) or the initialization of return values ([stmt.return]).

initializer:
    brace-or-equal-initializer
    ( expression-list )
brace-or-equal-initializer:
    = initializer-clause
    braced-init-list
initializer-clause:
    assignment-expression
    braced-init-list
initializer-list:
    initializer-clause ...opt
    initializer-list , initializer-clause ...opt
braced-init-list:
    { initializer-list ,opt }
    { }

Except for objects declared with the constexpr specifier, for which see [dcl.constexpr], an initializer in the definition of a variable can consist of arbitrary expressions involving literals and previously declared variables and functions, regardless of the variable's storage duration. [ Example:

int f(int);
int a = 2;
int b = f(a);
int c(b);

 — end example ]

Note: Default arguments are more restricted; see [dcl.fct.default].

The order of initialization of variables with static storage duration is described in [basic.start] and [stmt.dcl].  — end note ]

A declaration of a block-scope variable with external or internal linkage that has an initializer is ill-formed.

To zero-initialize an object or reference of type T means:

  • if T is a scalar type ([basic.types]), the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;105

  • if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;

  • if T is a (possibly cv-qualified) union type, the object's first non-static named data member is zero-initialized and padding is initialized to zero bits;

  • if T is an array type, each element is zero-initialized;

  • if T is a reference type, no initialization is performed.

To default-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause [class]), the default constructor ([class.ctor]) for T is called (and the initialization is ill-formed if T has no default constructor or overload resolution ([over.match]) results in an ambiguity or in a function that is deleted or inaccessible from the context of the initialization);

  • if T is an array type, each element is default-initialized;

  • otherwise, no initialization is performed.

If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause [class]) with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;

  • if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;

  • if T is an array type, then each element is value-initialized;

  • otherwise, the object is zero-initialized.

An object that is value-initialized is deemed to be constructed and thus subject to provisions of this International Standard applying to “constructed” objects, objects “for which the constructor has completed,” etc., even if no constructor is invoked for the object's initialization.

A program that calls for default-initialization or value-initialization of an entity of reference type is ill-formed.

Note: Every object of static storage duration is zero-initialized at program startup before any other initialization takes place. In some cases, additional initialization is done later.  — end note ]

An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.

Note: Since () is not permitted by the syntax for initializer,

X a();

is not the declaration of an object of class X, but the declaration of a function taking no argument and returning an X. The form () is permitted in certain other initialization contexts ([expr.new], [expr.type.conv], [class.base.init]).  — end note ]

If no initializer is specified for an object, the object is default-initialized. When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced ([expr.ass]). [ Note: Objects with static or thread storage duration are zero-initialized, see [basic.start.init].  — end note ] If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases:

  • If an indeterminate value of unsigned narrow character type ([basic.fundamental]) is produced by the evaluation of:

    then the result of the operation is an indeterminate value.

  • If an indeterminate value of unsigned narrow character type is produced by the evaluation of the right operand of a simple assignment operator ([expr.ass]) whose first operand is an lvalue of unsigned narrow character type, an indeterminate value replaces the value of the object referred to by the left operand.

  • If an indeterminate value of unsigned narrow character type is produced by the evaluation of the initialization expression when initializing an object of unsigned narrow character type, that object is initialized to an indeterminate value.

Example:

  int f(bool b) {
    unsigned char c;
    unsigned char d = c; // OK, d has an indeterminate value
    int e = d;           // undefined behavior
    return b ? d : 0;    // undefined behavior if b is true
  }

 — end example ]

An initializer for a static member is in the scope of the member's class. [ Example:

int a;

struct X {
  static int a;
  static int b;
};

int X::a = 1;
int X::b = a;       // X::b = X::a

 — end example ]

The form of initialization (using parentheses or =) is generally insignificant, but does matter when the initializer or the entity being initialized has a class type; see below. If the entity being initialized does not have class type, the expression-list in a parenthesized initializer shall be a single expression.

The initialization that occurs in the form

T x = a;

as well as in argument passing, function return, throwing an exception ([except.throw]), handling an exception ([except.handle]), and aggregate member initialization ([dcl.init.aggr]) is called copy-initialization. [ Note: Copy-initialization may invoke a move ([class.copy]).  — end note ]

The initialization that occurs in the forms

T x(a);
T x{a};

as well as in new expressions ([expr.new]), static_cast expressions ([expr.static.cast]), functional notation type conversions ([expr.type.conv]), and base and member initializers ([class.base.init]) is called direct-initialization.

The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.

  • If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized ([dcl.init.list]).

  • If the destination type is a reference type, see [dcl.init.ref].

  • If the destination type is an array of characters, an array of char16_t, an array of char32_t, or an array of wchar_t, and the initializer is a string literal, see [dcl.init.string].

  • If the initializer is (), the object is value-initialized.

  • Otherwise, if the destination type is an array, the program is ill-formed.

  • If the destination type is a (possibly cv-qualified) class type:

    • If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.

    • Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in [over.match.copy], and the best one is chosen through overload resolution ([over.match]). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see [class.temporary], [class.copy].

  • Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated ([over.match.conv]), and the best one is chosen through overload resolution ([over.match]). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.

  • Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. Standard conversions (Clause [conv]) will be used, if necessary, to convert the initializer expression to the cv-unqualified version of the destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed. Note: An expression of type “ cv1 T” can initialize an object of type “ cv2 T” independently of the cv-qualifiers cv1 and cv2.

    int a;
    const int b = a;
    int c = b;
    

     — end note ]

An initializer-clause followed by an ellipsis is a pack expansion ([temp.variadic]).

As specified in [conv.ptr], converting an integer literal whose value is 0 to a pointer type results in a null pointer value.

8.5.1 Aggregates [dcl.init.aggr]

An aggregate is an array or a class (Clause [class]) with no user-provided constructors ([class.ctor]), no private or protected non-static data members (Clause [class.access]), no base classes (Clause [class.derived]), and no virtual functions ([class.virtual]).

When an aggregate is initialized by an initializer list, as specified in [dcl.init.list], the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause. If the initializer-clause is an expression and a narrowing conversion ([dcl.init.list]) is required to convert the expression, the program is ill-formed. [ Note: If an initializer-clause is itself an initializer list, the member is list-initialized, which will result in a recursive application of the rules in this section if the member is an aggregate.  — end note ] [ Example:

struct A {
  int x;
  struct B {
    int i;
    int j;
  } b;
} a = { 1, { 2, 3 } };

initializes a.x with 1, a.b.i with 2, a.b.j with 3.  — end example ]

An aggregate that is a class can also be initialized with a single expression not enclosed in braces, as described in [dcl.init].

An array of unknown size initialized with a brace-enclosed initializer-list containing n initializer-clauses, where n shall be greater than zero, is defined as having n elements ([dcl.array]). [ Example:

int x[] = { 1, 3, 5 };

declares and initializes x as a one-dimensional array that has three elements since no size was specified and there are three initializers.  — end example ] An empty initializer list {} shall not be used as the initializer-clause for an array of unknown bound.106

Static data members and anonymous bit-fields are not considered members of the class for purposes of aggregate initialization. [ Example:

struct A {
  int i;
  static int s;
  int j;
  int :17;
  int k;
} a = { 1, 2, 3 };

Here, the second initializer 2 initializes a.j and not the static data member A::s, and the third initializer 3 initializes a.k and not the anonymous bit-field before it.  — end example ]

An initializer-list is ill-formed if the number of initializer-clauses exceeds the number of members or elements to initialize. [ Example:

char cv[4] = { 'a', 's', 'd', 'f', 0 };     // error

is ill-formed.  — end example ]

If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from its brace-or-equal-initializer or, if there is no brace-or-equal-initializer, from an empty initializer list ([dcl.init.list]). [ Example:

struct S { int a; const char* b; int c; int d = b[a]; };
S ss = { 1, "asdf" };

initializes ss.a with 1, ss.b with "asdf", ss.c with the value of an expression of the form int{} (that is, 0), and ss.d with the value of ss.b[ss.a] (that is, 's'), and in

struct X { int i, j, k = 42; };
X a[] = { 1, 2, 3, 4, 5, 6 };
X b[2] = { { 1, 2, 3 }, { 4, 5, 6 } };

a and b have the same value  — end example ]

If an aggregate class C contains a subaggregate member m that has no members for purposes of aggregate initialization, the initializer-clause for m shall not be omitted from an initializer-list for an object of type C unless the initializer-clauses for all members of C following m are also omitted. [ Example:

struct S { } s;
struct A {
  S s1;
  int i1;
  S s2;
  int i2;
  S s3;
  int i3;
} a = {
  { },      // Required initialization
  0,
  s,        // Required initialization
  0
};          // Initialization not required for A::s3 because A::i3 is also not initialized

 — end example ]

If an incomplete or empty initializer-list leaves a member of reference type uninitialized, the program is ill-formed.

When initializing a multi-dimensional array, the initializer-clauses initialize the elements with the last (rightmost) index of the array varying the fastest ([dcl.array]). [ Example:

int x[2][2] = { 3, 1, 4, 2 };

initializes x[0][0] to 3, x[0][1] to 1, x[1][0] to 4, and x[1][1] to 2. On the other hand,

float y[4][3] = {
  { 1 }, { 2 }, { 3 }, { 4 }
};

initializes the first column of y (regarded as a two-dimensional array) and leaves the rest zero.  — end example ]

Braces can be elided in an initializer-list as follows. If the initializer-list begins with a left brace, then the succeeding comma-separated list of initializer-clauses initializes the members of a subaggregate; it is erroneous for there to be more initializer-clauses than members. If, however, the initializer-list for a subaggregate does not begin with a left brace, then only enough initializer-clauses from the list are taken to initialize the members of the subaggregate; any remaining initializer-clauses are left to initialize the next member of the aggregate of which the current subaggregate is a member. [ Example:

float y[4][3] = {
  { 1, 3, 5 },
  { 2, 4, 6 },
  { 3, 5, 7 },
};

is a completely-braced initialization: 1, 3, and 5 initialize the first row of the array y[0], namely y[0][0], y[0][1], and y[0][2]. Likewise the next two lines initialize y[1] and y[2]. The initializer ends early and therefore y[3]s elements are initialized as if explicitly initialized with an expression of the form float(), that is, are initialized with 0.0. In the following example, braces in the initializer-list are elided; however the initializer-list has the same effect as the completely-braced initializer-list of the above example,

float y[4][3] = {
  1, 3, 5, 2, 4, 6, 3, 5, 7
};

The initializer for y begins with a left brace, but the one for y[0] does not, therefore three elements from the list are used. Likewise the next three are taken successively for y[1] and y[2].  — end example ]

All implicit type conversions (Clause [conv]) are considered when initializing the aggregate member with an assignment-expression. If the assignment-expression can initialize a member, the member is initialized. Otherwise, if the member is itself a subaggregate, brace elision is assumed and the assignment-expression is considered for the initialization of the first member of the subaggregate. [ Note: As specified above, brace elision cannot apply to subaggregates with no members for purposes of aggregate initialization; an initializer-clause for the entire subobject is required. — end note ]

Example:

struct A {
  int i;
  operator int();
};
struct B {
  A a1, a2;
  int z;
};
A a;
B b = { 4, a, a };

Braces are elided around the initializer-clause for b.a1.i. b.a1.i is initialized with 4, b.a2 is initialized with a, b.z is initialized with whatever a.operator int() returns.  — end example ]

Note: An aggregate array or an aggregate class may contain members of a class type with a user-provided constructor ([class.ctor]). Initialization of these aggregate objects is described in [class.expl.init].  — end note ]

Note: Whether the initialization of aggregates with static storage duration is static or dynamic is specified in [basic.start.init] and [stmt.dcl].  — end note ]

When a union is initialized with a brace-enclosed initializer, the braces shall only contain an initializer-clause for the first non-static data member of the union. [ Example:

union u { int a; const char* b; };
u a = { 1 };
u b = a;
u c = 1;                        // error
u d = { 0, "asdf" };            // error
u e = { "asdf" };               // error

 — end example ]

Note: As described above, the braces around the initializer-clause for a union member can be omitted if the union is a member of another aggregate.  — end note ]

The syntax provides for empty initializer-lists, but nonetheless C++ does not have zero length arrays.

8.5.2 Character arrays [dcl.init.string]

An array of narrow character type ([basic.fundamental]), char16_t array, char32_t array, or wchar_t array can be initialized by a narrow string literal, char16_t string literal, char32_t string literal, or wide string literal, respectively, or by an appropriately-typed string literal enclosed in braces ([lex.string]). Successive characters of the value of the string literal initialize the elements of the array. [ Example:

char msg[] = "Syntax error on line %s\n";

shows a character array whose members are initialized with a string-literal. Note that because '\n' is a single character and because a trailing '\0' is appended, sizeof(msg) is 25.  — end example ]

There shall not be more initializers than there are array elements. [ Example:

char cv[4] = "asdf";            // error

is ill-formed since there is no space for the implied trailing '\0'.  — end example ]

If there are fewer initializers than there are array elements, each element not explicitly initialized shall be zero-initialized ([dcl.init]).

8.5.3 References [dcl.init.ref]

A variable declared to be a T& or T&&, that is, “reference to type T” ([dcl.ref]), shall be initialized by an object, or function, of type T or by an object that can be converted into a T. [ Example:

int g(int);
void f() {
  int i;
  int& r = i;                   // r refers to i
  r = 1;                        // the value of i becomes 1
  int* p = &r;                  // p points to i
  int& rr = r;                  // rr refers to what r refers to, that is, to i
  int (&rg)(int) = g;           // rg refers to the function g
  rg(i);                        // calls function g
  int a[3];
  int (&ra)[3] = a;             // ra refers to the array a
  ra[1] = i;                    // modifies a[1]
}

 — end example ]

A reference cannot be changed to refer to another object after initialization. Note that initialization of a reference is treated very differently from assignment to it. Argument passing ([expr.call]) and function value return ([stmt.return]) are initializations.

The initializer can be omitted for a reference only in a parameter declaration ([dcl.fct]), in the declaration of a function return type, in the declaration of a class member within its class definition ([class.mem]), and where the extern specifier is explicitly used. Example:

int& r1;                        // error: initializer missing
extern int& r2;                 // OK

 — end example ]

Given types “ cv1 T1” and “ cv2 T2,” “ cv1 T1” is reference-related to cv2 T2” if T1 is the same type as T2, or T1 is a base class of T2. “ cv1 T1” is reference-compatible with “ cv2 T2” if T1 is reference-related to T2 and cv1 is the same cv-qualification as, or greater cv-qualification than, cv2. In all cases where the reference-related or reference-compatible relationship of two types is used to establish the validity of a reference binding, and T1 is a base class of T2, a program that necessitates such a binding is ill-formed if T1 is an inaccessible (Clause [class.access]) or ambiguous ([class.member.lookup]) base class of T2.

A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

  • If the reference is an lvalue reference and the initializer expression

    • is an lvalue (but is not a bit-field), and “ cv1 T1” is reference-compatible with “ cv2 T2,” or

    • has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an lvalue of type “ cv3 T3,” where “ cv1 T1” is reference-compatible with “ cv3 T3107 (this conversion is selected by enumerating the applicable conversion functions ([over.match.ref]) and choosing the best one through overload resolution ([over.match])),

    then the reference is bound to the initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object). [ Note: The usual lvalue-to-rvalue ([conv.lval]), array-to-pointer ([conv.array]), and function-to-pointer ([conv.func]) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done.  — end note ]

    Example:

    double d = 2.0;
    double& rd = d;                 // rd refers to d
    const double& rcd = d;          // rcd refers to d
    
    struct A { };
    struct B : A { operator int&(); } b;
    A& ra = b;                      // ra refers to A subobject in b
    const A& rca = b;               // rca refers to A subobject in b
    int& ir = B();                  // ir refers to the result of B::operator int&
    

     — end example ]

  • Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference. [ Example:

    double& rd2 = 2.0;              // error: not an lvalue and reference not const
    int  i = 2;
    double& rd3 = i;                // error: type mismatch and reference not const
    

     — end example ]

    • If the initializer expression

      • is an xvalue (but not a bit-field), class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or

      • has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3” (see [over.match.ref]),

      then the reference is bound to the value of the initializer expression in the first case and to the result of the conversion in the second case (or, in either case, to an appropriate base class subobject). In the second case, if the reference is an rvalue reference and the second standard conversion sequence of the user-defined conversion sequence includes an lvalue-to-rvalue conversion, the program is ill-formed.

      Example:

      struct A { };
      struct B : A { } b;
      extern B f();
      const A& rca2 = f();                // bound to the A subobject of the B rvalue.
      A&& rra = f();                      // same as above
      struct X {
        operator B();
        operator int&();
      } x;
      const A& r = x;                     // bound to the A subobject of the result of the conversion
      int i2 = 42;
      int&& rri = static_cast<int&&>(i2); // bound directly to i2
      B&& rrb = x;                        // bound directly to the result of operator B
      int&& rri2 = X();                   // error: lvalue-to-rvalue conversion applied to the
                                          // result of operator int&
      

       — end example ]

    • Otherwise:

      • If T1 is a class type, user-defined conversions are considered using the rules for copy-initialization of an object of type “ cv1 T1” by user-defined conversion ([dcl.init], [over.match.copy]); the program is ill-formed if the corresponding non-reference copy-initialization would be ill-formed. The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference. The program is ill-formed if the direct-initialization does not result in a direct binding or if it involves a user-defined conversion.

      • If T1 is a non-class type, a temporary of type “ cv1 T1” is created and copy-initialized ([dcl.init]) from the initializer expression. The reference is then bound to the temporary.

      If T1 is reference-related to T2:

      • cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2; and

      • if the reference is an rvalue reference, the initializer expression shall not be an lvalue.

      Example:

      struct Banana { };
      struct Enigma { operator const Banana(); };
      void enigmatic() {
        typedef const Banana ConstBanana;
        Banana &&banana1 = ConstBanana(); // ill-formed
        Banana &&banana2 = Enigma();      // ill-formed
      }
      
      const double& rcd2 = 2;         // rcd2 refers to temporary with value 2.0
      double&& rrd = 2;               // rrd refers to temporary with value 2.0
      const volatile int cvi = 1;
      const int& r2 = cvi;            // error: type qualifiers dropped
      double d2 = 1.0;
      double&& rrd2 = d2;             // error: copying lvalue of related type
      int i3 = 2;
      double&& rrd3 = i3;             // rrd3 refers to temporary with value 2.0
      

       — end example ]

In all cases except the last (i.e., creating and initializing a temporary from the initializer expression), the reference is said to bind directly to the initializer expression.

Note: [class.temporary] describes the lifetime of temporaries bound to references.  — end note ]

This requires a conversion function ([class.conv.fct]) returning a reference type.

8.5.4 List-initialization [dcl.init.list]

List-initialization is initialization of an object or reference from a braced-init-list. Such an initializer is called an initializer list, and the comma-separated initializer-clauses of the 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 called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization. [ Note: List-initialization can be used

Example:

int a = {1};
std::complex<double> z{1,2};
new std::vector<std::string>{"once", "upon", "a", "time"};  // 4 string elements
f( {"Nicholas","Annemarie"} );  // pass list of two elements
return { "Norah" };             // return list of one element
int* e {};                      // initialization to zero / null pointer
x = double{1};                  // explicitly construct a double 
std::map<std::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 to possibly cv-qualified 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: 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 template std::initializer_list is not predefined; if the header <initializer_list> is not included prior to 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 T is defined as follows:

  • If T is an aggregate, aggregate initialization is performed ([dcl.init.aggr]).

    Example:

    double ad[] = { 1, 2.0 };           // OK
    int ai[] = { 1, 2.0 };              // error: narrowing
    
    struct 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 ]

  • 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<E>, a prvalue initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type ([dcl.init]).

  • 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:

    struct S {
      S(std::initializer_list<double>); // #1
      S(std::initializer_list<int>);    // #2
      S();                              // #3
      // ...
    };
    S s1 = { 1.0, 2.0, 3.0 };           // invoke #1
    S s2 = { 1, 2, 3 };                 // invoke #2
    S s3 = { };                         // invoke #3
    

     — end example ]

    Example:

    struct Map {
      Map(std::initializer_list<std::pair<std::string,int>>);
    };
    Map ship = {{"Sophie",14}, {"Surprise",28}};
    

     — end example ]

    Example:

    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 ]

  • Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.

    Example:

    int x1 {2};                         // OK
    int x2 {2.0};                       // error: narrowing
    

     — end example ]

  • Otherwise, if T is a reference type, a prvalue temporary of the type referenced by T is copy-list-initialized or direct-list-initialized, depending on the kind of initialization for the reference, and the reference is bound to that temporary. [ Note: 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:

    struct S {
      S(std::initializer_list<double>); // #1
      S(const std::string&);            // #2
      // ...
    };
    const S& r1 = { 1, 2, 3.0 };        // OK: invoke #1
    const S& r2 { "Spinach" };          // OK: invoke #2
    S& r3 = { 1, 2, 3 };                // error: initializer is not an lvalue
    const int& i1 = { 1 };              // OK
    const int& i2 = { 1.1 };            // error: narrowing
    const int (&iar)[2] = { 1, 2 };     // OK: iar is bound to temporary array
    

     — end example ]

  • Otherwise, if the initializer list has no elements, the object is value-initialized.

    Example:

    int** pp {};                        // initialized to null pointer
    

     — end example ]

  • Otherwise, the program is ill-formed.

    Example:

    struct A { int i; int j; };
    A a1 { 1, 2 };                      // aggregate initialization 
    A a2 { 1.2 };                       // error: narrowing
    struct B {
      B(std::initializer_list<int>);
    };
    B b1 { 1, 2 };                      // creates initializer_list<int> and calls constructor
    B b2 { 1, 2.0 };                    // error: narrowing
    struct C {
      C(int i, double j);
    };
    C c1 = { 1, 2.2 };                  // calls constructor with arguments (1, 2.2) 
    C c2 = { 1.1, 2 };                  // error: narrowing
    
    int j { 1 };                        // initialize to 1
    int k { };                          // initialize to 0
    

     — end example ]

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: This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-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 allocated a temporary array of N elements of type const E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. [ Note: A constructor or conversion function selected for the copy shall be accessible (Clause [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.[ Example:

struct X {
  X(std::initializer_list<double> v);
};
X x{ 1,2,3 };

The initialization will be implemented in a way roughly equivalent to this:

const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));

assuming that the implementation can construct an initializer_list object with a pair of pointers.  — end example ]

The array has the same lifetime as any other temporary object ([class.temporary]), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary. [ Example:

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 } {}  // creates an A with 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 a constructor's ctor-initializer, so the array persists only until the constructor exits, and so any use of the elements of i4 after the constructor exits produces undefined behavior.  — end example ] [ Note: The implementation is free to allocate the array in read-only memory if an explicit array with the same initializer could be so allocated.  — end note ]

A narrowing conversion is an implicit conversion

  • from a floating-point type to an integer type, or

  • from long double to double or float, or from double to float, except where the source is a constant expression and the actual value after conversion is within the range of values that can be represented (even if it cannot be represented exactly), 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 constant expression whose value after integral promotions will fit into the target type.

Note: As indicated above, such conversions are not allowed at the top level in list-initializations. — end note ] [ Example:

int x = 999;              // x is not a constant expression
const int y = 999;
const int z = 99;
char c1 = x;              // OK, though it might narrow (in this case, it does narrow)
char c2{x};               // error: might narrow
char c3{y};               // error: narrows (assuming char is 8 bits)
char c4{z};               // OK: no narrowing needed
unsigned char uc1 = {5};  // OK: no narrowing needed
unsigned char uc2 = {-1}; // error: narrows
unsigned int ui1 = {-1};  // error: narrows
signed int si1 =
  { (unsigned int)-1 };   // error: narrows
int ii = {2.0};           // error: narrows
float f1 { x };           // error: might narrow
float f2 { 7 };           // OK: 7 can be exactly represented as a float
int f(int);
int a[] =
  { 2, f(2), f(2.0) };    // OK: the double-to-int conversion is not at the top level

 — end example ]