Is this an oversight in the Standard?
It is considered a defect in the standard, tracked as LWG #2089, which was resolved by C++20. There, constructor syntax can perform aggregate initialization on an aggregate type, so long as the expressions provided wouldn't have called the copy/move/default constructors. Since all forms of indirect initialization (push_back
, in_place
, make_*
, etc) uses constructor syntax explicitly, they can now initialize aggregates.
Pre-C++20, a good solution to it was elusive.
The fundamental problem comes from the fact that you cannot just use braced-init-lists willy-nilly. List initialization of types with constructors can actually hide constructors, such that certain constructors can be impossible to call through list initialization. This is the vector<int> v{1, 2};
problem. That creates a 2-element vector
, not a 1-element vector whose only element is 2.
Because of this, you cannot use list initialization in generic contexts like allocator::construct
.
Which brings us to:
I would think there's be a SFINAE trick to do that if possible, else resort to brace init that also works for aggregates.
That would require using the is_aggregate
type trait from C++17. But there's a problem with that: you would then have to propagate this SFINAE trick into all of the places where indirect initialization is used. This includes any/variant/optional
's in_place
constructors and emplacements, make_shared/unique
calls, and so forth, none of which use allocator::construct
.
And that doesn't count user code where such indirect initialization is needed. If users don't do the same initialization that the C++ standard library does, people will be upset.
This is a sticky problem to solve in a way that doesn't bifurcate indirect initialization APIs into groups that allow aggregates and groups that don't. There are many possible solutions, and none of them are ideal.
The language solution is the best of the bunch.