2

I saw this question recently Is returning by rvalue reference more efficient? and also noticed the comments chain. I am following up on the comments chain for the answer there.

People seem to say that in the case below the return value should be by rvalue reference and not by value. (Is returning by rvalue reference more efficient? and Is there any case where a return of a RValue Reference (&&) is useful?) Given the following code

#include <iostream>

using namespace std;

class Something {
public:
    Something() {
        cout << "Something()" << endl;
    }
    Something(const Something&) {
        cout << "Something(const Something&)" << endl;
    }
    Something(Something&&) {
        cout << "Something(Something&&)" << endl;
    }
};

class Maker {
public:
    Something get_something() && {
        return std::move(this->something);
    }
    const Something& get_something() & {
        return this->something;
    }

private:
    Something something;
};

int main() {
    auto maker = Maker{};
    auto something_one = maker.get_something();
    auto something_two = Maker{}.get_something();

    return 0;
}

What is the difference between defining the first method in the class Maker with an rvalue reference return type and a regular value? Running the code above gives me the following output as expected (the move happens in the function call and after the return the move is elided)

Something()
Something(const Something&)
Something()
Something(Something&&)

And when I change the code (i.e. the first method in the Maker class) to return an rvalue reference I still get the same output. Why shouldn't I just return by value in that case?

Community
  • 1
  • 1
Curious
  • 20,870
  • 8
  • 61
  • 146
  • It's not clear what the concern here is. Why do you think you *shouldn't* return by value in that case? – Nicol Bolas Feb 24 '17 at 04:30
  • @NicolBolas updated the question. I am wondering why people seem to say that this is a good use case for returning by rvalue reference. I am struggling to find a good use case for returning by rvalue references.. – Curious Feb 24 '17 at 04:42
  • I see nobody in those questions saying that it "should be" used there. Merely that it *could be* used as such. It all depends on exactly what you're trying to do. – Nicol Bolas Feb 24 '17 at 04:43
  • @NicolBolas is there any use case at all that cannot be better served by returning by value? – Curious Feb 24 '17 at 04:44
  • The other poster was correct; it's more of a duplicate of [Return value or rvalue reference](http://stackoverflow.com/questions/27368236/return-value-or-rvalue-reference?noredirect=1&lq=1) than the one I originally used. – Nicol Bolas Feb 24 '17 at 04:53

1 Answers1

4

When you use this reference qualifiers in this way, you have to ask yourself two questions:

  1. What does std::move(object).funcname() mean?
  2. What does Typename().funcname() mean?

If you return a value from the function, then both of those will mean the same thing. Regardless of what you do to capture the value, the value will be a whole and distinct object, move-constructed from some internal data stored in the object.

In the first case, object now potentially no longer owns the data. In the second case, it doesn't matter because the object was a temporary and has since been destroyed.

If you return a && from the function, then those will mean different things. Namely, #2 will mean "your code is broken".

As to why you might still want to do it, even if it allows broken code. Well, that has to do with the actual answers to that question: what do those things mean?

Here's what I am referring to. std::get is basically a member function of tuple. And yet, if you pass it a tuple&&, you will get a T&& returned and not a T. Why?

Because you're accessing a member of the tuple.

If you had a struct, then std::move(struct_object).x would be an rvalue reference as well. So std::get is simply behaving in the same way for a tuple as member access would for a struct. That's kinda the whole point of tuple, after all: to behave like a struct as much as possible.

This allows you to do things like std::get<0>(std::move(tpl)).member, such that member will still be an rvalue reference. So you can move from a subobject without disturbing the rest of the object, exactly as you could for any other rvalue reference member accesses.

If get returned a value, then this would do something very different. Regardless of what we do with the return value, it will be moved out of the object, no questions asked. So if we only wanted to move a subobject of that member... too bad. The original object lost the entire member, not just a subobject of that member.

Of course, that doesn't change the fact that:

auto &&x = SomeStruct().x; //This extends the temporary's lifetime
auto &&x = std::get<0>(SomeTuple(...)); //This gets a dangling reference.

That is an unfortunate limitation of the language. But if the function is logically a member accessor, then returning a && from a && qualified this function instead of a value is a legitimate choice.

It all depends on what matters more to you: safety or orthogonality with member accessors.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thanks for the answer! One more followup. `std::move(SomeStruct()).x;` I know this is an rvalue, but is it an xvalue or a prvalue? I don't think it is a prvalue since it has a name, but then why is it's lifetime extended? We can never know when to extend the lifetime of an xvalue right? – Curious Feb 24 '17 at 05:38
  • It seems like as per http://en.cppreference.com/w/cpp/language/value_category member accesses on any type of rvalue (xvalues or prvalues) are prvalues. But I still want to sure I didn't misunderstand that ... – Curious Feb 24 '17 at 05:50
  • @Curious: "*member accesses on any type of rvalue are prvalues*" It doesn't say that. Member accesses that always produce prvalues require the `m` to be "a member enumerator or a non-static member function". We're talking about non-static *data* members. If `a` is an rvalue and `m` is an NSDM, then `a.m` is an xvalue, exactly like that website says. – Nicol Bolas Feb 24 '17 at 13:32
  • @Curious: "*why is it's lifetime extended?*" Actually... it isn't. That `std::move` should not have been there. It breaks the ability to extend the lifetime. I've edited the example accordingly. – Nicol Bolas Feb 24 '17 at 13:38
  • Ok thanks! So just to clarify. Not it's a prvalue correct? – Curious Feb 24 '17 at 14:07
  • @Curious: No. It's an xvalue. Just like the website says: "the member of object expression, where a is an rvalue and **m is a non-static data member** of non-reference type". Whether the lifetime of a temporary is extended has nothing to do with the value category of the expression being bound to a reference. – Nicol Bolas Feb 24 '17 at 14:08
  • Sorry. I mean the way it is right now. SomeStruct().x and not the one with std::move() is a prvalue – Curious Feb 24 '17 at 14:10
  • @Curious: I know that's what you meant, and the answer is still the same. `SomeStruct()` is an rvalue, and `x` is a non-static data member of non-reference type. Therefore, `SomeStruct().x` is an *xvalue*. This is not complicated. Objects that have names are not prvalues. – Nicol Bolas Feb 24 '17 at 14:16
  • If it is an xvalue then it shouldn't be extending the lifetime of the temporary because xvalues can have differing dynamic types as compared to their static type. Here it seems obvious that the type of x is the same as its static type since it's not a reference. I'm just trying to reason about which is an xvalue and which is a prvalue – Curious Feb 24 '17 at 14:17
  • ***Whether the lifetime of a temporary is extended has nothing to do with the value category of the expression being bound to a reference.*** What part of that do you not understand? – Nicol Bolas Feb 24 '17 at 14:17
  • I was going off the definition someone posted here http://stackoverflow.com/questions/15482508/what-is-an-example-of-a-difference-in-allowed-usage-or-behavior-between-an-xvalu people seemed to agree on it.. was it wrong? I am talking about the distinction mentioned in the edit of the question and not the answer? And also if the lifetime being extended does not depend on the value category of the thing on the right, then what does it depend on? – Curious Feb 24 '17 at 14:21
  • It seemed reasonable to assume that since xvalues can have differing dyanmic types, their lifetimes are not extended when assigning them to references. Why is this not always true? Sorry I am just having a hard time understanding the concepts behind value categories and lifetime – Curious Feb 24 '17 at 14:23
  • This question http://stackoverflow.com/questions/3716277/do-rvalue-references-allow-dangling-references also makes the connection between prvalues and xvalues affecting when the lifetime of an object is extended. Do you disagree with that? – Curious Feb 24 '17 at 14:51
  • @Curious: No, the OP of that question misinterpreted a statement. If you look at [Pubby's comment](http://stackoverflow.com/questions/15482508/what-is-an-example-of-a-difference-in-allowed-usage-or-behavior-between-an-xvalu#comment21915260_15482508), it does not contradict anything I said. And that other question makes no such connection. – Nicol Bolas Feb 24 '17 at 14:51
  • @Curious: Your problem is that you're generalizing the fact that `auto &&x = std::move(Temporary())` doesn't extend the lifetime, to mean that *all xvalues* don't extend lifetimes. I'm telling you that lifetime extension has ***nothing to do*** with value categories. The reason that the `std::move` version doesn't extend the lifetime of the temporary has nothing to do with the fact that it's an xvalue. – Nicol Bolas Feb 24 '17 at 14:53
  • I understand what you are saying. I am just finding it hard to understand what exactly extends the lifetime of an object. Maybe that is something that should be a different question. I'll phrase another. Thanks for the answer! – Curious Feb 24 '17 at 14:54