初始化列表中的隐式转换失败

blu*_*rni 41 c++ gcc libstdc++ c++11

考虑一下片段:

#include <unordered_map>

void foo(const std::unordered_map<int,int> &) {}

int main()
{
        foo({});
}
Run Code Online (Sandbox Code Playgroud)

这与GCC 4.9.2失败并显示以下消息:

map2.cpp:7:19: error: converting to ‘const std::unordered_map<int, int>’ from initializer list would use explicit constructor ‘std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type, const hasher&, const key_equal&, const allocator_type&) [with _Key = int; _Tp = int; _Hash = std::hash<int>; _Pred = std::equal_to<int>; _Alloc = std::allocator<std::pair<const int, int> >; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type = long unsigned int; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::hasher = std::hash<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::key_equal = std::equal_to<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::allocator_type = std::allocator<std::pair<const int, int> >]’
Run Code Online (Sandbox Code Playgroud)

使用其他编译器/库实现进行测试:

  • 海湾合作委员会<4.9接受这个没有抱怨,
  • 带有libstdc ++的Clang 3.5失败并显示类似的消息,
  • 带有libc ++的Clang 3.5接受了这一点,
  • ICC 15.something接受这个(不确定它正在使用哪个标准库).

还有一些令人困惑的要点:

  • 更换std::unordered_mapstd::map使错误消失,
  • 替换foo({})为foo foo({{}})也会使错误消失.

此外,替换{}为非空的初始化程序列表在所有情况下都按预期工作.

所以我的主要问题是:

  • 谁在这?上面的代码是否格式良好?
  • 双花括号的语法foo({{}})究竟是什么使错误消失?

编辑修复了一些拼写错误.

Pio*_*cki 37

使用您的代码正在使用的braced-init-list的间接初始化语法称为copy-list-initialization.

在C++标准的以下部分中描述了为该情况选择最佳可行构造函数的重载解析过程:

§13.3.1.7按列表初始化初始化 [over.match.list]

  1. 当非聚合类类型的对象T被列表初始化(8.5.4)时,重载决策分两个阶段选择构造函数:

    - 最初,候选函数是类的初始化列表构造函数(8.5.4),T参数列表由初始化列表作为单个参数组成.

    - 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类的所有构造函数,T参数列表由初始化列表的元素组成.

如果初始化列表没有元素并且T具有默认构造函数,则省略第一个阶段.在copy-list-initialization中,如果选择了显式构造函数,则初始化是错误的.[ 注意:这与其他情况(13.3.1.3,13.3.1.4)不同,其中只考虑转换构造函数进行复制初始化.此限制仅适用于此初始化是重载解析的最终结果的一部分.- 结束说明 ].

根据的是,一初始化一览构造(可调用具有匹配型的构造的参数的单个参数一个std::initializer_list<T>)通常优选其他构造,但如果默认构造函数是可用的,并且支撑-INIT列表用于对于列表初始化 为空.

这里重要的是,由于LWG问题2193,标准库容器的构造函数集在C++ 11和C++ 14之间发生了变化.如果std::unordered_map为了我们的分析,我们对以下差异感兴趣:

C++ 11:

explicit unordered_map(size_type n = /* impl-defined */,
                     const hasher& hf = hasher(),
                     const key_equal& eql = key_equal(),
                     const allocator_type& alloc = allocator_type());

unordered_map(initializer_list<value_type> il,
            size_type n = /* impl-defined */,
            const hasher& hf = hasher(),
            const key_equal& eql = key_equal(),
            const allocator_type& alloc = allocator_type());
Run Code Online (Sandbox Code Playgroud)

C++ 14:

unordered_map();

explicit unordered_map(size_type n,
                     const hasher& hf = hasher(),
                     const key_equal& eql = key_equal(),
                     const allocator_type& alloc = allocator_type());

unordered_map(initializer_list<value_type> il,
            size_type n = /* impl-defined */,
            const hasher& hf = hasher(),
            const key_equal& eql = key_equal(),
            const allocator_type& alloc = allocator_type());
Run Code Online (Sandbox Code Playgroud)

换句话说,根据语言标准(C++ 11/C++ 14),有一个不同的默认构造函数(可以不带参数调用的构造函数),以及什么是至关重要的,C++中的默认构造函数14现在是非explicit.

引入了这一变化,以便人们可以说:

std::unordered_map<int,int> m = {};
Run Code Online (Sandbox Code Playgroud)

要么:

std::unordered_map<int,int> foo()
{
    return {};
}
Run Code Online (Sandbox Code Playgroud)

它们在语义上等同于您的代码({}作为函数调用的参数传递给initialize std::unordered_map<int,int>).

即,在C++ 11符合的文库的情况下,错误被预期,作为所选择的(默认)构造是explicit,因此该代码形成不良:

explicit unordered_map(size_type n = /* impl-defined */,
                     const hasher& hf = hasher(),
                     const key_equal& eql = key_equal(),
                     const allocator_type& alloc = allocator_type());
Run Code Online (Sandbox Code Playgroud)

如果是符合C++ 14的库,则不会出现错误,因为所选的(默认)构造函数不是 explicit,并且代码格式正确:

unordered_map();
Run Code Online (Sandbox Code Playgroud)

因此,您遇到的不同行为仅与您使用不同编译器/编译器选项的libstdc ++和libc ++版本相关.


更换std::unordered_mapstd::map使错误消失.为什么?

我怀疑这只是因为std::map在你使用的libstdc ++版本中已经为C++ 14更新了.


更换foo({})foo({{}})也使得错误消失.为什么?

因为现在这是带有非空的braced-init-list的复制列表初始化 (也就是说,它内部有一个元素,用空的braced-init-list初始化),所以来自§13.3的第一阶段的规则应用了.1.7 [over.match.list]/p1(之前引用过),它优先使用初始化列表构造函数.那个构造函数不是,因此调用是格式良好的.{{}} {}explicit


{}在所有情况下,替换为非空的初始化列表都可以正常工作.为什么?

与上面相同,重载决策最终以§13.3.1.7[over.match.list]/p1的第一阶段结束.


Col*_*mbo 5

引用的列表初始化定义如下,[dcl.init.list]/3:

否则,如果T是引用类型,则引用类型的prvalue临时值T是copy-list-initialized或direct-list-initialized,具体取决于引用的初始化类型,并且引用绑定到该临时值.

所以你的代码失败了因为

std::unordered_map<int,int> m = {};
Run Code Online (Sandbox Code Playgroud)

失败.这个案例的列表初始化通过[dcl.init.list]/3中的子弹来介绍:

否则,如果初始化列表没有元素并且T是具有默认构造函数的类类型,则对象将进行值初始化.

因此对象的默认构造函数将被调用1.
现在到关键位:在C++ 11中,unordered_map有这个默认构造函数2:

explicit unordered_map(size_type n = /* some value */ ,
                       const hasher& hf = hasher(),
                       const key_equal& eql = key_equal(),
                       const allocator_type& a = allocator_type());
Run Code Online (Sandbox Code Playgroud)

显然,explicit通过copy-list-initialization 调用这个构造函数是不正确的,[over.match.list]:

在copy-list-initialization中,如果explicit选择了构造函数,则初始化是错误的.

由于C++ 14 unordered_map声明了一个非显式的默认构造函数:

unordered_map();
Run Code Online (Sandbox Code Playgroud)

因此,C++ 14标准库实现应该没有问题地编译它.据推测,libc ++已经更新,但libstdc ++已经落后了.


1) [dcl.init]/7:

值初始化类型的对象T是指:
-如果T是(可能CV修饰)类型(第9节)与用户提供的构造(12.1),然后为默认的构造T称为[...];

2) [class.ctor]/4:

类的默认构造函数是类X的构造函数X,可以在没有参数的情况下调用.