Why doesn't emplace_back () use uniform initialization?

The following code:

#include <vector> struct S { int x, y; }; int main() { std::vector<S> v; v.emplace_back(0, 0); } 

Throws the following errors when compiling 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&&' 

Assuming that vector uses the usual constructor of the constructor () to construct an element from the arguments in emplace_back() . Why instead of vector use the equivalent initialization syntax {} to make examples like the ones described above?

It seems to me that there is nothing to lose using {} (it calls the constructor when it is, but still works when it is not), and it will be more like C + +11 for using {} - in the end, the whole point A uniform initialization is that it is used uniformly - that is, everywhere - to initialize objects.

+45
c ++ vector c ++ 11 uniform-initialization
Jan 09 2018-12-01T00:
source share
1 answer

Great thoughts also think; v). I submitted a defect report and suggested changing the standard on this topic.

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

Also, Luke Danton helped me understand the complexity: Direct or standard initialization in std :: allocator .

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

Changing std :: allocator :: construct to use list initialization will, among other things, give preference to std :: initializer_list overloading the constructor, violating existing code in an unintuitive and inapplicable way - for emplace_back there will be no access to access the constructor, superseded by std :: initializer_list , without significant re-execution of 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 trade-off is to use SFINAE with std :: is_constructible, which checks if direct initialization is generated correctly. If is_constructible is false, then the alternative to std :: allocator :: is the overload construct selected that uses List initialization. Since list initialization always returns to direct initialization, the user will see diagnostic messages, as if list initialization (unified initialization) has always been used, because overload with direct initialization cannot fail.

I see two corner cases that open spaces in this pattern. One occurs when the arguments intended for std :: initializer_list satisfy the constructor, such as an attempt to insert - insert the value {3, 4} in the above example. The workaround is to explicitly specify std :: initializer_list, as in v.emplace_back (std :: initializer_list (3, 4)). Since this corresponds to the semantics, as if std :: initializer_list was output, it seems that there will not be a real problem.

In another case, arguments intended for aggregate initialization satisfy the constructor. Since aggregates cannot be defined by the user of constructors, this requires that the first non-static data member the collection is implicitly converted from the aggregate type and that the list of initializers has one element. Workaround put the initializer for the second element. It is still not possible to locally build a population with one non-static data element by converting from a type convertible to its own aggregate type. This one seems like an acceptable little hole.

+42
Jan 09 '12 at 1:25
source share
— -



All Articles