简体   繁体   中英

Move-only type returned into converting constructor

The following code returns a move-only type that should then be converted to another type by a converting constructor.

#include <utility>

class Foo
{
public:
  Foo() {}
  Foo(const Foo&) = delete;
  Foo(Foo&&) = default;
};

class Other
{
public:
  Other(Foo foo) {}
};


Other moo()
{
  Foo foo;
  return foo;
}

int main()
{
  moo();
}

This threw me an error with my compiler and could only be fixed by adding std::move to the return statement which is considered bad practice, because in general it prevents return value optimization. Shouldn't the identifier of a return statement be treated as rvalue first to satisfy conversions?

Is this code valid and which compiler is right here?

Shouldn't the identifier of a return statement be treated as rvalue first to satisfy conversions?

Yes and no. From [class.copy] , as a result of CWG 1579 (the wording here is copied from C++17, although it's the same in C++14 . I find the bullets easier to read than the earlier grammar choice that would make James Joyce blush... ):

In the following copy-initialization contexts, a move operation might be used instead of a copy operation:

  • If 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 the innermost enclosing function or lambda-expression, or
  • [...]

overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue.

The first bullet applies here, so we first do overload resolution as if foo was an rvalue. This gets is to the Other(Foo ) constructor by way of Foo(Foo&& ) .

The first parameter of Other(Foo ) is not an rvalue reference, so we should do overload resolution again considering the foo as an lvalue, which fails. This seems like an unnecessary restriction, but I'd call clang correct here. If you change the constructor to Other(Foo&& ) , clang accepts it.

Barry's fine answer covers the standard rules, but I have a practical suggestion:

and could only be fixed by adding std::move to the return statement which is considered bad practice, because in general it prevents return value optimization.

Your worry is unjustified. NRVO does not apply to conversions anyway, and RVO on the result of the move is allowed. The explicit move is fine.

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