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?

1
  • 1
    This issue was also brought up in Defect report 1138. Commented Apr 25, 2013 at 23:11

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

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.

4 Comments

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.
@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.
@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.
@DanielFrey Passing by value instead of by reference was presented at Going Native 2012 - STL11 Magic && Secrets starting at the 35:10 mark. It's worth watching if you haven't seen it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.