C++ return语句中使用的局部变量不';t隐式转换为r值以匹配转换运算符
在下面的示例代码片段中,C++ return语句中使用的局部变量不';t隐式转换为r值以匹配转换运算符,c++,c++11,visual-c++,move-semantics,standards-compliance,C++,C++11,Visual C++,Move Semantics,Standards Compliance,在下面的示例代码片段中,return语句中使用的局部变量不会隐式转换为r值以匹配转换运算符。然而,对于move构造函数,它是有效的 我想知道这是标准行为还是bug。如果这是一种标准行为,原因是什么 我在Microsoft Visual Studio 2019(版本16.8.3)中以的“许可-模式对其进行了测试,结果产生了一个编译器错误。但在“许可”模式下,一切正常 #include <string> class X { std::string m_str; public:
return
语句中使用的局部变量不会隐式转换为r值以匹配转换运算符。然而,对于move构造函数,它是有效的
我想知道这是标准行为还是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;
}
#包括
X类
{
std::字符串m_str;
公众:
X()=默认值;
X(X&&that)
{
m_str=std::move(即.m_str);
}
运算符std::string()&&
{
返回std::move(mustr);
}
};
xf()
{
X;
返回x;
}
std::string g()
{
X;
return x;//一致性模式:Yes(/permissive-==>错误C2440:“return”:无法从“x”转换为“std::basic_string”
//返回std::move(x);//确定
//返回X{};//确定
}
int main()
{
f();
g();
返回0;
}
本条款是f
在(链接到足够接近的草稿)下工作的原因
[class.copy]/32
当满足或将满足省略复制操作的条件时,除了源
对象是函数参数,要复制的对象由左值指定,重载分辨率为
选择首先执行复制的构造函数,就像对象由右值指定一样
与本例相关的“省略复制操作的标准[on]”是
[class.copy]/31.1
- 在具有类返回类型的函数中的
return
语句中,当表达式是与函数返回类型具有相同cv非限定类型的非易失性自动对象(函数或catch子句参数除外)的名称时,通过将自动对象直接构造到函数的返回值中,可以省略复制/移动操作
这适用于f
,因为return x
中的x
是“非易失性自动对象的名称…具有与函数返回类型相同的cv非限定类型”;该类型为X
。这不适用于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
中应用,因此无需写入返回x代码>,因此您可以只编写
std::string g() {
X x;
return std::move(x);
}
不要担心这会导致错过优化
我们看到,C++11规则[class.copy]/32
的设计是为了尽可能减少对案例的影响。它适用于我们想要NVRO但没有复制构造函数的情况,并通过告诉我们假装调用移动构造函数使NVRO成为可能。但在实际编写代码时,这意味着要记住一条令人费解的规则:“为了最小化复制/移动,如果返回类型与变量类型相同,返回_变量;
,否则返回std::move(_变量)
”,这就是为什么C++20标准完全重新表述[class.copy]/32进入
隐式可移动实体是自动存储持续时间的变量,它是非易失性对象或对非易失性对象类型的右值引用。在以下复制初始化上下文中,在尝试复制操作之前,首先考虑移动操作:
- 如果
return
([stmt.return]
)或co\ureturn
([stmt.return.coroutine]
)语句中的表达式是一个id表达式(可能带括号),该表达式命名在最内层封闭函数或lambda表达式的主体或参数声明子句中声明的隐式可移动实体,或
首先执行重载解析以选择副本的构造函数或调用的返回值重载,就像表达式或操作数是右值一样
这并不要求返回类型与隐式移动的变量类型相同;它可以概括为概念上更简单的规则“返回变量尝试移动,然后尝试复制”。这导致了概念上更简单的原则“当从函数返回变量时,只要返回_变量;
,它就会做正确的事情”。(当然,没有GCC、Clang或MSVC。这必须是某种记录…从中可以看出,Clang比GCC更严格。我自己认为返回x时会出现错误<