gcc和clang之间的重载分辨率差异涉及移动构造函数和'Derived(Base &&)'构造函数

HC4*_*ica 17 c++ gcc clang overload-resolution c++11

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

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

谁是对的?

HC4*_*ica 14

我相信Clang在这里是正确的.海湾合作委员会不应接受该守则.

原因是return[class.copy] p32(强调我的)中指定了语句中发生的对象副本的构造函数的重载解析:

当满足复制/移动构造函数的省略标准时,[...]和要复制的对象由左值,[...]指定,首先执行重载的选择构造函数的重载决策好像对象是由右值指定的.如果第一个重载决策失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值.

在此示例中,符合elision的条件(通过第一个项目符号[class.copy] p31),并且要复制的对象由左值指定,因此本段适用.

首先尝试重载分辨率,就像对象由右值指定一样.该explicit构造函数不是候选人(见下文的原因的解释),所以Derived(Base&&)被选择的构造.但是,这属于"所选构造函数的第一个参数的类型不是对象类型的右值引用"(相反,它是对象基类类型的右值引用),因此应该再次执行重载决策,将对象视为左值.

第二个重载决策失败,因为唯一可行的构造函数(同样,explicit构造函数不是候选者)具有rvalue引用参数,该参数不能绑定到左值.Clang显示导致的重载决策失败错误.


为了完成解释,这就是为什么explicit构造函数不适合重载解析(所有重点都是我的).

首先,[dcl.init] p15说:

初始化发生在一个 括号或等于初始值条件(6.4)的形式,以及参数传递,函数返回,抛出异常(15.1),处理异常(15.3)和聚合成员初始化(8.5.1),称为复制初始化."

接下来,我们来看看[over.match.ctor] p1:

对于复制初始化,候选函数是该类的所有转换构造函数(12.3.1).

最后,我们看到explicit构造函数没有转换构造函数[class.conv.ctor] p1:

声明没有函数说明 explicit符的构造函数指定从其参数类型到其类类型的转换.这样的构造函数称为转换构造函数.