考虑一下代码段:
#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> >]’
使用其他编译器/库实现进行测试:
还有一些令人困惑的问题:
std::unordered_map
替换为std::map
会导致错误消失,foo({})
替换foo({{}})
也会使错误消失。此外,将{}
替换为非空初始值设定项列表在所有情况下均可按预期工作。
所以我的主要问题是:
foo({{}})
的语法是如何使错误消失的呢?编辑修复了一些错别字。
答案 0 :(得分:36)
使用 braced-init-list 代码的间接初始化语法称为 copy-list-initialization 。
在C ++标准的以下部分中描述了为该情况选择最佳可行构造函数的重载解析过程:
§13.3.1.7按列表初始化
初始化[over.match.list]
- 醇>
当非聚合类类型
T
的对象被列表初始化(8.5.4)时,重载决策选择构造函数 分两个阶段:- 最初,候选函数是类
T
的初始化列表构造函数(8.5.4),参数列表由初始化列表作为单个参数组成。- 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类
T
的所有构造函数,参数列表由初始化列表的元素组成。 / p>如果初始化列表没有元素且
T
具有默认构造函数,则省略第一个阶段。在copy-list-initialization中,如果选择了显式构造函数,则初始化是错误的。 [注意:这与其他情况(13.3.1.3,13.3.1.4)不同,其中只考虑转换构造函数进行复制初始化。此限制仅适用于此初始化是重载解析的最终结果的一部分。 - 结束记录]。
根据这个, initializer-list-constructor (一个可以使用与std::initializer_list<T>
类型的构造函数的参数匹配的单个参数调用的那个)通常比其他构造函数更受欢迎,但是,如果默认构造函数可用,则用于列表初始化 的 braced-init-list 为空。
重要的是,由于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的库,错误不期望,因为所选(默认)构造函数不 explicit
,并且代码格式良好:
unordered_map();
因此,您遇到的不同行为仅与您使用不同编译器/编译器选项的libstdc ++和libc ++版本相关。
用
std::unordered_map
替换std::map
会导致错误消失。为什么?的
我怀疑这只是因为您使用的libstdc ++版本中的std::map
已经针对C ++ 14进行了更新。
用
foo({})
替换foo({{}})
也会使错误消失。为什么?的
因为现在这是 copy-list-initialization {{}}
,其中包含非空 braced-init-list (是,它内部有一个元素,用空的 braced-init-list {}
初始化,所以第13.3.1.7节的第一阶段的规则[over.match.list] / p1(之前引用)优先使用 initializer-list-constructor 到其他的。该构造函数不是explicit
,因此调用格式良好。
使用非空初始值设定项列表替换
{}
在所有情况下都按预期工作。为什么?的
同上,重载决议以第13.3.1.7节[over.match.list] / p1的第一阶段结束。
答案 1 :(得分:5)
引用的列表初始化定义如下,[dcl.init.list] / 3:
否则,如果
T
是引用类型,则为该类型的prvalue临时值T
引用的是copy-list-initialized或direct-list-initialized, 取决于参考的初始化类型,以及 引用绑定到那个临时的。
所以你的代码失败了,因为
std::unordered_map<int,int> m = {};
失败。这个案例的列表初始化通过[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());
显然,通过copy-list-initialization调用这个explicit
构造函数是不正确的,[over.match.list]:
在copy-list-initialization中,如果选择了
explicit
构造函数,则初始化是不正确的。
由于C ++ 14 unordered_map
声明了一个非显式的默认构造函数:
unordered_map();
所以C ++ 14标准库实现应该没有问题地编译它。据推测,libc ++已经更新,但libstdc ++已经落后了。
<小时/> 1) [dcl.init] / 7:
值初始化
T
类型的对象意味着:
- 如果T
是 具有用户提供的(可能是cv-qualified)类类型(第9条) 构造函数(12.1),然后调用T
的默认构造函数 [...];
2) [class.ctor] / 4:
类
X
的默认构造函数是类X
的构造函数,可以不带参数调用。