Document number: P0960R2
Audience: EWG, CWG

Ville Voutilainen
Thomas Köppe
2019-01-21

Allow initializing aggregates from a parenthesized list of values

Abstract

This paper proposes allowing initializing aggregates from a parenthesized list of values; that is, Aggr(val1, val2) would mean the same thing as Aggr{val1, val2}, except that narrowing conversions are allowed. This is a language fix for the problem illustrated in N4462.

Revision history

Jacksonville 2018 discussion feedback

Revision R1 implements supporting parenthesized initialization for aggregates including arrays, without support for designated initializers. The matter of initialization and deleted constructors is handled by separate paper(s).

Rapperswil 2018 discussion feedback

From the initial EWG discussion:

After CWG review and feedback:

After further CWG feedback and EWG discussion:

This revision changes the mental model from the original “literal rewrite to a braced list” to “as if a synthesized, explicit constructor with appropriate mem-initializers was called”. This has the effect of allowing narrowing conversions in the parenthesized list, even when narrowing conversions would be forbidden in the corresponding braced list syntax. It also clarifies the non-extension of temporary lifetimes of temporaries bound to references, the absence of brace elision, and the absence of a well-defined order of evaluation of the arguments.

During the discussion, it was suggested by CWG that we should even break an existing corner case: Given struct A; struct C { operator A(); }; struct A { C c; };, the declaration A a(c); currently invokes C’s conversion function to A. It was suggested to change this behaviour to use the (arguably better-matching) aggregate initialization in this case, i.e. to behave like A a{c}; and not like A a = c;. There was, however, no consensus to pursue this direction, and the proposal remains a pure extension at this point.

Design principles

Wording

In [dcl.init]/17, delete item (17.5):

Otherwise, if the destination type is an array, the program is ill-formed.

In [dcl.init]/(17.6.2), edit as follows:

Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (16.3.1.3), and the best one is chosen through overload resolution (16.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies and the destination type is not an aggregate, or the overload resolution is ambiguous, the initialization is ill-formed.

After [dcl.init]/(17.6.3) and before [dcl.init]/(17.7), insert a new top-level bullet as follows:

If the destination type is an aggregate and the initializer is a parenthesized expression-list and no viable constructor was found above, the object is initialized as follows. Let e1, …, en be the elements the aggregate [dcl.init.aggr], and let T1, …, Tn be their respective declared types. Let x1, …, xk be the elements of the expression-list. If k is greater than n, the program is ill-formed. For each integer 1 ≤ i ≤ N, let vi be the empty token sequence, unless the destination type is a class type and ei has a default member initializer [class.mem], in which case vi consists of the same tokens as that default member initializer. [Note: Designators are not permitted. By contrast with direct-list-initialization, narrowing conversions [dcl.list.init] are permitted, the order of evaluation of the list elements is indeterminate, a temporary object [class.temporary] bound to a reference does not have its lifetime extended, and there is no brace elision. [Example:
struct A {
  int a;
  int&& r;
};

int f();

A a1{1, f()};     // OK
A a2{1.0, 1};     // Error, narrowing conversion
A a2(1.0, f());   // well-formed, but dangling reference
— end example] — end note]

Drafting note: the note in the above wording can easily be left out - the grammar of a parenthesized initialization does not allow designators. The note is added just for explanatory clarity.