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)
使用其他编译器/库实现进行测试:
还有一些令人困惑的要点:
std::unordered_map
用std::map
使错误消失,foo({})
为foo foo({{}})
也会使错误消失.此外,替换{}
为非空的初始化程序列表在所有情况下都按预期工作.
所以我的主要问题是:
foo({{}})
究竟是什么使错误消失?编辑修复了一些拼写错误.
Pio*_*cki 37
使用您的代码正在使用的braced-init-list的间接初始化语法称为copy-list-initialization.
在C++标准的以下部分中描述了为该情况选择最佳可行构造函数的重载解析过程:
§13.3.1.7按列表初始化初始化
[over.match.list]
当非聚合类类型的对象
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_map
用std::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的第一阶段结束.
引用的列表初始化定义如下,[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 ++已经落后了.
到值初始化类型的对象
T
是指:
-如果T
是(可能CV修饰)类型(第9节)与用户提供的构造(12.1),然后为默认的构造T
称为[...];
2) [class.ctor]/4:
类的默认构造函数是类
X
的构造函数X
,可以在没有参数的情况下调用.