重载调用不明确:一对内联映射作为构造函数参数

Overloaded call is ambiguous: one-pair inline map as constructor argument

我有一个 class——与下面的大致相似——将映射作为其唯一构造函数的唯一参数。

#include <iostream>
#include <map>

using namespace std;

class Dict {
    public:
    Dict (map<int, int> contents) {
        elements = contents;
    }

    int getElement (int i) {
        return elements[i];
    }

    map<int, int> elements;
};

int main() {
    Dict* test0 = new Dict({{1, 2}, {3, 4}});    /* Succeeds */
    Dict* test1 = new Dict({{1, 2}});            /* Fails */
}

正如上面评论中提到的,第一个构造函数不会抛出错误;与this等答案一致。模棱两可的调用错误如下:

main.cpp:43:36: error: call of overloaded 'Dict()' is ambiguous
    Dict* test1 = new Dict({{1, 2}});            /* Fails */
                                ^
main.cpp:16:5: note: candidate: Dict::Dict(std::map)
     Dict (map<int, int> contents) {
     ^
main.cpp:14:7: note: candidate: Dict::Dict(const Dict&)
 class Dict {
       ^
main.cpp:14:7: note: candidate: Dict::Dict(Dict&&)

如果映射中的键和值是不同类型的(例如,如果 Dict() 将整数映射为布尔值,我调用 new Dict({{1, true}})),则不会出现此错误并且代码按预期工作。

这个单一的构造函数怎么会模棱两可呢?为什么在同一类型的两个对象之间存在一个映射的情况下会产生歧义?香草 C++ 中是否有任何明显的解决方法?

这主要是由 std::map 的这个构造函数引起的:

template< class InputIterator >
map( InputIterator first, InputIterator last,
     const Compare& comp = Compare(),
     const Allocator& alloc = Allocator() );

即使参数不是迭代器,也会启用此构造函数,从而参与重载决议。结果,

{1, 2}   -> std::map<int, int>
{{1, 2}} -> std::map<int, int>    

都是有效的转换,也就是说

{{1, 2}} -> Dict
{{1, 2}} -> std::map<int, int> 

都是有效的转换。因此,Dict的三个构造函数是有歧义的:

Dict(map<int, int>);
Dict(const Dict&);
Dict(Dict&&);

对于new Dict({{1, true}})的情况,InputIterator无法正确推导,因此不再有歧义。

您可以明确 Dict(map<int, int>);,或使用 Ben Voigt 建议的三对大括号。


为什么三副牙套有效?

因为在这种情况下,对于 copy/move 候选构造函数,不允许用户定义的转换。这在[over.best.ics]/4中有明确说明(无关部分被我省略):

However, if the target is

  • the first parameter of a constructor or

  • ...

and the constructor or user-defined conversion function is a candidate by

  • ... or

  • the second phase of [over.match.list] when the initializer list has exactly one element that is itself an initializer list, and the target is the first parameter of a constructor of class X, and the conversion is to X or reference to cv X,

user-defined conversion sequences are not considered.