c++ref-qualifier

Is it reasonable to overload operators by ref-qualifier to prevent temporaries?


It just occurred to me that operator+ & co can operate on this for rvalues; i.e. given a class C, it's possible to do this:

class C {
    // ...
    C operator-( const C& rhs ) const & {
        C result = *this;
        result -= rhs;
        return result;
    }
    C&& operator-( const C& rhs ) && {
        *this -= rhs;
        return std::move( *this );
    }
};

This would prevent copies by simply modifying temporary values in-place.

Would this perform as I expect? Is it a reasonable optimization or would the compiler create equally fast code?


Solution

  • Let's say we just wrap std::string and do a simplified version of operator+:

    struct C { 
        std::string val;
    
        C&& operator+(const C& rhs) && {
            val += rhs.val;
            return std::move(*this);
        }   
    
        std::string::iterator begin() { return val.begin(); }
        std::string::iterator end() { return val.end(); }
    };
    

    With that, this works fine:

    for (char c : C{"hello"}) { .. }
    

    the range-for expression will extend the lifetime of the temporary, so we're ok. However, consider this:

    for (char c : C{"hello"} + C{"goodbye"}) { .. }
    

    We effectively have:

    auto&& __range = C{"hello"}.operator+(C{"goodbye"});
    

    Here, we're not binding a temporary to a reference. We're binding a reference. The object doesn't get its lifetime extended because... it's not an object. So we have a dangling reference, and undefined behavior. This would be very surprising to users who would expect this to work:

    for (char c : std::string{"hello"} + std::string{"goodbye"}) { .. }
    

    You'd have to return a value:

    C operator+(const C& rhs) && {
        val += rhs.val;
        return std::move(*this);
    }
    

    That solves this issue (as now we have temporary extension), and if moving your objects is cheaper than copying them, this is a win.