return 语句中使用的局部变量不会隐式转换为 r 值以匹配转换运算符



在下面的示例代码段中,return语句中使用的局部变量不会隐式转换为r值以匹配转换运算符。然而,对于移动构造函数,它是有效的。

我想知道这是标准行为还是bug。如果这是一种标准行为,原因是什么?

我在Microsoft Visual Studio 2019(16.8.3版)中以"许可-">模式对其进行了测试,结果产生了编译器错误。但在"允许">模式下,它是可以的。

#include <string>
class X
{
std::string m_str;
public:
X() = default;
X(X&& that)
{
m_str = std::move(that.m_str);
}
operator std::string() &&
{
return std::move(m_str);
}
};
X f()
{
X x;
return x;
}
std::string g()
{
X x;
return x; // Conformance mode: Yes (/permissive-) ==> error C2440: 'return': cannot convert from 'X' to 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>'
//return std::move(x); // OK
// return X{}; // OK
}
int main()
{
f();
g();
return 0;
}

f在C++11标准下工作的原因(链接到一个足够近的草稿)是条款

[class.copy]/32

当满足或将满足省略复制操作的标准时,除了源对象是一个函数参数,要复制的对象由左值指定,重载解析为选择复制的构造函数是第一次执行的,就好像对象是由右值指定的一样。。。

;省略复制操作的criteri[on];在这种情况下相关的是

[class.copy]/31.1

  • 在具有类返回类型的函数中的return语句中,当表达式是与函数返回类型具有相同cv非限定类型的非易失性自动对象(函数或catch子句参数除外)的名称时,可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作

这适用于f,因为return x中的x是";非易失性自动对象的名称。。。具有与函数返回类型"相同的cv不合格类型;;该类型是CCD_ 9。这对g不起作用,因为返回类型std::string不是由x命名的对象的类型X

我认为理解为什么这个规则在这里可能很重要。这个规则并不是关于隐式地将函数局部变量移动到函数返回值的,尽管它实际上是这么说的。这是关于使NRVO成为可能。考虑一下如果没有以下规则,您将不得不为f编写什么:

X f() {
X x;
return std::move(x);
}

但NVRO无法应用,因为您没有返回变量;您正在返回函数调用的结果!所以条款[class.copy]/32是关于让你的代码

X f() {
X x;
return x;
}

在语法上合法,而子句(使用move构造函数)所描述的语义将被忽略(假设您的实现不是太愚蠢),因为我们实际上只是要做NRVO,它不调用任何东西。

您可以看到,实际上,[class.copy]/32没有为g工作。它在f中的目的是使执行复制/移动构造函数成为可能。但是g来执行转换运算符;当您给该语言一个X时,它没有其他合理的方法来提取std::string。所以NVRO不能在g中应用,所以不需要写return x;,所以你可以只写

std::string g() {
X x;
return std::move(x);
}

并且不必担心这将导致错过优化。

我们看到,C++11规则[class.copy]/32的设计使其影响可能的最小部分情况。它适用于那些我们会NVRO但没有复制构造函数的情况,并通过告诉我们假装我们会调用移动构造函数使NVRO成为可能。但当真正编写代码时,这意味着要记住一条令人费解的规则:";如果返回类型与变量的类型相同,则为return the_variable;,否则为return std::move(the_variable)"这就是为什么C++20标准将[class.copy]/32完全改写为

[class.copy.elision]/3

隐式可移动实体是自动存储持续时间的变量,它是非易失性对象或对非易失对象类型的右值引用。在以下复制初始化上下文中,在尝试复制操作之前,首先考虑移动操作:

  • 如果return([stmt.return])或co_­return([stmt.return.coroutine])语句中的表达式是一个(可能带括号)的id表达式数声明子句中声明的隐式可移动实体,或

选择副本构造函数的重载解析或要调用的return_­value重载首先执行,就好像表达式或操作数是右值一样。。。

不要求返回类型与隐式移动的变量类型相同;它可以概括为概念上更简单的规则";返回一个变量尝试移动,然后尝试复制";。这导致了概念上更简单的原理";当从函数返回变量时,只要return the_variable;,它就会做正确的事情;。(当然,GCC、Clang、MSVC的似乎都没有收到备忘录。这一定是某种记录…)

最新更新