今天我遇到了一个相当奇怪的重载决议案例。我将其简化为以下内容:
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 的有效初始化器,1
是 C 的初始化器S
(它可以从 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
和它不会隐式生成的移动构造函数)。
我发现非常很难相信这是语言设计者的意图......
最佳答案
对于 C c({1, 2});
,您有两个可以使用的构造函数。因此发生重载决议并查看要采取的功能
C(S, Args...)
C(const C&)
Args
如您所想,将被推导为零。因此,编译器将构造 S
与从 {1, 2}
构造临时的 C
进行比较。从 {1, 2}
构造 S
非常简单,并采用您声明的 S
构造函数。从 {1, 2}
构造 C
也很直接,并采用构造函数模板(复制构造函数不可行,因为它只有一个参数,但有两个参数 - 1
和 2
- 已通过)。这两个转换序列不可比较。因此,如果第一个构造函数不是模板这一事实,那么这两个构造函数将是模棱两可的。所以 GCC 会更喜欢非模板,选择已删除的复制构造函数,并会给你一个诊断。
现在对于您的 C c({1});
测试用例,可以使用三个构造函数
C(S)
C(C const&)
C(C &&)
对于最后两个,编译器会更喜欢第三个,因为它将右值绑定(bind)到右值。但是,如果您将 C(S)
与 C(C&&)
进行对比,您将无法在这两种参数类型之间找到优胜者,因为对于 C(S)
你可以从 {1}
构造一个 S
并且对于 C(C&&)
你可以初始化一个 C
通过采用 C(S)
构造函数从 {1}
临时获取(标准明确禁止用户定义的移动或复制构造函数参数转换可用于来自 {...}
的类 C
对象的初始化,因为这可能导致不必要的歧义;这就是为什么转换 1
到 C&&
这里不考虑,只考虑从 1
到 S
的转换)。但这一次,与您的第一个测试用例相反,构造函数都不是模板,因此您最终会产生歧义。
这完全是事情的预期工作方式。 C++ 中的初始化很奇怪,因此遗憾的是让每个人都“直观”地了解一切是不可能的。即使是上面的简单示例也会很快变得复杂。当我写下这个答案时,一个小时后我无意中再次查看它时,我发现我忽略了一些东西,不得不修正答案。
关于c++ - C++11 重载决议的奇怪案例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7871172/