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?
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.
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.
An address can not be taken of rvalues. An rvalue has no name as its a temporary value.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With