转换运算符相对于复制构造函数的偏好从 C++14 更改为 C++17?

Kar*_*yan 18 c++ conversion-operator language-lawyer c++14 c++17

我正在玩下面的代码。

#include <iostream>

struct To
{
    To() = default;
    To(const struct From&) {std::cout << "Constructor called\n";}
    To(const To&) {std::cout << "Copy constructor called\n";}
};

struct From
{
    operator To(){std::cout << "Conversion called\n"; return To();}
};

int main()
{
    From f;
    To t(f);
    To t1 = f;
    To t2{To(f)};
}
Run Code Online (Sandbox Code Playgroud)

如果我-std=c++14同时使用GCCClang同意以下输出。

Constructor called
Conversion called
Constructor called
Run Code Online (Sandbox Code Playgroud)

但是,如果我使用 进行编译-std=c++17,则两个编译器都同意

Conversion called
Conversion called
Conversion called
Run Code Online (Sandbox Code Playgroud)

我知道将几个预期的输出行减少为输出 3 行是由于复制省略,但我无法弄清楚 C++17 中发生了什么变化导致了此输出。究竟是什么标准的变化引发了这种情况?

Bri*_*ian 11

在所有版本的 C++ 中,类类型对象的直接初始化都会在类的构造函数之间执行重载决策;例如,请参见当前草案中的 [dcl.init.general]/16.6.2。有一些例外,但它们不适用于此处。需要注意的是,转换函数不是初始化的候选函数,因此编译器必须选择是否使用To::To(const To&)To::To(const From&)用于初始化。后者赢得了重载解析,因为参数类型与实参类型完全匹配。

有趣的是,如果To::To(const From&)不存在,那么To::To(const To&)将被调用,并且From将调用对象的转换函数以初始化const To&参数。这不太好,因为它会强制进行复制;由于From::operator To直接创建To对象,因此最好From::operator To直接初始化初始化的目标,而不是创建必须复制到目标的临时对象。不幸的是,当前的标准要求进行复制。这是CWG2327的主题。

委员会尚未决定如何解决 CWG2327,但 Clang 和 GCC 各自对其重载解析规则进行了不同的调整,以便允许在某些情况下删除副本。所以基本上,他们在 C++17 模式下的行为不符合标准,但是是“乐观的”,即实现者希望 CWG2327 的解决方案与他们实现的调整类似。

您可以在此处阅读更多详细信息。本文的更新版本将在下一次邮件中发布,该版本将向 CWG2327 提出正式决议。