10

Considering the following code:

#include <iostream>
using namespace std;

struct I {
    I(I&& rv) { cout << "I::mvcotr" << endl; }
};

struct C {
    I i;
    I&& foo() { return move(i) };
    }
};

int main() {
    C c;
    I i = c.foo();
}

C contains I. And C::foo() allows you to move I out of C. What is the difference between the member function used above:

I&& foo() { return move(i) }; // return rvalue ref

and the following replacement member function:

I foo() { return move(i) }; // return by value

To me, they seem to do the same thing: I i = c.foo(); leads to a call to I::I(I&&);.

What consequences will there be that is not covered in this example?

Benji Mizrahi
  • 2,154
  • 2
  • 23
  • 38
  • 2
    You may notice a difference if you use it as `I&&i = c.foo()`. And you may get into trouble if you call foo on a temporary object. – Marc Glisse May 24 '13 at 11:09

1 Answers1

7

Considerations aside on whether the program you wrote actually makes sense (moving from a data member is awkward - but OK, perhaps there are some use cases), in this case the two versions of that function end up doing the same thing.

As general practice, however, you should prefer returning by value, because in many cases it allows the compiler to perform copy elision and elide the calls to the move constructor of the returned type, as permitted by paragraph 12.8/31 of the C++11 Standard.

Copy elision allows the compiler to create the return value of the function directly in the object which should be initialized from the function's return value.

Therefore, as a general guideline, prefer returning by value.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • @bmm: Does this answer your question? – Andy Prowl May 24 '13 at 10:38
  • The code may not make sense but I gave this example so that I&& C::foo() does not return a local variable which would be a compile-error. I just want to understand in what cases I would need I&& C::foo() and I C::foo() would not be helpful? – Benji Mizrahi May 24 '13 at 10:51
  • @bmm: In 99% of the cases, you do not need `I&& C::foo()` at all. Just return by value. The only useful 1% I can think of are `std::move()` and `std::forward()`, which return rvalue references (actually, the latter may return an lvalue refernence or an rvalue reference depending on the type of the template argument) so that you can get an rvalue from an lvalue. But those are already provided by the Standard Library, so you don't have to write them yourself. – Andy Prowl May 24 '13 at 10:54
  • s/rvalue/rvalue reference/ maybe? We need to be very specific, the question is about the difference between prvalue and xvalue, which are both subcategories of rvalue... – Marc Glisse May 24 '13 at 10:57
  • @MarcGlisse: No, I meant rvalue. If you return an rvalue reference, the value category of the object you return is rvalue. If you return an lvalue reference, the value category is lvalue. That's the trick that enables perfect forwarding through `std::forward<>` – Andy Prowl May 24 '13 at 10:59
  • @AndyProwl: Great! Because the only reason of not asking "Why does C++ even allow us to return-by-rvalue-ref in the first place?" was std::move() and std::forward(). And if there is no other use case of return-by-rvalue-ref, that is great! – Benji Mizrahi May 24 '13 at 11:02
  • 1
    @bmm: Well, in the end one *always* comes up with some exception to the rule or corner use case, so there *might* be some reason one may want to do that, but really, in most case that's just going to make your program more confusing *and* less efficient (because it inhibits copy elision). Btw, if this helped answering your question, please consider marking the answer as accepted :) – Andy Prowl May 24 '13 at 11:04
  • 1
    @AndyProwl: Uh, I now see "rvalue reference" everywhere in the text, either I missed it the first time or it changed. All I was pointing at was that whether you return I or I&&, you are returning an rvalue in both cases (prvalue for I, xvalue for I&&). – Marc Glisse May 24 '13 at 11:05
  • About the copy elision, the version returning by value can indeed elide one move, but it already did one, so it isn't saving anything. – Marc Glisse May 24 '13 at 11:06
  • @MarcGlisse: Yes, you're right. I should edit the answer, thank you. – Andy Prowl May 24 '13 at 11:18