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 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).
From the initial EWG discussion:
After CWG review and feedback:
std::vector<std::array<int, 3>>().emplace_back(1,
2, 3)
work via language specification: 3 | 1 | 20 | 11 | 11After 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.
A(b)
should not change.A
, A{b}
will not convert
b
to A
, unless b
is of type
A
or type derived from A
. A(b)
will convert even in other cases. This difference applies only to non-arrays.A{x, y, x}
forbids narrowing conversions,
while the new syntax A(x, y, z)
does allow narrowing.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 letT
1, …,T
n 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:
- If the destination is an array of type
U[N]
, then for each integer 0 ≤ i < k, the ith element is copy-initialized with xi+1, and for each integer k ≤ i <N
, the ith element is direct-list-initialized with{}
.- Otherwise, the destination type is a (possibly cv-qualified) class type
A
. Given an invented, public constructor of the formthat is a constexpr constructor [dcl.constexpr] if it satisfies the requirements for a constexpr constructor, then if the expressionexplicit A(T
1&& t
1,
…, T
k&& t
k) :
e1(std::forward<T
1>(t
1)),
…,
ek(std::forward<T
k>(t
k)),
ek+1(
vk+1),
…,
en(
vn)) {}
A(
x1, …, xk)
is ill-formed, the program is ill-formed. Otherwise, the object is initialized as if byA(
x1, …, xk)
.— end example] — end note]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
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.