c++ - 为什么 emplace_back() 不使用统一初始化?

标签 c++ vector c++11 uniform-initialization

以下代码:

#include <vector>

struct S
{
    int x, y;
};

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

使用 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&&'

建议 vector 使用常规的 () 构造函数语法从 emplace_back() 的参数构造元素。为什么 vector 不使用 {} 统一初始化语法来代替上述示例?

在我看来,使用 {} 并没有什么可失去的(它在有构造函数时调用构造函数,但在没有构造函数时仍然有效),而且会更多本着 C++11 的精神使用 {} - 毕竟,uniform 初始化的全部意义在于它被统一使用——即无处不在——来初始化对象。

最佳答案

伟大的思想都一样;v)。我提交了一份缺陷报告并建议对这个主题的标准进行更改。

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

另外,Luc Danton 帮助我理解了困难: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.

关于c++ - 为什么 emplace_back() 不使用统一初始化?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8782895/

相关文章:

c++ - 在堆栈上分配不完整类型

c++ - 二维随机单位 vector

opengl - OpenGL中如何确定视线平面的法向量?

c++ - 具有函数、输入和输出类型的可变参数模板

c++函数式编程(boost::phoenix && boost::spirit)测试指针占位符中的空指针

C++ 运算符销毁输入变量

c++ - 有没有一种优雅的方法可以将 vector<complex<float>> 转换为 vector<short>?

c++ - 重载分辨率 : assignment of empty braces

c++ - 通过函数指针传递给可变参数函数的参数改变它们的值

c++ - 在右值对象上调用 getter 方法时获取右值