C++ 11重载决议的奇怪情况

HC4*_*ica 9 c++ overload-resolution c++11

我今天遇到了一个相当奇怪的重载解决方案.我把它减少到以下几点:

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});
}
Run Code Online (Sandbox Code Playgroud)

我完全希望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
Run Code Online (Sandbox Code Playgroud)

我可以看看它是如何工作的 - {1, 2}可以被解释为C的有效初始化器,它1是一个初始化器S(可以从int隐式构造,因为它的构造函数的第二个参数有一个默认值),以及2作为一个可变参数...但我不明白为什么那将是一个更好的匹配,尤其是看到有问题的复制构造函数被删除.

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

编辑:由于有人提到代码片段使用不同的编译器编译,我应该澄清我在GCC 4.6.1中遇到了上述错误.

编辑2:我进一步简化了片段以获得更令人不安的失败:

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

struct C
{
    C(S);
};

int main()
{
    C c({1});
}
Run Code Online (Sandbox Code Playgroud)

错误:

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&&)
Run Code Online (Sandbox Code Playgroud)

而这一次,GCC 4.5.1也给出了相同的错误(减去constexprs和它不会隐式生成的移动构造函数).

我觉得很难相信,这是语言的设计者何意?

Joh*_*itb 6

因为C c({1, 2});你有两个可以使用的构造函数.因此,重载解析发生并查看要采取的功能

C(S, Args...)
C(const C&)
Run Code Online (Sandbox Code Playgroud)

Args当你想出来时,它会被推断为零.因此编译器将构造S与构造C临时的out进行比较{1, 2}.构造Sfrom {1, 2}是直接的,并采取你声明的构造函数S.构造Cfrom {1, 2}也是直接的并且采用你的构造函数模板(复制构造函数不可行,因为它只有一个参数,但是传递了两个参数 - 12- ).这两个转换序列无法比较.因此,如果不是第一个是模板的事实,那么两个构造函数将是模糊的.所以GCC会更喜欢非模板,选择已删除的复制构造函数,并为您提供诊断.

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

C(S)
C(C const&)
C(C &&)
Run Code Online (Sandbox Code Playgroud)

对于最后两个,编译器将更喜欢第三个,因为它将rvalue绑定到rvalue.但是如果你考虑C(S)反对,C(C&&)你就不会在两种参数类型之间找到胜利者,因为C(S)你可以构造一个Sfrom {1}和for,C(C&&)你可以通过采用构造函数初始化一个C临时(标准明确禁止用户定义的转换参数为移动或复制构造函数可用于初始化类对象,因为这可能导致不必要的歧义;这就是为什么不在此处考虑转换为to 而只考虑转换为to的原因.但是这次,与你的第一个测试用例相反,构造函数都不是模板,所以你最终会产生歧义.{1}C(S)C{...}1C&&1S

这完全是事情的目的.C++中的初始化很奇怪,因此让每个人都"直观"地对待每个人都是不可能的.即使是上面的一个简单例子也很快变得复杂.当我写下这个答案时,一小时后我偶然看了一遍,我注意到我忽略了一些东西而不得不修复答案.