gcc 7.5 上的 C++ 隐式转换和通用转发

C++ implicit conversion and universal forwarding on gcc 7.5

我正在创建一个使用通用转发的库组件。 这在大多数情况下都可以正常工作,但我遇到过这样一种情况,即我们的 linux 构建(看似)错误地使用了复制构造函数而不是移动构造函数。

我能够在 gcc 7.5(与我们的 linux 构建平台相同)的 godbolt 中使用 MVE 重现此内容:

#include <iostream>
using namespace std;

struct A 
{
    A(){}
    A(const A&) = delete;
    A(A&& other){ cout << "move A " << endl; }
};

template<class T>
struct B
{
    template<class U = T>
    B(U&& other)
    : m_a(std::forward<U>(other))
    {
        cout << "forward T into B " << endl;
    }
    T m_a;
};

B<A> foo(A a)
{
    return a;
    //return std::move(a);
}

int main() 
{    
    A a;
    auto b = foo(std::move(a));
}

为了清楚起见,我添加了不可编译的版本来说明问题。

现在,据我所知,在这种情况下我不能使用 (N)RVO,所以移动本身并不是错误的,但我宁愿避免写 return move(...); 当用 gcc 8.1(或相对较新的 MSVC)编译它时,它确实使用了移动构造函数而没有移动。那么这只是一个编译器问题还是我需要改进我的“B”结构来处理这种情况?

显示的程序(使用 return a;)从 C++17 开始就是良构的。因此,您需要一个符合 C++17 标准的编译器来编译它。 GCC 7 中的 C++17 支持是实验性的(C++17 尚未发布)。

自 C++11 以来,使用 return std::move(a); 格式良好,这应该适用于更旧的编译器。


关于使其在 C++17 中工作的不同措辞:

C++14(草案):

[class.copy]

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of t (3.1) he innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object ...

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter)

C++17(草案):

[class.copy.elision]

An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. In the following copy-initialization contexts, a move operation is first considered before attempting a copy operation:

  • If the expression in a return ([stmt.return]) or co_­return ([stmt.return.coroutine]) statement is a (possibly parenthesized) id-expression that names an implicitly movable entity declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or

简而言之,return语句规则中的自动移动而不是从左值复制,过去常常与不适用于函数参数的copy/move省略规则结合使用。现在,它们已经解耦,前者也明确适用于函数参数。