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);
Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • What's your question? – R Sahu Apr 10 '15 at 15:18
  • something like `Type object((foo, bar), anotherFoo, anotherBar);`? – user3528438 Apr 10 '15 at 15:20
  • My original statement about the question is unclear. Updated now. – Lingxi Apr 10 '15 at 15:35
  • Have you tried writing a forwarding constructor-type-thing that uses `{}`? – Yakk - Adam Nevraumont Apr 10 '15 at 15:54
  • @Yakk It works on GCC and clang. See http://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. – Lingxi Apr 10 '15 at 16:07
  • 1
    `std::allocator::construct` (and `std::allocator_traits::construct`) are specified to use `()` rather than `{}`, and must remain so for compatibility reasons (`vector(10, 10)` vs. `vector{10, 10}`). There's [an LWG issue](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2089) to make it fall back to using `{}` if `()` doesn't work. – T.C. Apr 10 '15 at 16:11
  • @T.C. I get your point. It is convincing. The fallback to `{}` may still surprise the programmers in some cases :) – Lingxi Apr 10 '15 at 16:15

2 Answers2

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!

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • You'd have to add `list_construct` to allocators too. I'm not sure I like that direction. – T.C. Apr 10 '15 at 16:21
  • 2
    @T.C. 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. – Yakk - Adam Nevraumont Apr 10 '15 at 16:24
  • 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. – T.C. Apr 10 '15 at 16:29
  • I guess it would be a good idea to automatically switch between `{}` and `()` according to whether the supplied type is an aggregate or not. – Lingxi Apr 10 '15 at 16:37
  • @Yakk `emplace` and `list_emplace` are different in name. This makes it impossible to write generic template code using a uniform method name. – Lingxi Apr 10 '15 at 16:42
  • @Yakk Well, I just realize that such generic code, if possible to write, would then have confusing behaviors :( – Lingxi Apr 10 '15 at 16:54
  • @Lingxi Yep. There is no universal way to initialize data in C++, despite attempts of C++11. – Yakk - Adam Nevraumont Apr 10 '15 at 17:18
  • 3
    "there is no way to reach the "3 copies of 1" syntax using {} construction" Is `std::vector{3, 1, std::allocator{}}` cheating? ;) – Casey Apr 10 '15 at 20:40
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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Indeed, (unaware of that issue), I implemented that fallback [myself](https://github.com/Arcoth/Constainer/blob/master/impl/BasicVector.hxx#L212). It doesn't even break code. – Columbo Sep 12 '15 at 07:17