29

What would be a valid use case for a signature like this?:

T&& foo();

Or is the rvalue ref only intended for use as argument?

How would one use a function like this?

T&& t = foo(); // is this a thing? And when would t get destructed?
Boann
  • 48,794
  • 16
  • 117
  • 146
Martin B.
  • 1,567
  • 14
  • 26
  • 6
    [`std::move`](https://en.cppreference.com/w/cpp/utility/move) comes to mind. It certainly returns `T&&`. Edit : [`std::optional::value`](https://en.cppreference.com/w/cpp/utility/optional/value) also has an `T&&` overload. Edit 2 : It also has a `const T &&` overload, though I'll admit I don't understand the meaning. – François Andrieux May 02 '19 at 19:32
  • 1
    @FrançoisAndrieux the `std::get` family of functions, too. – Brian Bi May 02 '19 at 19:34
  • 1
    See [this answer](https://stackoverflow.com/a/1116763/9593596). – lubgr May 02 '19 at 19:37
  • Isn't that a forwarding reference? – user2357112 May 03 '19 at 01:52

3 Answers3

25

For a free function it doesn't make much sense to return a rvalue reference. If it is a non-static local object then you never want to return a reference or pointer to it because it will be destroyed after the function returns. It can possibly make sense to return a rvalue reference to an object that you passed to the function though. It really depends on the use case for if it makes sense or not.

One thing that can greatly benefit from returning an rvalue reference is a member function of a temporary object. Lets say you have

class foo
{
    std::vector<int> bar;
public:
    foo(int n) : bar(n) {}
    std::vector<int>& get_vec() { return bar; }
};

If you do

auto vec = foo(10).get_vec();

you have to copy because get_vec returns an lvalue. If you instead use

class foo
{
    std::vector<int> bar;
public:
    foo(int n) : bar(n) {}
    std::vector<int>& get_vec() & { return bar; }
    std::vector<int>&& get_vec() && { return std::move(bar); }
};

Then vec would be able to move the vector returned by get_vec and you save yourself an expensive copy operation.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • @FrançoisAndrieux That's covered by my *It can possibly make sense to return a rvalue reference to an object that you passed to the function though. It really depends on the use case for if it makes sense or not.* catch all. I know there are cases but I really didn't want to try and list them all. – NathanOliver May 02 '19 at 19:37
  • Is there a reason to prefer returning by rvalue-ref compared to returning by value here? – super May 02 '19 at 19:38
  • @super I take it your talking about the `get_vec` case? If you return by value you incur 2 move operations. Passing by rvalue reference you only have 1 move. – NathanOliver May 02 '19 at 19:39
  • But in `get_vec()&&` you're returning an rvalue reference to what might be a temporary object (e.g. if used with a materialized prvalue). It would be much safer (and is recommended) to return by value. Any decent compiler will elide the extraneous constructor calls. – Cruz Jean May 02 '19 at 19:53
  • @CruzJean You can only call `get_vec() &&` on a temporary object so it's safe to call it. – NathanOliver May 02 '19 at 20:01
  • 1
    @NathanOliver Well, you can call it on any rvalue (e.g. `std::move(a).get_vec()`). My point is that you're potentially returning a reference to an object that's about to be destroyed. It's the same problem as returning a reference to a local function variable. – Cruz Jean May 02 '19 at 20:03
  • 5
    `std::vector get_vec() && { return std::move(bar); }` in this case ends up being better 999/1000. Can you come up with a better example? – Yakk - Adam Nevraumont May 02 '19 at 20:08
  • @MiljenMikic Those ampersands were not redundant. If they are removed it makes the code pointless. – NathanOliver May 13 '19 at 15:36
  • @NathanOliver Notice what @Yakk wrote, `std::vector get_vec() &&`, not `std::vector&& get_vec() &&`. – Miljen Mikic May 13 '19 at 15:38
  • @MiljenMikic I've noted and left it as is. I can't come up with a better example so I'm leaving it as is. If you compile the code pre C++17, with copy elision turned off, it will make a difference. It's pretty much moot now but it is still a valid example. – NathanOliver May 13 '19 at 15:40
  • @NathanOliver Yup, in that case it will make a difference. Fair enough. – Miljen Mikic May 13 '19 at 16:02
  • @NathanOliver, I have just ran in real troubles with `std::vector&& get_vec() &&`. My code is: `auto &&v = std::move(a).get_vec();` `auto &&` should be always safe but in this case it will capture only reference and does not prolong lifetime of `a`. See e.g. https://stackoverflow.com/questions/10190677/will-a-reference-bound-to-a-function-parameter-prolong-the-lifetime-of-that-temp – Jarek C Sep 09 '22 at 12:28
  • 1
    @JarekC That question asks about temporaries. You don't have a temporary though, you have an lvalue. Also, no move actually happens here, `a` still exists, so `v` is still a valid reference to `a`s internal vector. – NathanOliver Sep 09 '22 at 12:31
  • 1
    @JarekC This overload is really only intended to be used like `auto bar = foo{42}.get_vec();` – NathanOliver Sep 09 '22 at 12:33
  • Don't want to make this list too long, @NathanOliver is right, my "failing" case is more similar to `auto &&bar = foo{42}.get_vec();` than to my example with `move` above. – Jarek C Sep 09 '22 at 12:37
  • @CruzJean, similar comment as to the answer below: If we "should not" return `&&`, why e.g. `std::optional` is doing exactly this in `operator*()`? – Jarek C Sep 09 '22 at 12:38
  • @JarekC It is to give you a way to extract the value from the optional without having to make a copy. You can do something like `std::optional bar() { ... } auto foo = *bar()` and now the wrapped `expensive` object is moved instead of copied. – NathanOliver Sep 09 '22 at 12:44
  • If `std::optional::operator*` was `T operator*() &&`, then `auto foo = *bar()` would do two moves (possibly optimizable), still no copy. – Jarek C Sep 09 '22 at 13:09
  • @JarekC Two moves is still twice the work. Once of C++ core tenents is high performance. – NathanOliver Sep 09 '22 at 13:11
6
T&& t = foo(); // is this a thing? And when would t get destructed?

An rvalue reference is really similar to a lvalue reference. Think about your example like it was normal references:

T& foo();

T& t = foo(); // when is t destroyed?

The answer is that t is still valid to use as long as the object is refers to lives.

The same answer still applies to you rvalue reference example.


But... does it make sense to return an rvalue reference?

Sometimes, yes. But very rarely.

consider this:

std::vector<int> v = ...;

// type is std::tuple<std::vector<int>&&>
auto parameters = std::forward_as_tuple(std::move(v));

// fwd is a rvalue reference since std::get returns one.
// fwd is valid as long as v is.
decltype(auto) fwd = std::get<0>(std::move(parameters));

// useful for calling function in generic context without copying
consume(std::get<0>(std::move(parameters)));

So yes there are example. Here, another interesting one:

struct wrapper {

    auto operator*() & -> Heavy& {
        return heavy;
    }

    auto operator*() && -> Heavy&& {
        return std::move(heavy);
    }

private:
    Heavy instance;
};

// by value
void use_heavy(Heavy);

// since the wrapper is a temporary, the
// Heavy contained will be a temporary too. 
use_heavy(*make_wrapper());
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • 1
    In the last example, if the `wrapper` instance you use this on is a temporary, `operator*()` returns a reference to `instance`, which is likewise a temporary. So after the function returns you have a reference to an object whose lifetime has ended (undefined behavior). – Cruz Jean May 02 '19 at 20:51
  • Are you sure you don't need `decltype(auto) fwd = std::get<0>(std::move(parameters));`? – Yakk - Adam Nevraumont May 13 '19 at 15:47
  • @CruzJean, I agree with you but on the other hand, I really don't understand why e.g. `T&& std::optional::operator*() &&` is defined to behave exactly as proposed by @GuillaumeRacicot. I would expect `T std::optional::operator*() &&` but it is not this way... – Jarek C Sep 09 '22 at 12:34
1

I think a use case would be to explicitly give permission to "empty" some non-local variable. Perhaps something like this:

class Logger
{
public:
    void log(const char* msg){
        logs.append(msg);
    }
    std::vector<std::string>&& dumpLogs(){
        return std::move(logs);
    }
private:
    std::vector<std::string> logs;
};

But I admit I made this up now, I never actually used it and it also can be done like this:

std::vector<std::string> dumpLogs(){
    auto dumped_logs = logs;
    return dumped_logs;
}
Quimby
  • 17,735
  • 4
  • 35
  • 55
  • 1
    That's what the other answers have come up with as well, but in the case where this is called on a temporary object, by the time the function returns you have an (rvalue) reference to an object whose lifetime has ended (undefined behavior). – Cruz Jean May 02 '19 at 20:52
  • @CruzJean Well, my answer was here first and no one downvoted/complained yet so I will keep it here. I am not returning any reference to a temporary anywhere. – Quimby May 02 '19 at 20:58
  • `return std::move(logs);` where the return value is a reference type and `*this` is a temporary (due to being an rvalue method) makes `this->logs` a temporary as well. That's the reference to a temporary I mean. – Cruz Jean May 02 '19 at 22:25
  • @CruzJean Do you mean e.g. `auto&& x = Logger{}.dumpLogs();` ? Yes, that is dangling reference, but that happens for all getters no matter what type of reference they return. My use case would be something like `Logger l; /*calls to log()*/, auto logs = l.dumpLogs();/*log again.. and repeat.*/` – Quimby May 03 '19 at 10:54