Forwarded in-place construction and list-initializ

2019-08-05 13:01发布

问题:

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);

回答1:

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!



回答2:

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.