By forwarded in-place construction, I take to mean std::allocator::construct and the various emplace methods, e.g., std::vector::emplace_back. I just find that forwarded in-place construction in C++ does not (unable to?) take advantage of the list-initialization syntax. As a result, it seems one can never forward in-place construct an aggregate. I just want to make sure whether forwarded in-place construction does not support list-initialization and hence aggregate types. Is this due to the limitation of the language? Could someone provide reference to the standard concerning this issue? Following is an illustration:
While we can do in-place construction directly like
int(*p)[3] = ...;
new(p) int[3]{1, 2, 3};
we cannot do forwarded in-place construction like
std::allocator<int[3]> allo;
allo.construct(p, 1, 2, 3);
While {}
has been called uniform initialization syntax, it is far from universal.
take these two examples:
size_t a = 3;
size_t b = 1;
std::vector<size_t> v1{a,b};
std::vector<size_t> v2(a,b);
in the first case, we construct a vector containing two elements, 3
and 1
.
In the second case, we create a vector containing 1,1,1
-- 3 copies of 1
.
live example.
So {}
based construction can cause a different behavior than ()
based construction in some cases. What more, in the above case, there is no way to reach the "3 copies of 1" syntax using {}
construction (that I know of). But the {3,2}
case can be handled by simply explicitly creating an initializer list and passing it to ()
.
As most types that take initializer lists can be emplace constructed by explicitly passing in an initializer list, and the C++ standard library was designed for types with constructors more than types without them, the C++ standard library nearly uniformly emplace constructs using ()
and not {}
.
The downside is that types that want to be list-initialized cannot be emplaced via this mechanism.
In theory, list_emplace
methods that construct using {}
instead could be added to each interface. I encourage you to propose that!
std::allocator
's construct()
(and also the default implementation provided by std::allocator_traits
) are specified to use ()
: ::new((void *)p) U(std::forward<Args>(args)...)
(see [allocator.members]/p12, [allocator.traits.members]/p5).
It is impractical to change it to {}
at this point, because it would silently break existing code:
std::vector<std::vector<int>> foo;
foo.emplace_back(10, 10); // add a vector of ten 10s with (); two 10s with {}
There is an LWG issue to make it fall back to using {}
if ()
doesn't work. We'll have to see whether the committee agrees with that direction.
@Yakk points out the potential drawbacks with this approach:
foo.emplace_back(10); // ten 0s
foo.emplace_back(10, 10); // ten 10s
foo.emplace_back(10, 10, 10); // three(!) 10s
A similar issue (see Appendix B of N2215) led to the decision that list-initialization will always prefer initializer_list
constructors.