我今天遇到了一个相当奇怪的重载解决方案。我把它减少到以下几点:
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}
被视为{{1}的初始化列表构造对象。
但是,我收到一个编译器错误,表明它与C!
的已删除拷贝构造函数匹配S
我可以看看它是如何工作的 - 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
可以被解释为C的有效初始化器,{1, 2}
是1
的初始化器(可隐式构造)从一个int,因为它的构造函数的第二个参数有一个默认值),S
是一个可变参数...但我不明白为什么这将是一个更好的匹配,尤其是看作复制构造函数问题被删除。
有人可以解释一下这里正在使用的重载决策规则,并说明是否有一个解决方法不涉及在构造函数调用中提及S的名称?
编辑:由于有人提到代码段使用不同的编译器进行编译,我应该澄清一下,我在GCC 4.6.1中遇到了上述错误。
编辑2 :我进一步简化了代码片段以获得更令人不安的失败:
2
错误:
struct S
{
S(int, int = 0);
};
struct C
{
C(S);
};
int main()
{
C c({1});
}
这一次,GCC 4.5.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&&)
s和它不会隐式生成的移动构造函数。)
我觉得非常很难相信这就是语言设计师的意图......
答案 0 :(得分:6)
对于C c({1, 2});
,您有两个可以使用的构造函数。因此,重载解析发生并查看要采取的功能
C(S, Args...)
C(const C&)
你发现, Args
将被推断为零。因此,编译器将构造S
与构造C
临时{1, 2}
进行比较。从S
构建{1, 2}
是直截了当的,并将您声明的构造函数S
。从C
构建{1, 2}
也是直截了当的,并采用您的构造函数模板(复制构造函数不可行,因为它只有一个参数,但有两个参数 - 1
和2
- 通过)。这两个转换序列无法比较。因此,如果不是第一个是模板的事实,那么两个构造函数将是模糊的。所以GCC会更喜欢非模板,选择已删除的复制构造函数,并为您提供诊断。
现在,对于C c({1});
测试用例,可以使用三个构造函数
C(S)
C(C const&)
C(C &&)
对于最后两个,编译器会更喜欢第三个,因为它将rvalue绑定到rvalue。但是,如果您考虑C(S)
对C(C&&)
,则不会在两种参数类型之间找到赢家,因为对于C(S)
,您可以从S
构建{1}
对于C(C&&)
,您可以通过C
构造函数从{1}
初始化C(S)
临时值(标准明确禁止用户定义的转换为移动或复制构造函数的参数可用于从C
初始化类{...}
对象,因为这可能会导致不必要的歧义;这就是为什么不考虑将1
转换为C&&
的原因这里只是从1
到S
的转换。但是这一次,与你的第一个测试用例相反,构造函数都不是模板,所以你最终会产生歧义。
这完全是事情的目的。 C ++中的初始化很奇怪,因此让每个人都“直观”地对待每个人都是不可能的。即使是上面的一个简单例子也很快变得复杂。当我写下这个答案时,一小时后我偶然再次看了一遍,我注意到我忽略了一些东西,不得不解决问题。
答案 1 :(得分:4)
在解释为什么它可以从初始化列表中创建C
时,您可能是正确的。 ideone愉快地编译您的示例代码,两个编译器都不正确。假设创建副本有效,但是......
因此从编译器的角度来看,它有两个选择:创建一个新的S{1,2}
并使用模板化构造函数,或者创建一个新的C{1,2}
并使用复制构造函数。通常,非模板函数比模板函数更受欢迎,因此选择了复制构造函数。 然后它会查看是否可以调用该函数...它不能,因此它会发出错误。
SFINAE需要不同类型的错误......它们在第一步中发生,当检查哪些功能可能匹配时。如果简单地创建函数会导致错误,则忽略该错误,并且该函数不被视为可能的过载。在枚举可能的重载之后,关闭此错误抑制并且您仍然坚持使用所获得的内容。