Consider the following:
struct vec
{
int v[3];
vec() : v() {};
vec(int x, int y, int z) : v{x,y,z} {};
vec(const vec& that) = default;
vec& operator=(const vec& that) = default;
~vec() = default;
vec& operator+=(const vec& that)
{
v[0] += that.v[0];
v[1] += that.v[1];
v[2] += that.v[2];
return *this;
}
};
vec operator+(const vec& lhs, const vec& rhs)
{
return vec(lhs.v[0] + rhs.v[0], lhs.v[1] + rhs.v[1], lhs.v[2] + rhs.v[2]);
}
vec&& operator+(vec&& lhs, const vec& rhs)
{
return move(lhs += rhs);
}
vec&& operator+(const vec& lhs, vec&& rhs)
{
return move(rhs += lhs);
}
vec&& operator+(vec&& lhs, vec&& rhs)
{
return move(lhs += rhs);
}
Thanks to r-value references, with these four overloads of operator+ I can minimize the number of objects created, by reusing temporaries. But I don’t like the duplication of code this introduces. Can I achieve the same with less repetition?
Recycling temporaries is an interesting idea and you’re not the only one who wrote functions that return rvalue references for this reason. In an older C++0x draft operator+(string&&,string const&) was also declared to return an rvalue reference. But this changed for good reasons. I see three issues with this kind of overloading and choice of return types. Two of them are independent of the actual type and the third argument refers to the kind of type that
vecis.Safety issues. Consider code like this:
If your last operator returns an rvalue reference,
xwill be a dangling reference. Otherwise, it won’t. This is not an artificial example. For example, theauto&&trick is used in the for-range loop internally to avoid unnecessary copies. But since the life-time extension rule for temporaries during reference binding does not apply in case of a function call that simply returns a reference, you’ll get a dangling reference.If the last operator+ returned an rvalue reference to the temporary that is created during the first concatenation, this code would invoke undefined behaviour because the string temporary would not exist long enough.
In generic code, functions that return rvalue references force you to write
instead of
simply because the last op+ might return an rvalue reference. This is getting ugly, in my humble opinion.
Since your type
vecis both “flat” and small, these op+ overloads are hardly useful. See FredOverflow’s answer.Conclusion: Functions with an rvalue reference return type should be avoided especially if these references may refer to short-lived temporary objects.
std::moveandstd::forwardare special-purpose exceptions to this rule of thumb.