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:

Modify paragraph 4:

4For each explicitly initialized element:

Modify paragraph 6:

6

[ 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), andss.d with the value ofss.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 andb have the same value

struct A {
 string a;
 int b = 42;
 int c = -1;
};

A{.c=21} has the following steps:

end example ]

Modify paragraph 10:

10 An The number of elements (9.3.4.5[dcl.array]) in an array of unknown bound initialized with a brace-enclosed_initializer-list_ containingn _initializer-clause_s is defined as havingn elements (9.3.4.5[dcl.array]) the number of explicitly initialized elements of the array.

[ Example:

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; };
X a[] = { 1, 2, 3, 4, 5, 6 };
X b[2] = { { 1, 2, 3 }, { 4, 5, 6 } };

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-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 };     // error

is ill-formed. — end example ]

Remove paragraph 14:

14 If an aggregate class C contains a subaggregate element e with no elements, the initializer-clause fore shall not be omitted from an_initializer-list_ for an object of typeC unless the_initializer-clause_s 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-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

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 }
 }
};

x andy have 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 forb.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:

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], namely y[0][0],y[0][1], andy[0][2]. Likewise the next two lines initialize y[1] andy[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 fory[0] does not, therefore three elements from the list are used. Likewise the next three are taken successively for y[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 forb.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 ]

References

[CWG2149] Vinny Romano. 2015-06-25. Brace elision and array length deduction.

https://wg21.link/cwg2149

[N4971] Thomas Köppe. 2023-12-18. Working Draft, Programming Languages — C++.

https://wg21.link/n4971