今天,我遇到了一个非常奇怪的重载解决方案案例。我将其简化为以下内容:

struct S
{
    S(int, int = 0);
};

class C
{
public:
    template <typename... Args>
    C(S, Args... args);

    C(const C&) = delete;
};

int main()
{
    C c({1, 2});
}

我完全希望C c({1, 2})C的第一个构造函数匹配,可变参数的数量为零,并且{1, 2}被视为S对象的初始化列表构造。

但是,我收到一个编译器错误,表明它与已删除的C拷贝构造函数匹配!
test.cpp: In function 'int main()':
test.cpp:17:15: error: use of deleted function 'C(const C &)'
test.cpp:12:5: error: declared here

我可以看到它是如何工作的-{1, 2}可以解释为C的有效初始值设定项,1S的初始值设定项(可以从int隐式构造,因为其构造函数的第二个参数具有默认值) ,并且2是一个可变参数...但是我不明白为什么这会是更好的匹配,尤其是看到有问题的拷贝构造函数被删除时。

有人可以解释这里使用的重载解析规则,并说是否有一种解决方法不涉及在构造函数调用中提及S的名称吗?

编辑:由于有人提到该代码段使用其他编译器进行编译,因此我应澄清在GCC 4.6.1中出现上述错误。

编辑2 :我进一步简化了代码段,以得到更令人不安的故障:
struct S
{
    S(int, int = 0);
};

struct C
{
    C(S);
};

int main()
{
    C c({1});
}

错误:
test.cpp: In function 'int main()':
test.cpp:13:12: error: call of overloaded 'C(<brace-enclosed initializer list>)' is ambiguous
test.cpp:13:12: note: candidates are:
test.cpp:8:5: note: C::C(S)
test.cpp:6:8: note: constexpr C::C(const C&)
test.cpp:6:8: note: constexpr C::C(C&&)

这次,GCC 4.5.1也给出了相同的错误(减去constexpr和它不会隐式生成的move构造函数)。

我很难相信这就是语言设计师的意图。

最佳答案

对于C c({1, 2});,您可以使用两个构造函数。因此发生了重载解析,并看起来要执行什么功能

C(S, Args...)
C(const C&)

如您所知,Args将被推为零。因此,编译器将构造S与从C构造临时{1, 2}进行比较。从S构造{1, 2}很简单,并采用了您声明的S构造函数。从C构造{1, 2}也是很简单的,并采用您的构造函数模板(复制构造函数不可行,因为它只有一个参数,但传递了两个参数12-)。这两个转换序列不可比较。因此,如果不是第一个是模板这一事实,那么这两个构造函数将是模棱两可的。因此,GCC将选择非模板,选择已删除的拷贝构造函数,然后进行诊断。

现在对于C c({1});测试用例,可以使用三个构造函数
C(S)
C(C const&)
C(C &&)

对于最后两个,编译器将首选第三个,因为它将右值绑定(bind)到右值。但是,如果您考虑将C(S)C(C&&)相对,则不会在这两个参数类型之间找到赢家,因为对于C(S)您可以从S构造{1},对于C(C&&)您可以通过采用C构造函数从{1}临时初始化C(S)明确禁止用户定义的移动或复制构造函数的参数转换可用于从C初始化类{...}对象,因为这可能会导致不必要的歧义;这就是为什么此处不考虑将1转换为C&&的原因,仅从1S的转换)。但是这次,与您的第一个测试用例相反,这两个构造函数都不是模板,因此您最终会感到模棱两可。

这完全是事情的原意。 C++中的初始化很奇怪,因此让所有人“直观”地进行所有操作将是可悲的。甚至上面的一个简单示例也很快变得复杂。当我写下这个答案时,一个小时后,我无意中再次看了一下它,我发现我忽略了某些东西,不得不修正答案。

关于c++ - C++ 11重载解析的奇怪情况,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/7871172/

10-13 03:00