Why doesn't emplace_back() use uniform initialization?

C++VectorC++11Uniform Initialization

C++ Problem Overview


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.

C++ Solutions


Solution 1 - C++

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: https://stackoverflow.com/questions/7820145/direct-vs-uniform-initialization-in-stdallocator.

> 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> 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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionHighCommander4View Question on Stackoverflow
Solution 1 - C++PotatoswatterView Answer on Stackoverflow