C++构造函数与相同键/值类型的std::map不明确
这里以类定义为例。
#include <string>
#include <map>
template <class T>
class Collection
{
private:
std::map<std::string, T> data;
public:
Collection() {}
Collection(std::map<std::string, T> d)
{
data = d;
}
};
这在使用ints、chars 甚至vector模板类型初始化集合时工作正常。但是,当用 a 初始化一个string并调用第二个重载的构造函数时,例如:
Collection<std::string> col({
{ "key", "value" }
});
它不会编译,并抛出此退出错误:
main.cpp:24:22: error: call to constructor of 'Collection<std::__cxx11::string>'
(aka 'Collection<basic_string<char> >') is ambiguous
Collection<string> col({
^ ~
main.cpp:8:7: note: candidate constructor (the implicit move constructor)
class Collection
^
main.cpp:8:7: note: candidate constructor (the implicit copy constructor)
main.cpp:16:3: note: candidate constructor
Collection(map<string, T> d)
^
奇怪的是,虽然这种表示法适用于其他类型,但这会中断,但这种表示法适用于string:
Collection<std::string> col(std::map<std::string, std::string>({
{ "key", "value" }
}));
这里发生了什么?
回答
这是一个有趣的。
Amap可以由两个迭代器构造:
template<class InputIterator>
map(InputIterator first, InputIterator last,
const Compare& comp = Compare(), const Allocator& = Allocator());
值得注意的是,这个构造函数根本不需要检查它InputIterator是一个迭代器,更不用说取消引用它的结果可以转换为map的值类型。实际上,尝试构造映射当然会失败,但是重载解析map是可以从任何两个相同类型的参数构造的。
所以与
Collection<std::string> col({
{ "key", "value" }
});
编译器看到两种解释:
- 外大括号
map使用map的初始化列表构造函数初始化 a ,内大括号pair为该初始化列表构造函数初始化 a 。 - 外大括号初始化 a
Collection,内大括号map使用“迭代器对”构造函数初始化 a 。
两者都是排名中用户定义的转换,两者之间没有决胜局,因此调用是模棱两可的 - 即使第二个,如果选择,会导致map构造函数内部某处的错误。
当您也在最外层使用大括号时:
Collection<std::string> col{{
{ "key", "value" }
}};
标准中有一条特殊规则,排除了第二种解释。
- @acikek This part (the brace-hell) is _not_ why I like C++ 🙂 It shouldn't be complicated to initialize but ... there are so many layers of things that need to be handled. Uniform initialization - any day now ....
回答
在这种情况下,您缺少一个包含地图的 {} {{ "key", "value" }}
编辑:抱歉,由于声誉不足,我无法对 TC 的回答发表评论。无论如何,感谢您出色地强调了歧义。
我想补充他们的答案 - 完整说明为什么用 {} 构造不会导致这种歧义,但用 () 构造会导致这种歧义。
花括号初始化和括号初始化之间的主要区别在于,在构造函数重载解析期间,花括号初始化器尽可能与 std::initializer_list 参数匹配,即使其他构造函数提供更好的匹配。这就是使用 {} 构造可以解决歧义的原因。
(摘自 Scott Myers 的 Effective Modern C++ 第 7 条)