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时会出现错误<