Clarifying rules for brace elision in aggregate
initialization (original) (raw)
Introduction
[CWG2149] points out an inconsistency in the wording with respect to array lengths inferred from braced initializer lists in the presence of brace elision. The wording has changed since the issue was raised, but the essence of the issue remains. The standard first states that the length of an array of unknown bound is the same as the number of elements in the initializer list, but then describes the rules for brace elision, in which some elements are used to initialize subobjects of aggregate members, such that there is not a one-to-one mapping of initializer list elements to array elements. Similarly, aggregate initialization from a braced initializer list is supposed to explicitly initialize a number of aggregate elements equal to the length of the list, which is not possible in the presence of brace elision.
This paper aims to resolve the core issue by clarifying the rules for brace elision to match user expectations and the behavior of existing compilers. The intent is to better describe the existing design without introducing any evolutionary changes.
Wording
All changes are presented relative to [N4971].
§9.4.2[dcl.init.aggr]:
Modify the second list item of paragraph 3:
- (3.2) If the initializer list is a brace-enclosed initializer-list, the explicitly initialized elements of the aggregate are
the firstthose for which an element of the initializer list appertains to the aggregate element or to a subobject thereof (see below).nelements of the aggregate, wherenis the number of elements in the initializer list
Modify paragraph 4:
4For each explicitly initialized element:
- (4.1) […]
- (4.2)Otherwise, if the initializer list is a brace-enclosed_designated-initializer-list_, the element is
copy-initialized from the corresponding initializer-clause or isinitialized with the brace-or-equal-initializer of the corresponding_designated-initializer-clause_. If that initializer is of the form_assignment-expression_or=assignment-expression_and a narrowing conversion (9.4.5[dcl.init.list]) is required to convert the expression, the program is ill-formed.
[ Note: ~~If the initialization is by_designated-initializer-clause, its~~ The form of the initializerdetermines whether copy-initialization or direct-initialization is performed. — end note ]- (4.3) Otherwise, the initializer list is a brace-enclosed initializer-list. If an_initializer-clause_ appertains to the aggregate element, then the aggregate element is copy-initialized from the_initializer-clause_. Otherwise, the aggregate element is copy-initialized from a brace-enclosed _initializer-list_consisting of all of the _initializer-clause_s that appertain to subobjects of the aggregate element, in the order of appearance.
[ Note: If an initializer is itself an initializer list, […] — end note ]
[ Example: […] — end example ]
Modify paragraph 6:
6
[ Example:
struct S { int a; const char* b; int c; int d = b[a]; }; S ss = { 1, "asdf" };initializes
ss.awith 1,ss.bwith"asdf",ss.cwith the value of an expression of the formint{}(that is,0), andss.dwith the value ofss.b[ss.a](that is,'s'), and instruct X { int i, j, k = 42; }; X a[] = { 1, 2, 3, 4, 5, 6 }; X b[2] = { { 1, 2, 3 }, { 4, 5, 6 } };
aandbhave the same valuestruct A { string a; int b = 42; int c = -1; };
A{.c=21}has the following steps:
- (6.1)Initialize
awith{}- (6.2)Initialize
bwith= 42- (6.3)Initialize
cwith= 21— end example ]
Modify paragraph 10:
10
AnThe number of elements (9.3.4.5[dcl.array]) in an array of unknown bound initialized with a brace-enclosed_initializer-list_containingisn_initializer-clause_sdefined as havingthe number of explicitly initialized elements of the array.nelements (9.3.4.5[dcl.array])[ Example:
declares and initializes
xas a one-dimensional array that has three elements since no size was specified and there are three initializers. — _end example_][ Example: In
struct X { int i, j, k; }; X a[] = { 1, 2, 3, 4, 5, 6 }; X b[2] = { { 1, 2, 3 }, { 4, 5, 6 } };
aandbhave the same value. —end example ]An array of unknown bound shall not be initialized with an empty_braced-init-list_
{}.[ Footnote: The syntax provides for empty _braced-init-list_s, but nonetheless C++ does not have zero length arrays. — _end footnote_][ Note: A default member initializer does not determine the bound for a member array of unknown bound. Since the default member initializer is ignored if a suitable_mem-initializer_ is present (11.9.3[class.base.init]), the default member initializer is not considered to initialize the array of unknown bound.
[ Example:
struct S { int y[] = { 0 }; // error: non-static data member of incomplete type };— end example ]
— end note ]
Remove paragraph 12:
12 An_initializer-list_ is ill-formed if the number of_initializer-clause_s exceeds the number of elements of the aggregate.
[ Example:
char cv[4] = { 'a', 's', 'd', 'f', 0 }; // erroris ill-formed. — end example ]
Remove paragraph 14:
14 If an aggregate class
Ccontains a subaggregate elementewith no elements, the initializer-clause foreshall not be omitted from an_initializer-list_ for an object of typeCunless the_initializer-clause_s for all elements ofCfollowingeare also omitted.[ Example: […] — end example ]
The example in the above paragraph is relocated to another paragraph below.
Remove paragraph 16:
16 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-clause_s initializes the elements of a subaggregate; it is erroneous for there to be more_initializer-clause_s than elements. If, however, the_initializer-list for a subaggregate does not begin with a left brace, then only enough _initializer-clause_s from the list are taken to initialize the elements of the subaggregate; any remaining_initializer-clause_s are left to initialize the next element of the aggregate of which the current subaggregate is an element.
[ Example: […] — end example ]
The example in the above paragraph is relocated to another paragraph below.
Insert a new paragraph in place of removed paragraph 16:
14 Each_initializer-clause_ in a brace-enclosed_initializer-list_ is said to appertain to an element of the aggregate being initialized or to an element of one of its subaggregates. Considering the sequence of _initializer-clause_s, and the sequence of aggregate elements initially formed as the sequence of elements of the aggregate being initialized and potentially modified as described below, each initializer-clause appertains to the corresponding aggregate element if
- (14.1) the aggregate element is not an aggregate, or
- (14.2) the_initializer-clause_ begins with a left brace, or
- (14.3) the_initializer-clause_ is an expression and an implicit conversion sequence can be formed that converts the expression to the type of the aggregate element, or
- (14.4) the aggregate element is an aggregate that itself has no aggregate elements.
Otherwise, the aggregate element is an aggregate and that subaggregate is replaced in the list of aggregate elements by the sequence of its own aggregate elements, and the appertainment analysis resumes with the first such element and the same_initializer-clause_.
[ Note: These rules apply recursively to the aggregate’s subaggregates.
[ Example: In
struct S1 { int a, b; }; struct S2 { S1 s, t; }; S2 x[2] = { 1, 2, 3, 4, 5, 6, 7, 8 }; S2 y[2] = { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } };
xandyhave the same value. —end example ]— end note ]
This process continues until all _initializer-clause_s have been exhausted.
If any initializer-clause remains that does not appertain to an element of the aggregate or one of its subaggregates, the program is ill-formed.
[ Example:
char cv[4] = { 'a', 's', 'd', 'f', 0 }; // error: too many initializers— end example ]
Remove paragraph 17:
15 All implicit type conversions (7.3[conv]) are considered when initializing the element with an assignment-expression. If the_assignment-expression_ can initialize an element, the element is initialized. Otherwise, if the element is itself a subaggregate, brace elision is assumed and the assignment-expression is considered for the initialization of the first element of the subaggregate.
[ Note: As specified above, brace elision cannot apply to subaggregates with no elements; 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.iis initialized with 4,b.a2is initialized witha,b.zis initialized with whatevera.operator int()returns.— end example ]
Insert the remaining new paragraphs (containing the examples relocated from prior paragraphs) after the above paragraph:
16
[ 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], namelyy[0][0],y[0][1], andy[0][2]. Likewise the next two lines initializey[1]andy[2]. The initializer ends early and thereforey[3]’s elements are initialized as if explicitly initialized with an expression of the formfloat(), that is, are initialized with0.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
ybegins with a left brace, but the one fory[0]does not, therefore three elements from the list are used. Likewise the next three are taken successively fory[1]andy[2]. — end example ]
17
[ Note: The initializer for an empty subaggregate is required if any initializers are provided for subsequent elements.
[ 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 ]
— end note ]
18
[ 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.iis initialized with 4,b.a2is initialized witha,b.zis initialized with whatevera.operator int()returns.— end example ]
References
[CWG2149] Vinny Romano. 2015-06-25. Brace elision and array length deduction.
[N4971] Thomas Köppe. 2023-12-18. Working Draft, Programming Languages — C++.