c++ - C++11 重载决议的奇怪案例

标签 c++ c++11 overload-resolution

今天我遇到了一个相当奇怪的重载决议案例。我将其简化为以下内容:

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 也很直接,并采用构造函数模板(复制构造函数不可行,因为它只有一个参数,但有两个参数 - 12 - 已通过)。这两个转换序列不可比较。因此,如果第一个构造函数不是模板这一事实,那么这两个构造函数将是模棱两可的。所以 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 对象的初始化,因为这可能导致不必要的歧义;这就是为什么转换 1C&& 这里不考虑,只考虑从 1S 的转换)。但这一次,与您的第一个测试用例相反,构造函数都不是模板,因此您最终会产生歧义。

这完全是事情的预期工作方式。 C++ 中的初始化很奇怪,因此遗憾的是让每个人都“直观”地了解一切是不可能的。即使是上面的简单示例也会很快变得复杂。当我写下这个答案时,一个小时后我无意中再次查看它时,我发现我忽略了一些东西,不得不修正答案。

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

相关文章:

c++ - 如何在不更改的情况下检索当前的 terminate() 处理程序?

c++ - 为什么我可以阻止基元的隐式转换而不是用户定义的类型?

c# - 如何在 VB.NET 中编写 [DefaultValue(null)]? <DefaultValue(Nothing)> 不编译

c# - 重载的方法组参数会混淆重载的解析?

c++ - 静态变量会阻碍数据缓存吗?

c++ - 表达式f()>g()的值,当f&g修改同一个全局变量undefined或unspecified时?

c++ - 显示 opencv 矩阵时出现内存错误

c++ - 为什么可变参数模板函数会得到 "no matching function for call to .."?

c++ - numeric_limits 最低和最低成员函数

c++ - Clang (OS X) 在特定的嵌套声明中需要 "template"关键字,而 VS 禁止它