Document number: Dnnnn
Project: Programming Language C++, Language Evolution Group
Date: 2014-01-20
Reply-to: Daryle Walker <darylew at gmail dot com>

Proposal to Add Designation to Initializers

I. Table of Contents

  1. Table of Contents
  2. Introduction
  3. Motivation and Scope
  4. Impact on the Standard
  5. Design Decisions
  6. Technical Specifications
  7. Acknowledgments
  8. History
  9. References

II. Introduction

As of the C++ 2014 drafts, initializer clauses in braced initialization lists must be in the declared subobject order (not counting static data members, unnamed bit fields, and skipped trailing subobjects). The 1999 edition of the ISO C Standard adds the capability to override the order of subobject initialization, including sub-subobjects. This proposal is to add this capability to C++.

III. Motivation and Scope

The motivation is to improve parity with C. The feature allows sparse initializations for array and class types; order-independent initializations for class types (since members' order during initialization is otherwise hidden); and selection of any variant member as the initial one for union types.

IV. Impact on the Standard

The proposal adds to the initialization rules (section 8.5 [dcl.init]). At least two implementations, Clang since version 2.8 and Microsoft's 2013 edition of Visual Studio, incorporate this C99 feature into their C++11 compilers, proving this proposal viable.

V. Design Decisions

Specifying this feature should be as easy as importing the C Standard text into the C++ Standard text, modulo how the two Standards differ in expressing ideas. There are outstanding issues on how overriding initializations work when said initializations are at the sub-subobject level; how are the unmentioned sibling sub-subobjects initialized? (See forum post by Richard Smith.)

The elision rules ban subobjects of class types with multiple members, therefore allowing class types of one member or array types. Thus this proposal integrates N3526 by Michael Price. But I don't think I did it right; I think N3526 allows a brace-levels between all or nothing, and this proposal doesn't.

A post by Richard Smith suggested adding rules for how initializers are sequenced.

The brace elision rules allowing class types of one member have changed to class types of one member or a single pack of variants.

VI. Technical Specifications

The changes are based off C++ Working Draft Standard N3797. The numbers for new paragraphs and footnotes have place-holders; the final numbers, plus moving existing ID numbers to fit, are to be determined later.

In section 8.5 [dcl.init], modify the grammar production list after paragraph 1:

initializer:
brace-or-equal-initializer
( expression-list )
brace-or-equal-initializer:
= initializer-clause
braced-init-list
initializer-clause:
assignment-expression
braced-init-list
designated-initializer-clause:
designator-list brace-or-equal-initializer
designator-list:
designator
designator-list designator
designator:
[ constant-expression ]
. templateopt id-expression
initializer-list:
initializer-clause ...opt
designated-initializer-clause ...opt
initializer-list , initializer-clause ...opt
initializer-list , designated-initializer-clause ...opt
braced-init-list:
{ initializer-list ,opt }
{ }

Add these changes to the last part of section A.7 [gram.decl]. And add the terms designated-initializer-clause, designator-list, and designator to the Index of grammar productions.

Author's Note: I don't know if appropriate members can ever be described that require the template keyword preceding them.

In the same section (8.5), add new paragraphs before paragraph 2:

-A- A designator is a token sequence representing a single element or member access from an unwritten reference to an object. When the implementation needs to synthesize designators, any required numeric values are canonically represented by an integer-literal. A designator-list is a chain of designators and represents nested access.

-B- A designated-initializer-clause (or pack expansion thereof) shall be permitted as a term of an initializer-list only when the immediately-enclosing braced-init-list corresponds to a object that is under aggregate initialization (8.5.1).

In section 8.5.1 [dcl.init.aggr], modify paragraph 2:

-2- When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members subobjects of the aggregate, in increasing subscript or member order where an initializer's target object is explicitly addressed in designated-initialization-clauses and implicitly addressed in initializer-clauses. Each member is copy-initialized from the corresponding initializer-clause. Each subobject that is a target object, in whole or part, by multiple qualifying initializers is initialized in composite; otherwise a mentioned subobject is initialized by a single corresponding designated-initializer-clause (which may use either copy- or direct-initialization), or an implied subobject is copy-initialized by a single corresponding initializer-clause.. If the either an initializer-clause or the post-designator-list part of a designator-initializer-clause is an expression and a narrowing conversion (8.5.4) is required to convert the expression, the program is ill-formed. [Note: If either an initializer-clause or the post-designator-list part of a designator-initializer-clause is itself an initializer list, the member target object is list-initialized, which will result in a recursive application of the rules in this section if the member target object 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]

Add a paragraph before paragraph 4:

-A- The directly settable subobjects of an aggregate are the ones reachable using a single designator; for an array type, the subobjects are its (direct) elements; for a class type, its named non-static data members and any named variant members not already covered. [Note: For class types, the directly settable subobjects are the ones that could appear in a mem-initializer-id for a theoretical constructor of the class type. —end note] The subobjects have an order; for an array type, it is in increasing subscript value; for a class type, it is in the order declared in the class definition. The aggregate is considered the initializing root of these subobjects. [Note: Any named subobject that is not a directly settable subobject is either within a non-aggregate or a directly settable subobject of some subaggregate. —end note] Any subobject's virtual designator list is its initializing root's virtual designator list followed by the designator from the initializing root to the subobject, starting with a null list for the aggregate itself. The designated successor of a directly settable subobject, if it exists, is the first following one where neither share any immediately-enclosing unions. [Note: The last declared directly settable subobject has no designated successor, nor do any of its co-variant members (when the last subobject is a union member). —end note] [Example:

#ifndef TEST
#define TEST 0
#endif

struct X {
  int a;
  static int b;
  int c;
  int :17;
  int d;
  int e[2];
  union { long f; float g; };
#if TEST
  double h;
#endif
} x;
  

The directly settable subobjects of x.e are x.e[0] and x.e[1], ordered as listed. Within x.e, x.e[0]'s designated successor is x.e[1], which itself has no designated successor. The directly settable subobjects of x are x.a, x.c, x.d, x.e (as a unit), x.f, and x.g, ordered as listed. Since x.f is under the same group of variants as x.g, it does not have a designated successor. If TEST gets predefined to a nonzero constant expression, then x.h would be the designated successor to both x.f and x.g, but have no designated successor itself. —end example]

Modify paragraph 4:

-4- An array of unknown size bound initialized with a brace-enclosed initializer-list braced-init-list containing n initializer-clauses, where n shall be greater than zero, a positive number of initializers is defined as having n elements (8.3.4), where n is one greater than the maximum value among the initializers' target object's virtual designator list's first designator's subscripts. Until the bound needs to be known, the array shall be treated as if its element count approaches infinity. [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:

int y[][2] = { 2, 4, 6, 8, 10 };

declares and initializes y as a two-dimensional array of three elements, each an array of two elements. With a minimum designator length (see below) of 2, each pair of loose inner elements was grouped as an array of the outer element. The second element of the third outer array gets initialized with an empty list (i.e. zero). Since there are no designators used, the last element's virtual designator list, [2][0], also reveals the answer. —end example] [Example:

int z[] = { 100, [3] = 101, 102, [1] = 103 };

declares and initializes z as a one-dimensional array. All the initializers' target objects have a single-element virtual designator list. Among the subscripts of the first designators of said lists, the maximum subscript is 4 (for the element of value 102 and virtual designator list [4]). So z is assigned an element length of 5. (The element with virtual designator list [2] is initialized with an empty list.) —end example] An empty initializer list {} shall not be used as the initializer-clause for an array of unknown bound107.

(Footnote 107 is unchanged.)

Paragraph 5 should be removed. (Author's Note: Its main information is now in paragraph A.)

Add a paragraph before paragraph 6:

-B- The target object of a designated-initializer-clause is the subobject whose virtual designator list is equivalent to the clause's designator-list; if no such subobject exists, the designated-initializer-clause shall be ill-formed; ill-formed behavior shall include when a designator in a designated-initializer-clause would reference: a static data member, a member within a non-aggregate, a non-existent member, an excessive subscript, an element as a subobject of a class type, or a member as a subobject of an array type. An initializer-clause in an aggregate with zero directly settable subobjects shall be ill-formed. The target object of an initializer-clause that is the first initializer is:

The minimum designator length of an aggregate is unity when there are no initializers or the first initializer is a designated-initializer-clause, otherwise it is the count of designators in the virtual designator list of the first initializer's target object. The target object of an initializer-clause that is not the first initializer is:

Modify paragraph 6:

-6- An initializer-list is ill-formed if the number of initializer-clauses exceeds the number of members or elements to initialize any initializer-clause or designated-initializer-clause is ill-formed. [Note: A pack expansion of an initializer-clause or designated-initializer-clause may create instantiations that result in an ill-formed initializer-list. —end note] [Example:

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

is ill-formed. —end example] If a designated-initializer-clause uses a designator-list shorter than the minimum designator length, the initializer-list is ill-formed. [Example:

char sl[][2] = { 'h', 'j', [1] = {[0]{'k'}, 'l'}, [2][1]{'\''}, [2][0] = ';' }; // error

is ill-formed due to the third initializer. —end example]

Add several paragraphs after paragraph 6:

-C- When an aggregate initialization for a non-union type is evaluated, the direct subobjects are initialized in order; an array type's initialization starts from the first element and proceeds in increasing subscript order while a class type's initialization starts from the first declared non-static data member and proceeds in succeeding declaration order, including anonymous bit-fields and class types, but excluding static data members. Evaluation occurs after pack expansions (14.5.3) for applicable initializer-clauses and designated-initializer-clauses have been resolved. [Note: Arrays of unknown bound have their bound fixed before evaluation, based on the (post-pack-expanded) initializers' target object's virtual designator lists, as described earlier. —end note] Every value computation and side effect associated with a given direct subobject's initialization is sequenced before every value and computation and side effect associated with initializations of later direct subobjects. [Note: This evaluation ordering holds regardless of the semantics of the aggregate initialization. —end note]

-D- Each initializer shall be matched with the direct subobject that is or contains that initializer's target object. If a direct subobject has no initializers that match with it, then its initialization shall be evaluated with its brace-or-equal-initializer or, if there is no brace-or-equal-initializer, an empty initializer list (8.5.4). If a direct subobject's lexically last matching initializer references the object in whole and does not use aggregate initialization, then its initialization shall be evaluated with said initializer. [Note: If the initializer is an initializer-clause, then copy-initialization is performed, else the type of initialization denoted by the post-designator-list part of the designated-initializer-clause is performed. —end note] [Note: An initializer matching a member of anonymous class type always references it in part. —end note] Otherwise, the direct subobject shall be evaluated with aggregate initialization; the subaggregate is a composite of all matching initializers except the ones before and at the lexically last matching initializer that references the object in whole and does not use aggregate initialization. The subaggregate is composed with: any matching initializer with an initializer-clause is converted to a designated-initializer-clause copy-initializing the same target object; any matching subaggregate initializer has its root-level initializer-clauses converted to designated-initializer-clauses (relative to the subaggregate) copy-initializing the same respective target objects [Note: The internals of sub-subaggregates are not altered. —end note]; insertions to the composite occur in lexical order, with aggregate-level clauses inserted with their first designator removed, and the initializers within subaggregate-level clauses inserted directly.

-E- When an aggregate initialization for a union-type is evaluated, and the same variant member is or contains every initializer's target object, then the variant member shall be the active member of its enclosing union (Which is either the aggregate directly or an anonymous union; if the latter, said anonymous union is the active member of the aggregate.) and its initialization shall be evaluated with a subaggregate identical to the original aggregate except that any root-level designated-initializer-clauses lose their first designator. [Note: If there is an initializer-clause in the aggregate, it must be the only one, it must be the first one, and any trailing designated-initializer-clause must target the union's first variant member in whole or part. —end note] Targeting across multiple variant members is conditionally supported with implementation-defined semantics. When there are no initializers: if a variant member has a brace-or-equal-initializer, then that variant member shall be the active member of its immediately enclosing union and its initialization shall be evaluated with its brace-or-equal-initializer, else the first variant member shall be the active member of its immediately enclosing union and its initialization shall be evaluated with an empty initializer list; if the aggregate is not the variant member's immediate enclosing object, then the intermediate anonymous union is the active member of the aggregate.

Paragraph 7 should be removed. (Author's Note: Its main information is now in paragraphs D and E.)

Author's Query: Should paragraph 8 be removed? Its material is sort-of implied from paragraphs A, B, and D. (The current paragraph 8 says that empty subaggregate members can't be skipped unless the aggregate stops early. The fact that paragraph B has non-first initializer-clauses always use designated successors means no named member can be skipped outside of co-variants unless the aggregate stops early and/or designators are used.)

Modify paragraph 9:

-9- If an incomplete or empty initializer-list leaves a member of reference type uninitialized a subobject of reference type and outside any non-aggregate is not the target object of any initializer, the program is ill-formed.

Paragraphs 10 and 11 should be removed. (Author's Note: Their main information is now in paragraphs A and B.)

Modify paragraph 12:

-12- All implicit type conversions (Clause 4) are considered when initializing the an aggregate member with an assignment-expression, i.e. without list-initialization. If the assignment-expression can initialize a member the target object, the member subobject is initialized. Otherwise, if the member target object is itself a subaggregate, brace elision is assumed and the assignment-expression is considered for the initialization of the first member of the subaggregate it is list-initialized with a list containing just the assignment-expression. [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. Since that list-initialization would be ill-formed when the subaggregate has zero directly settable subobjects, the initializer is ill-formed unless the assignment-expression is of a type that has an implicit conversion to the subaggregate's type. —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 A list-initialization context applies on 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]

Paragraphs 15 should be removed. (Author's Note: Its information is covered by paragraph B.) Author's Query: The new syntax allows a union that has an array as its first member to use "brace elision" (I purged the term.) to fill the array's inner non-aggregate elements at the top aggregate level. Is that allowed now? If not, should I keep support? (I think yes.)

Paragraph 16 should be removed. (Author's Note: Its main information is covered by paragraph 12 plus what was needed by paragraph 15.)

Author's Query: Can anyone provide examples that cover the (possibly) removed paragraphs (5, 7, 10, 11, 15, 16)? They should at least cover the material the examples within those paragraphs did.

In section 8.5.4 [dcl.init.list], modify paragraph 4:

-4- Within the initializer-list of a braced-init-list that has no designated-initializer-clauses, the initializer-clauses, including any that result from pack expansions (14.5.3), 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] [Note: If the initializer-list contains designated-initializer-clauses, the braced-init-list must be for aggregate initialization (8.5.1). —end note]

In section 14.5.3 [temp.variadic], modify the third list item (not counting the second item's inner list) of paragraph 4:

VII. Acknowledgments

VIII. History

IX. References