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

标签 c++ c++11 gcc libstdc++

考虑一下片段:

#include <unordered_map>

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

int main()
{
        foo({});
}

使用 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> >]’

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

  • GCC < 4.9 毫无怨言地接受这一点,
  • 带有 libstdc++ 的 Clang 3.5 失败并显示类似消息,
  • 带有 libc++ 的 Clang 3.5 接受这一点,
  • ICC 15.something 接受这个(不确定它使用的是哪个标准库)。

还有几个令人困惑的地方:

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

此外,将 {} 替换为非空初始化程序列表在所有情况下都可以正常工作。

所以我的主要问题是:

  • 谁在这里?上面的代码格式正确吗?
  • 双花括号 foo({{}}) 的语法究竟做了什么来消除错误?

EDIT 修正了几个错别字。

最佳答案

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

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

§ 13.3.1.7 Initialization by list-initialization [over.match.list]

  1. When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

    — Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.

    — If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

If the initializer list has no elements and T has a default constructor, the first phase is omitted. In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed. [ Note: This differs from other situations (13.3.1.3, 13.3.1.4), where only converting constructors are considered for copy-initialization. This restriction only applies if this initialization is part of the final result of overload resolution. — end note ].

据此,initializer-list-constructor(具有与构造函数类型 std::initializer_list<T> 的构造函数参数匹配的单个参数的可调用函数)通常优于其他构造函数,但不是如果默认构造函数可用,并且用于list-initializationbraced-init-list为空。 p>

这里重要的是,由于 LWG issue 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());

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());

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

引入这种变化是为了让人们可以说:

std::unordered_map<int,int> m = {};

或:

std::unordered_map<int,int> foo()
{
    return {};
}

它们在语义上都等同于您的代码(传递 {} 作为函数调用的参数来初始化 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());

对于符合 C++14 的库,错误是不期望,因为选择的(默认)构造函数是 not explicit ,并且代码格式正确:

unordered_map();

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


Replacing std::unordered_map with std::map makes the error go away. Why?

我怀疑这只是因为 std::map在您使用的 libstdc++ 版本中,已经针对 C++14 进行了更新。


Replacing foo({}) with foo({{}}) also makes the error go away. Why?

因为现在这是复制列表初始化 {{}} non-empty braced-init-list(也就是说,它内部有一个元素,用空的 braced-init-list 初始化{} ),因此应用了第 13.3.1.7 节 [over.match.list]/p1(之前引用)的第一阶段的规则,该规则优先于其他的 initializer-list-constructor .该构造函数不是 explicit ,因此调用是格式正确的


Replacing {} with a non-empty initializer list works as expected in all cases. Why?

与上述相同,重载解决方案以 § 13.3.1.7 [over.match.list]/p1 的第一阶段结束。

关于c++ - 初始化列表中的隐式转换失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26947704/

相关文章:

c++ - 乘以 __int64

c++ - 传递 std::plus<>() 作为谓词

c++ - 使用智能指针变得聪明 : avoiding shared_ptr overuse

gcc - 除了 gcc 之外还有哪些编译器可以向量化代码?

c++ - Windows 上不会发生的 Linux 上的隐形 SIGSEGV?

c++ - 在单独的线程中调用 boost::python::object 作为函数

c++ - 成员函数与友元函数 : why one and not the other?

c++ - 为什么我们对 fcgi 应用程序使用 cout 和 cin?

c++ - 不使用 std::function 将捕获的 lambda 转换为函数指针

c++ - ISO-10646 XFont 编码问题