13

I know that when passing an object by value to a function, the move constructor is always called if there is one, assuming no copy elision. What about returning an object by value?

For example, say we have a class Foo which has a move constructor, and we have a function that returns a Foo object.

Foo g() {
    Foo f;

    // do something with f

    return f;
}

If we assume there is no RVO, is the move constructor guaranteed to be called?

Update: I guess I didn't show my intention clearly. I just want to know I can in the worst case have the object moved not copied. Either RVO or NRVO happens, I am happy. And I should also say that move constructor and move assignment are not deleted and are properly implemented.

haotang
  • 155
  • 1
  • 6
  • Yes, local objects in automatic storage are implicitly treated as xvalues in return statements. – ildjarn Jun 18 '12 at 17:45
  • @ildjarn: I think it's only if you return it directly. Somewhere on SO I was corrected from saying this moved `f` into the function: `return do_something(f);`. – GManNickG Jun 18 '12 at 17:47
  • @GManNickG : I didn't have time for the standardese or caveats, hence a comment rather than an answer. I think you're correct. :-] – ildjarn Jun 18 '12 at 17:51
  • @ildjarn: I was actually secretly hoping you'd recorrect me to saying it does actually move it (it makes more sense dammit!). Oh well. :) – GManNickG Jun 18 '12 at 17:54
  • @GManNickG: The exact quote is from 12.8/31: "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) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value". – Mankarse Jun 18 '12 at 17:59
  • Note, that since c++17, it is guaranteed to be elided in many circumstances. See https://stackoverflow.com/questions/38043319/how-does-guaranteed-copy-elision-work – sp2danny May 03 '19 at 09:10

3 Answers3

8

Yes. See [class.copy] p32

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, 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. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. — end note ]

Jonathan Wakely
  • 153,269
  • 21
  • 303
  • 482
2

In this case, since the return value has a name (f), it would be NRVO (named return value optimization) that would apply.

So, the technical answer based only on wording, is that the absence of RVO won't prevent copy elision, since NRVO could still allow it.

Past that, I believe the selection between move/copy of the return value can/will depend on the definition of Foo -- there are definitely times it'll be copied instead of moved, such as if you've explicitly deleted the move constructor and move assignment operators, or you haven't defined move construction/assignment, and it isn't eligible for them being synthesized implicitly.

Edit: [responding to edited question]: Having a move constructor still doesn't guarantee that the result will be moved. One obvious example would be if you had deleted the move assignment operator, and were assigning the result (rather than using it to initialize). In this case, the deleted move assignment operator would prevent moving the return value.

To answer what you may have been getting at, though, the general rule is that moving will be done if possible, and it'll fall back to copying if and only if something prevents the result from being moved.

Jerry Coffin
  • 437,173
  • 71
  • 570
  • 1,035
  • I guess I didn't show my intention clearly in the question. I just want to know I can in the worst case have the object moved not copied. Either RVO or NRVO happens, I am happy. And I should also say that move constructor and move assignment are not deleted. Thanks. – haotang Jun 18 '12 at 18:00
  • 3
    @Jerry: There are two potential copies in that code. The first is from the local variable to the return statement, which is commonly referred as (N)RVO. That is guaranteed not to copy if there is a move constructor, the compiler can apply (N)RVO or not, but if it does not it *must* move construct. The second potential copy is on the caller side, from the returned object, and that falls outside of the scope of the question, as it depends on the caller code, not on the function. – David Rodríguez - dribeas Jun 18 '12 at 18:08
2

The rule is that whenever copy elision is allowed but does not occur, the move constructor will be used if it is available, and otherwise the copy constructor will be used.

The exact behaviour is defined by [class.copy]/32:

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, 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.

Mankarse
  • 37,343
  • 9
  • 88
  • 138