三元运算符中是否强制复制省略(如果允许的话)?

请考虑以下 C++17 代码:

#include <iostream>
#include <optional>

struct S
{
    S(int) { std::cout << "S() "; }
    S(const S &) { std::cout << "S(const S &) "; }
    S(S &&) = delete;
    ~S() { std::cout << "~S() "; }
};

int main() 
{
    [[maybe_unused]] std::optional<S> v = true ? std::optional<S>(1) : std::nullopt;
}

在带有 /std:c++latest 选项 (C++20) 的最新 Visual Studio 2019 16.10.3 中,它会打印

S() S(const S &) ~S() ~S()

即使在具有优化的发布配置中。

即使没有优化,GCC 和 Clang 输出也是不同的(https://gcc.godbolt.org/z/ofGrzhjbc)

S() ~S()

复制省略在这里是可选的(所有编译器都在他们的权利范围内),还是这里不允许复制省略(只有 MSVC 是正确的),或者复制省略在这里是强制性的(只有 GCC 和 Clang 是正确的)?

回答

条件运算符很复杂,我们必须仔细阅读标准才能理解它。参见 [ expr.cond ]。

p4:“否则,如果第二个和第三个操作数具有不同的类型,并且其中一个具有(可能是 cv 限定的)类类型 [...]其他. [...] 如果E2是纯右值 [或 ...] 并且至少一个操作数具有(可能是 cv 限定的)类类型:目标类型是E2在应用左值到右值、数组到指针和函数到指针的标准转换之后。使用这个过程,确定是否可以形成从第二个操作数到为第三个操作数确定的目标类型的隐式转换序列,反之亦然。如果两个序列都可以形成,或者一个序列可以形成但它是二义性转换序列,则程序格式错误。如果无法形成转换序列,则操作数保持不变,并按如下所述执行进一步检查。否则,如果恰好可以形成一个转换序列,则该转换将应用于选定的操作数,并且在本子条款的其余部分中使用转换后的操作数代替原始操作数。

根据 p4,由于std::nullopt_t可隐式转换为std::optional<S>,分析继续假设已完成此类转换(如果选择了第三个操作数)。到非引用目标类型的隐式转换会std::optional<S>产生一个类型为 的纯右值std::optional<S>。因此,对于本节的其余部分,我们假设第二个和第三个操作数都是 prvalues 类型std::optional<S>

p6:“否则,结果是纯右值。[...]”

p7:“左值到右值、数组到指针和函数到指针的标准转换在第二个和第三个操作数上执行。在这些转换之后,以下内容之一应成立:第二个和第三个操作数具有相同类型;结果属于该类型,结果对象使用选定的操作数进行初始化。[...]”

第二个和第三个都已经是纯右值,因此不需要执行左值到右值的转换。它们具有相同的类型,因此结果属于该类型。它是 的右值std::optional<S>

到目前为止,不需要任何移动std::optional<S>。最后, 的初始化v受到保证复制省略的约束,因此那里也不会发生任何移动。相反,来自条件表达式的结果纯右值v作为其结果对象,因此v直接从作为条件表达式结果的纯右值“配方”初始化。

您还没有说明您使用的是哪个版本的 MSVC,但它的行为对我来说似乎很奇怪。显然它没有正确实现 C++17 保证移动省略,所以假设它支持 C++14。但是在 C++14 中,这段代码应该使用移动构造函数,它被删除了;因此,该程序应该是格式错误的。我看不出有任何理由允许它进行复制。

  • @Fedor 好吧,听起来那个版本有一个错误。

以上是三元运算符中是否强制复制省略(如果允许的话)?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>