Forwarded in-place construction and list-initialization
Asked Answered
R

2

1

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);
Raskin answered 10/4, 2015 at 15:17 Comment(7)
What's your question?Vapor
something like Type object((foo, bar), anotherFoo, anotherBar);?Esthete
My original statement about the question is unclear. Updated now.Raskin
Have you tried writing a forwarding constructor-type-thing that uses {}?Furfur
@Yakk It works on GCC and clang. See coliru.stacked-crooked.com/a/833a11295113578b. But I'm not sure whether this is actually allowed according to the standard. If it is permitted, I see no reason in the standard library using what it is using now instead of this.Raskin
std::allocator::construct (and std::allocator_traits::construct) are specified to use () rather than {}, and must remain so for compatibility reasons (vector<int>(10, 10) vs. vector<int>{10, 10}). There's an LWG issue to make it fall back to using {} if () doesn't work.Edwinedwina
@Edwinedwina I get your point. It is convincing. The fallback to {} may still surprise the programmers in some cases :)Raskin
F
5

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!

Furfur answered 10/4, 2015 at 16:17 Comment(8)
You'd have to add list_construct to allocators too. I'm not sure I like that direction.Edwinedwina
@Edwinedwina I saw the other direction (where construct falls back on list construction if bracket construction fails). That seems error prone: emplace(1,2) putting a 2 while emplace(1,2,3) putting a 1,2,3 seems utter insanity.Furfur
That's a good point. I believe there was a similar discussion about the semantics of list-initialization during the C++11 standardization process that lead to the "initializer_list constructor is always preferred for {}" rule.Edwinedwina
I guess it would be a good idea to automatically switch between {} and () according to whether the supplied type is an aggregate or not.Raskin
@Yakk emplace and list_emplace are different in name. This makes it impossible to write generic template code using a uniform method name.Raskin
@Yakk Well, I just realize that such generic code, if possible to write, would then have confusing behaviors :(Raskin
@Raskin Yep. There is no universal way to initialize data in C++, despite attempts of C++11.Furfur
"there is no way to reach the "3 copies of 1" syntax using {} construction" Is std::vector<int>{3, 1, std::allocator<int>{}} cheating? ;)Unthrone
E
3

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.

Edwinedwina answered 10/4, 2015 at 16:20 Comment(1)
Indeed, (unaware of that issue), I implemented that fallback myself. It doesn't even break code.Revengeful

© 2022 - 2024 — McMap. All rights reserved.