为啥 emplace_back() 不使用统一初始化?

Posted

技术标签:

【中文标题】为啥 emplace_back() 不使用统一初始化?【英文标题】:Why doesn't emplace_back() use uniform initialization?为什么 emplace_back() 不使用统一初始化? 【发布时间】:2012-02-05 15:52:52 【问题描述】:

以下代码:

#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 不使用 uniform-initialization 语法来代替上述示例?

在我看来,使用并没有什么可失去的(当有构造函数时它会调用构造函数,但在没有构造函数时仍然有效),它会更符合C+的精神+11 使用 - 毕竟,uniform 初始化的全部意义在于它被统一使用——即在任何地方——初始化对象。

【问题讨论】:

【参考方案1】:

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

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

另外,Luc Danton 帮助我理解了困难:Direct vs uniform initialization in std::allocator。

当 EmplaceConstructible (23.2.1 [container.requirements.general]/13)需求用于初始化 一个对象,发生直接初始化。初始化聚合或 使用带有 emplace 的 std::initializer_list 构造函数需要命名 初始化的类型和移动一个临时的。这是由于 std::allocator::construct 使用直接初始化,而不是 列表初始化(有时称为“统一初始化”) 语法。

改变 std::allocator::construct 以使用列表初始化 除其他外,将优先考虑 std::initializer_list 构造函数重载,以不直观的方式破坏有效代码 不可修复的方式 — emplace_back 无法访问 构造函数被 std::initializer_list 抢占,本质上没有 重新实现 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

建议的折衷方案是将 SFINAE 与 std::is_constructible 一起使用, 它测试直接初始化是否格式正确。如果 is_constructible 为假,则另一种 选择使用 std::allocator::construct 重载 列表初始化。由于列表初始化总是依赖于 直接初始化,用户将看到诊断消息,就好像 列表初始化(uniform-initialization)总是被使用, 因为直接初始化重载不会失败。

我可以看到两个极端案例暴露了这个方案中的空白。一发生 当用于 std::initializer_list 的参数满足 构造函数,例如尝试在 上面的例子。解决方法是明确指定 std::initializer_list 类型,如 v.emplace_back(std::initializer_list(3, 4))。由于这匹配 语义好像 std::initializer_list 被推导出来,似乎 在这里没有真正的问题。

另一种情况是用于聚合初始化的参数 满足构造函数。由于聚合不能有用户定义的 构造函数,这要求第一个非静态数据成员 聚合可以从聚合类型隐式转换,并且 初始化器列表只有一个元素。解决方法是 为第二个成员提供一个初始化器。仍然不可能 就地构造一个只有一个非静态数据成员的聚合 从可转换的类型转换为聚合自己的类型。这 似乎是一个可以接受的小洞。

【讨论】:

人们可以发明一种语言支持类型来完美地转发语法大括号初始化列表。然后可以说 emplace_back(a, b, ...) @JohannesSchaub-litb:恕我直言,这更像是std::initializer_list 应该的样子。但是对std::allocator 的简单修改应该可以解决问题。 @JohannesSchaub-litb 安置的优势在于创建不可移动构造的对象。为什么能够放置容器很重要?同样,请记住,initializer_list 也是不可移动的,并且始终会制作副本。 如果这适用于聚合,那就太好了。谢谢你的努力。作为更新,以下是自上述 cmets 以来发生的情况:[2015-02 Cologne] 移至 EWG,Ville 写论文。 [2015-09,电信] Ville: N4462 在 Lenexa 进行了审核。 EWG 讨论将在科纳继续。 [2016-08 Chicago] 见 N4462 Lenexa 中的注释说 Marshall & Jonathan 自愿为此撰写论文 进一步更新:P0960 现在在 C++20 工作草案中!

以上是关于为啥 emplace_back() 不使用统一初始化?的主要内容,如果未能解决你的问题,请参考以下文章

为啥统一初始化没有显式构造函数[重复]

为什么emplace_back比push_back更快?快是有条件的

push_back和emplace_back的区别

[CPP]push_back和emplace_back的区别

emplace_back 当循环同一个列表

如何使用向量的 emplace_back 函数? [关闭]