72

以下代码:

#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 的精神总而言之,统一初始化的全部意义在于它被统一使用 - 即无处不在 - 来初始化对象。

4

1 回答 1

59

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

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

此外,Luc Danton 帮助我理解了困难:在 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 重载。由于列表初始化始终依赖于直接初始化,因此用户将看到诊断消息,就好像始终使用列表初始化(统一初始化)一样,因为直接初始化重载不会失败。

我可以看到两个极端案例暴露了这个方案中的空白。当用于 std::initializer_list 的参数满足构造函数时会发生一种情况,例如在上面的示例中尝试插入值 {3, 4}。解决方法是显式指定 std::initializer_list 类型,如 v.emplace_back(std::initializer_list(3, 4))。由于这与推断 std::initializer_list 的语义相匹配,因此这里似乎没有真正的问题。

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

于 2012-01-09T01:25:26.987 回答