How can I implement “op” in terms of “op=” in a CR

2019-06-02 07:20发布

Herb Sutter's Guru of the Week #4, "Class Mechanics", teaches that the "a op b" form of an overloaded operator should be implemented in terms of the "a op= b" form (see point #4 in the solutions).

As an example, he shows how do to this for the + operator:

T& T::operator+=( const T& other ) {
    //...
    return *this;
}

T operator+( T a, const T& b ) {
    a += b;
    return a;
}

He points out that first parameter in operator+ is intentionally being passed by value, so that it can be moved if the caller passes a temporary.

Notice that this requires that the operator+ be a non-member function.

My question is, how can I apply this technique to an overloaded operator in a CRTP base class?

So say this is my CRTP base class with its operator+=:

template <typename Derived>
struct Base
{
    //...

    Derived operator+=(const Derived& other)
    {
        //...
        return static_cast<Derived&>(*this);
    }
};

I can see how to implement operator+ in terms of operator+= as a member function if I dispense with the "pass the first argument by value" optimization:

template <typename Derived>
struct Base
{
    //...

    Derived operator+(const Derived& other) const
    {
        Derived result(static_cast<const Derived&>(*this);
        result += other;
        return result;
    }
};

but is there a way to do this while using that optimization (and therefore making the operator+ a nonmember)?

1条回答
时光不老,我们不散
2楼-- · 2019-06-02 07:38

The normal way to implement Herb's advice is as follows:

struct A {
      A& operator+=(cosnt A& rhs)
      {
          ...
          return *this;
      }
      friend A operator+(A lhs, cosnt A& rhs)
      {
          return lhs += rhs;
      }
};

Extending this to CRTP:

template <typename Derived>
struct Base
{
    Derived& operator+=(const Derived& other)
    {
        //....
        return *self();
    }
    friend Derived operator+(Derived left, const Derived& other)
    {
        return left += other;
    }
private:
    Derived* self() {return static_cast<Derived*>(this);}
};

If you try to avoid the use of friend here, you realize it's almost this:

 template<class T>
 T operator+(T left, const T& right) 
 {return left += right;}

But is only valid for things derived from Base<T>, which is tricky and ugly to do.

template<class T, class valid=typename std::enable_if<std::is_base_of<Base<T>,T>::value,T>::type>
T operator+(T left, const T& right) 
{return left+=right;}

Additionally, if it's a friend internal to the class, then it's not technically in the global namespace. So if someone writes an invalid a+b where neither is a Base, then your overload won't contribute to the 1000 line error message. The free type-trait version does.


As for why that signature: Values for mutable, const& for immutable. && is really only for move constructors and a few other special cases.

 T operator+(T&&, T) //left side can't bind to lvalues, unnecessary copy of right hand side ALWAYS
 T operator+(T&&, T&&) //neither left nor right can bind to lvalues
 T operator+(T&&, const T&) //left side can't bind to lvalues
 T operator+(const T&, T) //unnecessary copies of left sometimes and right ALWAYS
 T operator+(const T&, T&&) //unnecessary copy of left sometimes and right cant bind to rvalues
 T operator+(const T&, const T&) //unnecessary copy of left sometimes
 T operator+(T, T) //unnecessary copy of right hand side ALWAYS
 T operator+(T, T&&) //right side cant bind to lvalues
 T operator+(T, const T&) //good
 //when implemented as a member, it acts as if the lhs is of type `T`.

If moves are much faster than copies, and you're dealing with a commutative operator, you may be justified in overloading these four. However, it only applies to commutative operators (where A?B==B?A, so + and *, but not -, /, or %). For non-commutative operators, there's no reason to not use the single overload above.

T operator+(T&& lhs , const T& rhs) {return lhs+=rhs;}
T operator+(T&& lhs , T&& rhs) {return lhs+=rhs;} //no purpose except resolving ambiguity
T operator+(const T& lhs , const T& rhs) {return T(lhs)+=rhs;} //no purpose except resolving ambiguity
T operator+(const T& lhs, T&& rhs) {return rhs+=lhs;} //THIS ONE GIVES THE PERFORMANCE BOOST
查看更多
登录 后发表回答