9

The following compiles without error:

#include <memory>

std::unique_ptr<int> f() {
    std::unique_ptr<int> x(new int(42));
    return x;
}

int main() {
    std::unique_ptr<int> y = f();
}

I thought that the return value of f() was copy-initialized by x, but std::unique_ptr is a move-only type. How is it that this isn't ill-formed because the copy constructor isn't available? What is the relevant clause in the standard? Is there somewhere that says if f() is a move-only type than a return statement becomes a move construction instead of a copy construction?

sashoalm
  • 63,456
  • 96
  • 348
  • 677
Andrew Tomazos
  • 58,923
  • 32
  • 156
  • 267

1 Answers1

16

I thought that the return value of f() was copy-initialized by x, but std::unique_ptr is a move-only type

The return value of f() is indeed copy-initialized from the expression x, but copy-initialization does not always imply copy-construction. If the expression is an rvalue, then the move constructor will be picked by overload resolution (assuming a move constructor is present).

Now although it is true that the expression x in the return x; statement is an lvalue (which may lead you to think that what I just wrote does not apply), in situations where a named object with automatic storage duration is returned, the compiler shall first try to treat the id-expression as an rvalue for overload resolution.

What is the relevant clause in the standard? Is there somewhere that says if f() is a move-only type than a return statement becomes a move construction instead of a copy construction?

Per paragraph 12.8/32 of the C++ Standard ([class.copy]/32, draft N4296):

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 the 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. [...]

Andy Prowl
  • 114,596
  • 21
  • 355
  • 432
  • .. and therefore the move constructor is eligible, rendering _"the return value of f() was copy-initialized by x"_ false. `` – Lightness Races in Orbit Feb 09 '15 at 13:14
  • Best to say _which_ C++ Standard you're quoting, since there have been several. – Lightness Races in Orbit Feb 09 '15 at 13:15
  • @LightnessRacesinOrbit: It does not make that statement false. AFAIK copy-initialization does not imply copy-construction. Concerning the Standard version, will edit that, thanks. – Andy Prowl Feb 09 '15 at 13:17
  • @AndyProwl: Oh ok well in that case the OP has a misconception (_"the return value of f() was copy-initialized by x, but std::unique_ptr is a move-only type. How is it that this isn't ill-formed because the copy constructor isn't available?"_) so the answer could still use a conclusion. – Lightness Races in Orbit Feb 09 '15 at 13:18
  • @LightnessRacesinOrbit: Agreed, will elaborate. – Andy Prowl Feb 09 '15 at 13:19
  • Yes, normally copy-initialization with an lvalue implies copy construction - but there is this special rule I was unaware of which says to treat it as an rvalue and move construct in this situation. – Andrew Tomazos Feb 09 '15 at 14:57