Reputation: 56903
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?
Upvotes: 7
Views: 1402
Reputation: 56903
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)
statementlet 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)
statementlet 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.
Upvotes: 9
Reputation: 477408
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.
Upvotes: 5