简体   繁体   中英

C++ implicit conversion and universal forwarding on gcc 7.5

I am creating a library component that uses universal forwarding. This works fine in most cases but I have encountered a case where our linux build (seeminingly) incorrectly uses a copy constructor instead of a move constructor.

I was able to reproduce this in godbolt on gcc 7.5 (same as our linux build platform) with a 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));
}

For clarity I added the non compilable version to illustrate the problem.

Now, as far as I know I can't use (N)RVO in this case so a move is not inherently wrong but I'd rather avoid writing return move(...); When compiling this with gcc 8.1 (or a relatively recent MSVC) it does use the move constructor without the move. So is this just a compiler problem or is there something I need to improve on my "B" struct to handle this case?

The shown program (using return a; ) is well-formed since C++17. As such, you need a C++17 conforming compiler to compile it. C++17 support in GCC 7 was experimental (C++17 hadn't been released yet).

Using return std::move(a); is well-formed since C++11, and such should work with much older compilers.


Regarding the different wording that makes it work in C++17:

C++14 (draft):

[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 (draft):

[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

In short, the automatic move instead of copy from lvalue in return statement rule used to be coupled with the copy/move elision rule which doesn't apply to function parameters. Now, they have been decoupled and the former applies explicitly to function parameters as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM