GCC(使用4.9测试)接受以下测试用例:
struct Base {};
struct Derived : Base {
Derived();
explicit Derived(const Derived&);
explicit Derived(Derived&&);
explicit Derived(const Base&);
Derived(Base&&);
};
Derived foo() {
Derived result;
return result;
}
int main() {
Derived result = foo();
}
Clang(使用3.5测试)拒绝它,并显示以下错误消息:
test.cpp:13:10: error: no matching constructor for initialization of 'Derived'
return result;
^~~~~~
test.cpp:8:5: note: candidate constructor not viable: no known conversion from 'Derived' to 'Base &&' for 1st argument
Derived(Base&&);
^
test.cpp:4:5: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
Derived();
^
谁是对的?
答案 0 :(得分:14)
我相信Clang在这里是正确的。 GCC不应该接受该代码。
原因是在return
(强调我的)中指定[class.copy] p32
语句中发生的对象副本的构造函数的重载解析方式:
当满足复制/移动构造函数的省略标准时, [...],并且要复制的对象由左值,[...]指定, 重载决策首先选择复制的构造函数 表现为好像该对象是由右值指定的。如果是第一个 重载决议失败或未执行,或者类型为 所选构造函数的第一个参数不是右值 引用对象的类型(可能是cv-qualified),重载 再次执行分辨率,将对象视为左值。
在此示例中,符合elision的条件(通过[class.copy] p31
中的第一个项目符号),并且要复制的对象由左值指定,因此本段适用。
首先尝试重载分辨率,就像对象是由右值指定的一样。 explicit
构造函数不是候选者(请参阅下面的解释原因),因此选择了Derived(Base&&)
构造函数。但是,这属于“所选构造函数的第一个参数的类型不是对象类型的右值引用”(相反,它是对象基类类型的右值引用),因此应该再次执行重载决策,将对象视为左值。
第二个重载决策失败,因为唯一可行的构造函数(同样,explicit
构造函数不是候选者)有一个rvalue引用参数,它不能绑定到左值。 Clang显示导致的重载决策失败错误。
要完成解释,这就是为什么explicit
构造函数不适合重载解析(所有重点都是我的)。
首先,[dcl.init] p15
说:
在a的形式中发生的初始化 brace-or-equal-initializer 或 condition (6.4),以及参数 传递,函数返回,抛出异常(15.1),处理一个 异常(15.3)和聚合成员初始化(8.5.1),是 称为复制初始化 。“
接下来,我们来看看[over.match.ctor] p1
:
对于复制初始化,候选函数所有转换 该类的构造函数(12.3.1)。
最后,我们看到explicit
构造函数未在[class.conv.ctor] p1
中转换构造函数:
构造函数声明而没有函数说明符
explicit
指定从其参数类型到其类类型的转换。这样的构造函数称为 转换 构造 强>