77

The following code:

#include <vector>

struct S
{
    int x, y;
};

int main()
{
    std::vector<S> v;
    v.emplace_back(0, 0);
}

Gives the following errors when compiled with GCC:

In file included from c++/4.7.0/i686-pc-linux-gnu/bits/c++allocator.h:34:0,
                 from c++/4.7.0/bits/allocator.h:48,
                 from c++/4.7.0/vector:62,
                 from test.cpp:1:
c++/4.7.0/ext/new_allocator.h: In instantiation of 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = S; _Args = {int, int}; _Tp = S]':
c++/4.7.0/bits/alloc_traits.h:265:4:   required from 'static typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>; typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type = void]'
c++/4.7.0/bits/alloc_traits.h:402:4:   required from 'static void std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>]'
c++/4.7.0/bits/vector.tcc:97:6:   required from 'void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int, int}; _Tp = S; _Alloc = std::allocator<S>]'
test.cpp:11:24:   required from here
c++/4.7.0/ext/new_allocator.h:110:4: error: new initializer expression list treated as compound expression [-fpermissive]
c++/4.7.0/ext/new_allocator.h:110:4: error: no matching function for call to 'S::S(int)'
c++/4.7.0/ext/new_allocator.h:110:4: note: candidates are:
test.cpp:3:8: note: S::S()
test.cpp:3:8: note:   candidate expects 0 arguments, 1 provided
test.cpp:3:8: note: constexpr S::S(const S&)
test.cpp:3:8: note:   no known conversion for argument 1 from 'int' to 'const S&'
test.cpp:3:8: note: constexpr S::S(S&&)
test.cpp:3:8: note:   no known conversion for argument 1 from 'int' to 'S&&'

Suggesting that vector is using regular () constructor syntax to construct the element from the arguments to emplace_back(). Why doesn't vector use the {} uniform-initialization syntax instead, to make examples like the above work?

It seems to me that there is nothing to lose by using {} (it calls the constructor when there is one, but still works when there isn't one), and it would be more in the spirit of C++11 to use {} - after all, the whole point of uniform initialization is that it is used uniformly - that is, everywhere - to initialize objects.

HighCommander4
  • 50,428
  • 24
  • 122
  • 194

1 Answers1

67

Great minds think alike ;v) . I submitted a defect report and suggested a change to the standard on this very topic.

http://cplusplus.github.com/LWG/lwg-active.html#2089

Also, Luc Danton helped me understand the difficulty: Direct vs uniform initialization in std::allocator.

When the EmplaceConstructible (23.2.1 [container.requirements.general]/13) requirement is used to initialize an object, direct-initialization occurs. Initializing an aggregate or using a std::initializer_list constructor with emplace requires naming the initialized type and moving a temporary. This is a result of std::allocator::construct using direct-initialization, not list-initialization (sometimes called "uniform initialization") syntax.

Altering std::allocator::construct to use list-initialization would, among other things, give preference to std::initializer_list constructor overloads, breaking valid code in an unintuitive and unfixable way — there would be no way for emplace_back to access a constructor preempted by std::initializer_list without essentially reimplementing push_back.

std::vector<std::vector<int>> v;
v.emplace_back(3, 4); // v[0] == {4, 4, 4}, not {3, 4} as in list-initialization

The proposed compromise is to use SFINAE with std::is_constructible, which tests whether direct-initialization is well formed. If is_constructible is false, then an alternative std::allocator::construct overload is chosen which uses list-initialization. Since list-initialization always falls back on direct-initialization, the user will see diagnostic messages as if list-initialization (uniform-initialization) were always being used, because the direct-initialization overload cannot fail.

I can see two corner cases that expose gaps in this scheme. One occurs when arguments intended for std::initializer_list satisfy a constructor, such as trying to emplace-insert a value of {3, 4} in the above example. The workaround is to explicitly specify the std::initializer_list type, as in v.emplace_back(std::initializer_list(3, 4)). Since this matches the semantics as if std::initializer_list were deduced, there seems to be no real problem here.

The other case is when arguments intended for aggregate initialization satisfy a constructor. Since aggregates cannot have user-defined constructors, this requires that the first nonstatic data member of the aggregate be implicitly convertible from the aggregate type, and that the initializer list have one element. The workaround is to supply an initializer for the second member. It remains impossible to in-place construct an aggregate with only one nonstatic data member by conversion from a type convertible to the aggregate's own type. This seems like an acceptably small hole.

Community
  • 1
  • 1
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 2
    One could invent a language supporrt type to perfectly forward syntactic braces init lists. Then one can just say emplace_back({a, b, ...}) – Johannes Schaub - litb Jan 09 '12 at 08:07
  • 4
    @JohannesSchaub-litb: IMHO, that's more like what `std::initializer_list` should have been. But the simple modification to `std::allocator` should fix things up alright. – Potatoswatter Jan 09 '12 at 08:14
  • What if thevuser wants to emplace a vector with a custom alloc and writes emplace_back(1,2,alloc). How does it know to forward it as ({1,2}, alloc) ? imo that allocator hack breaks thinga ecen more. a proper solution should be worked on. – Johannes Schaub - litb Jan 09 '12 at 08:26
  • @JohannesSchaub-litb: The custom allocator should implement `construct` the same way as `std::allocator`. It's best to inherit from `std::allocator` and only replace behaviors getting customized anyway. – Potatoswatter Jan 09 '12 at 08:33
  • No i mean how do you know what to pass as a brace init liat and what to pass as a normal argument forward? in my above example the alloc ahould not be part of the init list. – Johannes Schaub - litb Jan 09 '12 at 08:36
  • Ah, sorry the previous message isn't really applicable to that issue. Emplacement doesn't apply to something going into an allocator anyway, so that particular instance is solved by `v.emplace_back( alloc )` followed by a sequence of `emplace_back` operations. – Potatoswatter Jan 09 '12 at 08:38
  • And you still couls not forward init lists with depth > 1. So you cannot emplace_back a std::map using that hack. – Johannes Schaub - litb Jan 09 '12 at 08:38
  • @JohannesSchaub-litb To fix the syntax of your example alone, using the example in my proposal, it would simply be `v.emplace_back( std::initializer_list< T >( 1, 2 ), alloc )`. Ugly, but nothing is lost semantically. I don't understand "couls not forward init lists with depth > 1". – Potatoswatter Jan 09 '12 at 08:40
  • semantically the generically of the braced list is lost. the first param doesnt have to be a initializer_list. it could be an aggregate you want to list initialize generically. Having the compiler have a special type designating a braced init list could solve it i think. whenever an object of it is passed, the argument expression is rransformed to be a braced initlist with the respective elements. – Johannes Schaub - litb Jan 09 '12 at 08:49
  • re depth. consider emplace_back(1, 2, 3, 4). user wants to pass it as {{1, 2}, {3, 4}}. How can we forward it that way? – Johannes Schaub - litb Jan 09 '12 at 08:51
  • @JohannesSchaub-litb by abandoning deduction of `std::initializer_list` and naming it explicitly, as in my example. But remember that `initializer_list` stores, does not forward, values. It is problematic that the conversion from the argument type to the container's `value_type` occurs when constructing this object. But you can avoid that by breaking it into multiple `emplace` calls, which is likely better style anyway. – Potatoswatter Jan 09 '12 at 08:59
  • I wonder wheter it is possible to simulate all this in a lib by having special marker arguments "bo" and "be" standing for "brace open" and "brace close!. And then write emplace_back(bo, bo, 1, 2 be, bo, 3, 4 be, be). Before forwarding it one would beed to stick them into braces by pack expansion somehow. – Johannes Schaub - litb Jan 09 '12 at 09:02
  • multiple emplace calls will push multiple new elements, not a single large element. – Johannes Schaub - litb Jan 09 '12 at 09:03
  • Oops, you can't avoid the early conversion and subsequent copy with multiple `emplace`s, but neither is the effect any different from a deduced `initializer_list`. – Potatoswatter Jan 09 '12 at 09:03
  • 2
    @JohannesSchaub-litb the strength of emplacement is in creating objects that aren't MoveConstructible. Why is it important to be able to emplace containers? Again, remember that `initializer_list` is also not movable and a copy is always made. – Potatoswatter Jan 09 '12 at 09:07
  • Im not only about emplace but about the.bigger picture. – Johannes Schaub - litb Jan 09 '12 at 09:09
  • 1
    Hot off the press: the committee's Evolution Working Group looked at this issue this morning and encouraged the Library Working Group to adopt the proposed resolution. – HighCommander4 May 05 '15 at 16:17
  • @HighCommander4 Nice, that's the most success I've had yet! However, I don't really understand why this would be in EWG, aside from Ville being both its chaiman and the author of the paper which turned this into a proposal. And I don't think they forward directly to LWG. Perhaps you meant LEWG? – Potatoswatter May 06 '15 at 00:42
  • 1
    @Potatoswatter: No, LWG sent the issue to EWG at a previous meeting, to ask whether there should be a core-language feature for "constructing the object the way that works". By sending it back to LWG and encouraging them to adopt the proposed library solution instead, EWG is saying "no". – HighCommander4 May 06 '15 at 01:01
  • 1
    @HighCommander4 OK, thanks for the insight, that explains why Ville wrote that paper. However, while it's nice that the wheels are turning, LWG can (perhaps must) still forward it to LEWG since it's an extension, before it returns to LWG again. At any stage it can be rejected :P and LWG already avoided supporting it once. – Potatoswatter May 06 '15 at 05:20
  • 1
    @Potatoswatter: I wouldn't characterize it as they avoided supporting it. They recognized that a language feature might be the appropriate avenue for solving this problem, and accordingly asked EWG for guidance. Now that they got a response, I expect they'll get on with the library solution. – HighCommander4 May 06 '15 at 14:27
  • 2
    It would be great if this worked for aggregates. Thanks for your effort. As an update, here's what's happened since the above comments: _[2015-02 Cologne] Move to EWG, Ville to write a paper. [**2015-09**, Telecom] **Ville: [N4462](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4462.html)** reviewed in Lenexa. **EWG discussion to continue** in Kona. [**2016-08** Chicago] See N4462 The notes in Lenexa say that **Marshall & Jonathan volunteered to write a paper on this**_ – underscore_d Sep 17 '16 at 16:55
  • 1
    At long last, the latest proposal for this, **[P0960](http://wg21.link/p0960)**, which takes the approach of solving the problem in the core language rather than the library, has been **approved by the Evolution Working Group** at the San Diego meeting in November, targeting C++20. – HighCommander4 Dec 15 '18 at 05:08
  • 2
    Further update: **[P0960](http://wg21.link/p0960)** is now in the C++20 working draft! – HighCommander4 Mar 03 '19 at 00:41