- A+
It seems to me that aggregate initialization (of suitable types) is not considered a constructor that you can actually call (except a few cases.)
As an example, if we have a very simple aggregate type:
struct Foo { int x, y; };
then this obviously works:
auto p = new Foo {42, 17}; // not "Foo (42, 17)" though...
but this doesn't work on any compiler that I've tested (including latest versions of MSVC, GCC, and Clang):
std::vector<Foo> v; v.emplace_back(2, 3);
Again, it seems to me that any code that wants to call the constructor for a type T
(in this case, the code in vector::emplace_back
that forwards the passed arguments to the c'tor of T
,) cannot use aggregate initialization simply (it seems) because they use parentheses instead of curly braces!
Why is that? Is it just a missed feature (nobody has proposed/implemented it yet,) or there are deeper reasons? This is a little strange, because aggregate types by definition have no other constructor to make the resolution ambiguous, so the language could have just defined a default aggregate constructor (or something) that would have all the members as defaulted arguments.
Is it just a matter of syntax? If the implementation of vector::emplace_back
in the above example had used placement new
with curly braces instead of parentheses, would it have worked?
Note: I want to thank those comments who've pointed out the behavior of vector
and emplace
because their comments will be valuable to those who'll find this question using those keywords, but I also want to point out that those are just examples. I picked the most familiar and concise example, but my point was about explicitly calling the aggregate initializer in any code (or in placement new
, more specifically.)
For what it's worth, P0960 "Allow initializing aggregates from a parenthesized list of values" does exactly what it says. It seems to have passed EWG and is on its way into C++20.
aggregate types by definition have no other constructor to make the resolution ambiguous
That is incorrect. All classes have default constructors, as well as copy/move constructors. Even if you = delete
them or they are implicitly deleted, they still technically have such constructors (you just can't call them).
C++ being C++, there are naturally corner cases where even P0960 does the "wrong thing", as outlined in the paper:
struct A; struct C { operator A(); //Implicitly convertible to `A` }; struct A { C c; }; //First member is a `C` C c2; A a(c2);
The initialization of a
is a case of ambiguity. Two things could happen. You could perform implicit conversion of c2
to an A
, then initialize a
from the resulting prvalue. Or you could perform aggregate initialization of a
by a single value of type C
.
P0960 takes the backwards compatible route: if a constructor could be called (under existing rules), then it always takes priority. Parentheses only invoke aggregate initialization if there is no constructor that could have been called.