Files
2025-10-25 03:02:53 +03:00

369 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[dcl.init.list]
# 9 Declarations [[dcl]](./#dcl)
## 9.5 Initializers [[dcl.init]](dcl.init#list)
### 9.5.5 List-initialization [dcl.init.list]
[1](#1)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/declarations.tex#L6366)
[*List-initialization*](#def:list-initialization "9.5.5List-initialization[dcl.init.list]") is initialization of an object or reference from a[*braced-init-list*](dcl.init.general#nt:braced-init-list "9.5.1General[dcl.init.general]")[.](#1.sentence-1)
Such an initializer is called an [*initializer list*](#def:initializer_list), and
the comma-separated[*initializer-clause*](dcl.init.general#nt:initializer-clause "9.5.1General[dcl.init.general]")*s* of the [*initializer-list*](dcl.init.general#nt:initializer-list "9.5.1General[dcl.init.general]") or[*designated-initializer-clause*](dcl.init.general#nt:designated-initializer-clause "9.5.1General[dcl.init.general]")*s* of the [*designated-initializer-list*](dcl.init.general#nt:designated-initializer-list "9.5.1General[dcl.init.general]") are called the [*elements*](#def:elements) of the initializer list[.](#1.sentence-2)
An initializer list may be empty[.](#1.sentence-3)
List-initialization can occur in direct-initialization or copy-initialization contexts;
list-initialization in a direct-initialization context is called[*direct-list-initialization*](#def:direct-list-initialization "9.5.5List-initialization[dcl.init.list]") and list-initialization in a
copy-initialization context is called [*copy-list-initialization*](#def:copy-list-initialization "9.5.5List-initialization[dcl.init.list]")[.](#1.sentence-4)
Direct-initialization that is not list-initialization is called[*direct-non-list-initialization*](#def:direct-non-list-initialization "9.5.5List-initialization[dcl.init.list]")[.](#1.sentence-5)
[*Note [1](#note-1)*:
List-initialization can be used
- [(1.1)](#1.1)
as the initializer in a variable definition ([[dcl.init]](dcl.init "9.5Initializers")),
- [(1.2)](#1.2)
as the initializer in a [*new-expression*](expr.new#nt:new-expression "7.6.2.8New[expr.new]") ([[expr.new]](expr.new "7.6.2.8New")),
- [(1.3)](#1.3)
in a return statement ([[stmt.return]](stmt.return "8.8.4The return statement")),
- [(1.4)](#1.4)
as a [*for-range-initializer*](stmt.pre#nt:for-range-initializer "8.1Preamble[stmt.pre]") ([[stmt.iter]](stmt.iter "8.6Iteration statements")),
- [(1.5)](#1.5)
as a function argument ([[expr.call]](expr.call "7.6.1.3Function call")),
- [(1.6)](#1.6)
as a template argument ([[temp.arg.nontype]](temp.arg.nontype "13.4.3Constant template arguments")),
- [(1.7)](#1.7)
as a subscript ([[expr.sub]](expr.sub "7.6.1.2Subscripting")),
- [(1.8)](#1.8)
as an argument to a constructor invocation ([[dcl.init]](dcl.init "9.5Initializers"), [[expr.type.conv]](expr.type.conv "7.6.1.4Explicit type conversion (functional notation)")),
- [(1.9)](#1.9)
as an initializer for a non-static data member ([[class.mem]](class.mem "11.4Class members")),
- [(1.10)](#1.10)
in a [*mem-initializer*](class.base.init#nt:mem-initializer "11.9.3Initializing bases and members[class.base.init]") ([[class.base.init]](class.base.init "11.9.3Initializing bases and members")), or
- [(1.11)](#1.11)
on the right-hand side of an assignment ([[expr.assign]](expr.assign "7.6.19Assignment and compound assignment operators"))[.](#1.sentence-6)
[*Example [1](#example-1)*: 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 elementsreturn { "Norah" }; // return list of one elementint* 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*]
[2](#2)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/declarations.tex#L6413)
A constructor is an [*initializer-list constructor*](#def:initializer-list_constructor "9.5.5List-initialization[dcl.init.list]") 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]](dcl.fct.default "9.3.4.7Default arguments"))[.](#2.sentence-1)
[*Note [2](#note-2)*:
Initializer-list constructors are favored over other constructors in
list-initialization ([[over.match.list]](over.match.list "12.2.2.8Initialization by list-initialization"))[.](#2.sentence-2)
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]](temp.deduct.call "13.10.3.2Deducing template arguments from a function call"))[.](#2.sentence-3)
— *end note*]
The templatestd::initializer_list is not predefined;
if a standard library declaration ([[initializer.list.syn]](initializer.list.syn "17.11.2Header <initializer_­list> synopsis"), [[std.modules]](std.modules "16.4.2.4Modules"))
of std::initializer_list is not reachable from ([[module.reach]](module.reach "10.7Reachability"))
a use of std::initializer_list —
even an implicit use in which the type is not named ([[dcl.spec.auto]](dcl.spec.auto "9.2.9.7Placeholder type specifiers")) —
the program is ill-formed[.](#2.sentence-4)
[3](#3)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/declarations.tex#L6433)
List-initialization of an object or reference of type *cv* T is defined as follows:
- [(3.1)](#3.1)
If the [*braced-init-list*](dcl.init.general#nt:braced-init-list "9.5.1General[dcl.init.general]") contains a [*designated-initializer-list*](dcl.init.general#nt:designated-initializer-list "9.5.1General[dcl.init.general]") andT is not a reference type,T shall be an aggregate class[.](#3.1.sentence-1)
The ordered [*identifier*](lex.name#nt:identifier "5.11Identifiers[lex.name]")*s* in the [*designator*](dcl.init.general#nt:designator "9.5.1General[dcl.init.general]")*s* of the [*designated-initializer-list*](dcl.init.general#nt:designated-initializer-list "9.5.1General[dcl.init.general]") shall form a subsequence
of the ordered [*identifier*](lex.name#nt:identifier "5.11Identifiers[lex.name]")*s* in the direct non-static data members of T[.](#3.1.sentence-2)
Aggregate initialization is performed ([[dcl.init.aggr]](dcl.init.aggr "9.5.2Aggregates"))[.](#3.1.sentence-3)
[*Example [2](#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)](#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.2.sentence-1)
- [(3.3)](#3.3)
Otherwise, if T is a character array and the initializer list has a
single element that is an appropriately-typed [*string-literal*](lex.string#nt:string-literal "5.13.5String literals[lex.string]") ([[dcl.init.string]](dcl.init.string "9.5.3Character arrays")),
initialization is performed as described in that subclause[.](#3.3.sentence-1)
- [(3.4)](#3.4)
Otherwise, if T is an aggregate, aggregate initialization is
performed ([[dcl.init.aggr]](dcl.init.aggr "9.5.2Aggregates"))[.](#3.4.sentence-1)
[*Example [3](#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)](#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.5.sentence-1)
- [(3.6)](#3.6)
Otherwise, if T is a specialization of std::initializer_list,
the object is constructed as described below[.](#3.6.sentence-1)
- [(3.7)](#3.7)
Otherwise, if T is a class type, constructors are considered[.](#3.7.sentence-1)
The applicable constructors are enumerated and
the best one is chosen through overload resolution ([[over.match]](over.match "12.2Overload resolution"), [[over.match.list]](over.match.list "12.2.2.8Initialization by list-initialization"))[.](#3.7.sentence-2)
If a narrowing
conversion (see below) is required to convert any of the arguments, the program is
ill-formed[.](#3.7.sentence-3)
[*Example [4](#example-4)*: struct S { S(std::initializer_list<double>); // #1 S(std::initializer_list<int>); // #2 S(std::initializer_list<S>); // #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](#example-5)*: struct Map { Map(std::initializer_list<std::pair<std::string,int>>);};
Map ship = {{"Sophie",14}, {"Surprise",28}}; — *end example*]
[*Example [6](#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)](#3.8)
Otherwise, if T is an enumeration
with a fixed underlying type ([[dcl.enum]](dcl.enum "9.8.1Enumeration declarations")) U,
the [*initializer-list*](dcl.init.general#nt:initializer-list "9.5.1General[dcl.init.general]") 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]](expr.type.conv "7.6.1.4Explicit type conversion (functional notation)"));
if a narrowing conversion is required to convert v to U, the program is ill-formed[.](#3.8.sentence-1)
[*Example [7](#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)](#3.9)
Otherwise, if
the initializer list
is not a [*designated-initializer-list*](dcl.init.general#nt:designated-initializer-list "9.5.1General[dcl.init.general]") 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[.](#3.9.sentence-1)
[*Example [8](#example-8)*: int x1 {2}; // OKint x2 {2.0}; // error: narrowing — *end example*]
- [(3.10)](#3.10)
Otherwise, if T is a reference type, a prvalue is generated[.](#3.10.sentence-1)
The prvalue initializes its result object by copy-list-initialization from the initializer list[.](#3.10.sentence-2)
The prvalue is then used to direct-initialize the reference[.](#3.10.sentence-3)
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[.](#3.10.sentence-4)
[*Note [3](#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[.](#3.10.sentence-5)
— *end note*]
[*Example [9](#example-9)*: struct S { S(std::initializer_list<double>); // #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)](#3.11)
Otherwise, if the initializer list has no elements, the object is
value-initialized[.](#3.11.sentence-1)
[*Example [10](#example-10)*: int** pp {}; // initialized to null pointer — *end example*]
- [(3.12)](#3.12)
Otherwise, the program is ill-formed[.](#3.12.sentence-1)
[*Example [11](#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<int>);};
B b1 { 1, 2 }; // creates initializer_list<int> 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](#4)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/declarations.tex#L6651)
Within the [*initializer-list*](dcl.init.general#nt:initializer-list "9.5.1General[dcl.init.general]") of a [*braced-init-list*](dcl.init.general#nt:braced-init-list "9.5.1General[dcl.init.general]"),
the [*initializer-clause*](dcl.init.general#nt:initializer-clause "9.5.1General[dcl.init.general]")*s*, including any that result from pack
expansions ([[temp.variadic]](temp.variadic "13.7.4Variadic templates")), are evaluated in the order in which they
appear[.](#4.sentence-1)
That is, every value computation and side effect associated with a
given [*initializer-clause*](dcl.init.general#nt:initializer-clause "9.5.1General[dcl.init.general]") is sequenced before every value
computation and side effect associated with any [*initializer-clause*](dcl.init.general#nt:initializer-clause "9.5.1General[dcl.init.general]") that follows it in the comma-separated list of the [*initializer-list*](dcl.init.general#nt:initializer-list "9.5.1General[dcl.init.general]")[.](#4.sentence-2)
[*Note [4](#note-4)*:
This evaluation ordering holds regardless of the semantics of the
initialization; for example, it applies when the elements of the[*initializer-list*](dcl.init.general#nt:initializer-list "9.5.1General[dcl.init.general]") are interpreted as arguments of a constructor
call, even though ordinarily there are no sequencing constraints on the
arguments of a call[.](#4.sentence-3)
— *end note*]
[5](#5)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/declarations.tex#L6667)
An object of type std::initializer_list<E> is constructed from
an initializer list as if
the implementation generated and materialized ([[conv.rval]](conv.rval "7.3.5Temporary materialization conversion"))
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*](#def:array,backing "9.5.5List-initialization[dcl.init.list]")[.](#5.sentence-1)
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[.](#5.sentence-2)
[*Note [5](#note-5)*:
A constructor or conversion function selected for the copy needs to be
accessible ([[class.access]](class.access "11.8Member access control")) in the context of the initializer list[.](#5.sentence-3)
— *end note*]
If a narrowing conversion is required to initialize any of the elements,
the program is ill-formed[.](#5.sentence-4)
[*Note [6](#note-6)*:
Backing arrays are potentially non-unique objects ([[intro.object]](intro.object "6.8.2Object model"))[.](#5.sentence-5)
— *end note*]
[6](#6)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/declarations.tex#L6687)
The backing array has the same lifetime as any other temporary
object ([[class.temporary]](class.temporary "6.8.7Temporary objects")), except that initializing aninitializer_list object from the array extends the lifetime of
the array exactly like binding a reference to a temporary[.](#6.sentence-1)
[*Example [12](#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}}; // 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));} 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[.](#6.sentence-2)
— *end example*]
[*Example [13](#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 } {} // 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[.](#6.sentence-3)
For i3, the initializer_list object is a variable,
so the array persists for the lifetime of the variable[.](#6.sentence-4)
For i4, the initializer_list object is initialized in
the constructor's [*ctor-initializer*](class.base.init#nt:ctor-initializer "11.9.3Initializing bases and members[class.base.init]") as if by binding
a temporary array to a reference member, so the program is
ill-formed ([[class.base.init]](class.base.init "11.9.3Initializing bases and members"))[.](#6.sentence-5)
— *end example*]
[7](#7)
[#](http://github.com/Eelis/draft/tree/9adde4bc1c62ec234483e63ea3b70a59724c745a/source/declarations.tex#L6758)
A [*narrowing conversion*](#def:conversion,narrowing "9.5.5List-initialization[dcl.init.list]") is an implicit conversion
- [(7.1)](#7.1)
from a floating-point type to an integer type, or
- [(7.2)](#7.2)
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
- [(7.3)](#7.3)
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
- [(7.4)](#7.4)
from an integer type or unscoped enumeration type to an integer type that cannot
represent all the values of the original type, except where
* [(7.4.1)](#7.4.1)
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
* [(7.4.2)](#7.4.2)
the source is a constant
expression whose value after integral promotions will fit into the target type, or
- [(7.5)](#7.5)
from a pointer type or a pointer-to-member type to bool[.](#7.sentence-1)
[*Note [7](#note-7)*:
As indicated above, such conversions are not allowed at the top level in
list-initializations[.](#7.sentence-2)
— *end note*]
[*Example [14](#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*]