7

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?

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • 1
    This issue was also brought up in [Defect report 1138](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#1138). – Jesse Good Apr 25 '13 at 23:11

2 Answers2

9

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.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
5

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.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • For `x = a + b`, an extra copy of `b` is created which is not needed. But as I said, I know how to fix it, the question was why returning an rvalue reference is a problem. – Daniel Frey Apr 25 '13 at 23:08
  • @DanielFrey: Yeah, I guess, one of the two could be a reference... Returning a reference is only OK if you can guarantee that the reference refers to something that exists, which is hard and non-obvious for the user. – Kerrek SB Apr 25 '13 at 23:10
  • @DanielFrey: Since appending at the front is presumably not worth optimizing, I'd probably change the second parameter only to a `const MyString &`, as you have in your original code. – Kerrek SB Apr 25 '13 at 23:13
  • @DanielFrey Passing by value instead of by reference was presented at [Going Native 2012 - STL11 Magic && Secrets](http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/STL11-Magic-Secrets) starting at the `35:10` mark. It's worth watching if you haven't seen it. – Captain Obvlious Apr 26 '13 at 01:21