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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With