Document #: | P3106R0 |
Date: | 2024-02-03 |
Project: | Programming Language C++ |
Audience: |
CWG |
Reply-to: |
James Touton <bekenn@gmail.com> |
[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.
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).n
elements of the aggregate, wheren
is the number of elements in the initializer list
Modify the second list item of paragraph 4:
(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 formassignment-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, itsThe form of the initializer determines whether copy-initialization or direct-initialization is performed. — end note ][ Note: If an initializer is itself an initializer list, […] — end note ]
[ Example: […] — end example ]
Add a new list item to paragraph 4:
(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-clauses that appertain to subobjects of the aggregate member, in the order that they appear.
[ Note: If an initializer is itself an initializer list, […] — end note ]
[ Example: […] — end example ]
The note and example that are struck from 4.2 should appear verbatim in 4.3.
Modify paragraph 6:
[ Example:struct S { int a; const char* b; int c; int d = b[a]; }; = { 1, "asdf" }; S ss
initializes
ss.a
with 1,ss.b
with"asdf"
,ss.c
with the value of an expression of the formint{}
(that is,0
), andss.d
with the value ofss.b[ss.a]
(that is,'s'
), and instruct X { int i, j, k = 42; }; [] = { 1, 2, 3, 4, 5, 6 }; X a[2] = { { 1, 2, 3 }, { 4, 5, 6 } }; X b
a
andb
have the same valuestruct A { string a;int b = 42; int c = -1; };
— end example ]
A{.c=21}
has the following steps:
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-listcontainingis defined asn
initializer-clauseshavingequal to the number of explicitly initialized elements of the array.n
elements (9.3.4.5 [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 ][ Example: In
struct X { int i, j, k; }; [] = { 1, 2, 3, 4, 5, 6 }; X a[2] = { { 1, 2, 3 }, { 4, 5, 6 } }; X b
a
andb
have 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-lists, 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:— end note ]— end example ]struct S { int y[] = { 0 }; // error: non-static data member of incomplete type };
Remove paragraph 12:
12 An initializer-list is ill-formed if the number of initializer-clauses exceeds the number of elements of the aggregate.
[ Example:char cv[4] = { 'a', 's', 'd', 'f', 0 }; // error
is ill-formed. — end example ]
Remove paragraph 14:
14 If an aggregate class
C
contains a subaggregate elemente
with no elements, the initializer-clause fore
shall not be omitted from an initializer-list for an object of typeC
unless the initializer-clauses for all elements ofC
followinge
are 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-clauses initializes the elements of a subaggregate; it is erroneous for there to be more initializer-clauses than elements. 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 elements of the subaggregate; any remaining initializer-clauses 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 or subobject thereof of the aggregate being initialized. Beginning with the first aggregate member and the first initializer-clause, each initializer-clause appertains to the corresponding aggregate member if
- (14.1) the aggregate member is not an aggregate, or
- (14.2) the initializer-clause begins with a left brace, or
- (14.3) the initializer-clause is an expression whose type is convertible to the cv-unqualified type of the aggregate member, or
- (14.4) the aggregate member is an aggregate that itself has no aggregate members.
Otherwise, the members of the subaggregate are considered in place of the subaggregate.
[ Note: These rules are applied recursively to the aggregate’s subaggregates, and to subaggregates of the subaggregates, and so on.
— end note ][ Example: In
struct S1 { int a, b; }; struct S2 { S1 s, t; }; [2] = { 1, 2, 3, 4, 5, 6, 7, 8 }; S2 x[2] = { S2 y{ { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } };
x
andy
have the same value. — end example ]This process continues until all initializer-clauses have been exhausted.
If any initializer-clauses remain after all members of the top-level aggregate have been exhausted, the program is ill-formed.
[ Example:char cv[4] = { 'a', 's', 'd', 'f', 0 }; // error
is ill-formed. — end example ]
Modify paragraph 17 (now 15):
15 All implicit type conversions (7.3 [conv]) are considered when initializing
thean 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;= { 4, a, a }; B b
Braces are elided around the initializer-clause for
b.a1.i
.b.a1.i
is initialized with 4,b.a2
is initialized witha
,b.z
is initialized with whatevera.operator int()
returns. — end example ]
Insert the remaining new paragraphs (containing the examples relocated from prior paragraphs) after the above paragraph:
[ 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
y
begins 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 ]
[ Note: The initializer for an empty subaggregate is required if any initializers are provided for subsequent elements.
[ Example:— end note ]— end example ]struct S { } s; struct A { S s1;int i1; S s2;int i2; S s3;int i3; } a = { { }, // Required initialization 0, // Required initialization s, 0 }; // Initialization not required for
A::s3
becauseA::i3
is also not initialized
[ Example:struct A { int i; operator int(); }; struct B { A a1, a2;int z; }; A a;= { 4, a, a }; B b
Braces are elided around the initializer-clause for
b.a1.i
.b.a1.i
is initialized with 4,b.a2
is initialized witha
,b.z
is initialized with whatevera.operator int()
returns. — end example ]