Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I reuse an rvalue reference parameter to return an rvalue reference?

Consider the following code:

struct MyString
{
  // some ctors

  MyString& operator+=( const MyString& other ); // implemented correctly
};

MyString operator+( const MyString& lhs, const MyString& rhs )
{
  MyString nrv( lhs );
  nrv += rhs;
  return nrv;
}

MyString&& operator+( MyString&& lhs, const MyString& rhs )
{
  lhs += rhs;
  return std::move( lhs ); // return the rvalue reference we received as a parameter!
}

This works for the following use-case

MyString a, b, c; // initialized properly
MyString result = a + b + c;

But it creates a dangling reference for

const MyString& result = a + b + c;

Now, I understand why that is and how to fix it (returning an ravlue instead of an rvalue reference) but I consider it a usage error if someone writes the above as the code looks like it is asking for trouble. Is there any "canonical" real-world example where the above operator returning a rvalue reference is a problem? What is a convincing reason why I should always return an rvalue from operators?

like image 926
Daniel Frey Avatar asked Apr 25 '13 23:04

Daniel Frey


People also ask

Can a function return an rvalue?

Typically rvalues are temporary objects that exist on the stack as the result of a function call or other operation. Returning a value from a function will turn that value into an rvalue. Once you call return on an object, the name of the object does not exist anymore (it goes out of scope), so it becomes an rvalue.

Can you pass an lvalue to an rvalue reference?

In the example, the main function passes an rvalue to f . The body of f treats its named parameter as an lvalue. The call from f to g binds the parameter to an lvalue reference (the first overloaded version of g ). You can cast an lvalue to an rvalue reference.

Can you take the address of an rvalue?

An address can not be taken of rvalues. An rvalue has no name as its a temporary value.

Can you modify an rvalue?

rvalue references have two properties that are useful: rvalue references extend the lifespan of the temporary object to which they are assigned. Non-const rvalue references allow you to modify the rvalue.


2 Answers

The example you are looking for is a range-based for statement:

MyString a, b, c;
for( MyCharacter mc : a + b + c ) { ... }

In this case the result of a + b + c is bound to a reference, but the nested temporary (generated by a + b and returned as an rvalue reference by (a + b) + c) is destroyed before the range-based for loop is executed.

The standard defines range-based for loops in

6.5.4 The range-based for statement [stmt.ranged]

1 For a range-based for statement of the form

for (for-range-declaration:expression)statement

let range-init be equivalent to the expression surrounded by parentheses

( expression )

and for a range-based for statement of the form

for (for-range-declaration:braced-init-list)statement

let range-init be equivalent to the braced-init-list. In each case, a range-based for statement is equivalent to

{
   auto && __range = range-init;
   for ( auto __begin = begin-expr,
              __end = end-expr;
         __begin != __end;
         ++__begin ) {
      for-range-declaration = *__begin;
      statement
   }
}

Note that auto && __range = range-init; would extend the lifetime of a temporary returned from range-init, but it does not extend the lifetime of nested temporaries inside of range-init.

like image 181
Daniel Frey Avatar answered Sep 28 '22 08:09

Daniel Frey


Instead of asking for trouble, you should trust the string's own move constructor:

MyString operator+(MyString lhs, MyString rhs)
{
    lhs += std::move(rhs);
    return std::move(lhs);
}

Now both MyString x = a + b; and MyString y = MyString("a") + MyString("b"); work efficiently.

like image 29
Kerrek SB Avatar answered Sep 28 '22 07:09

Kerrek SB