Как я могу реализовать "op" в терминах "op=" в базовом классе CRTP?

Гуру Херба Саттера недели № 4, "Механика классов", учит, что форма " op b" перегруженного оператора должна быть реализована в форме "a op = b" (см. Пункт #4 в решениях),

В качестве примера он показывает, как сделать это для + оператор:

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

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

Он указывает, что первый параметр в operator+ преднамеренно передается по значению, так что его можно переместить, если вызывающий передает временный.

Обратите внимание, что это требует, чтобы operator+ быть не членом функции.

У меня вопрос, как я могу применить эту технику к перегруженному оператору в базовом классе CRTP?

Скажем так, это мой базовый класс CRTP с его operator+=:

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

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

Я вижу, как реализовать operator+ с точки зрения operator+= в качестве функции-члена, если я обойдусь без оптимизации "передать первый аргумент по значению":

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

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

но есть ли способ сделать это при использовании этой оптимизации (и, следовательно, сделать operator+ не член)?

1 ответ

Решение

Обычный способ реализовать совет Херба заключается в следующем:

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

Расширение этого до 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);}
};

Если вы пытаетесь избежать использования friend здесь вы понимаете, что это почти так:

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

Но действует только для вещей, полученных из Base<T> что сложно и некрасиво сделать.

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;}

Кроме того, если это friend внутренне к классу, то технически это не в глобальном пространстве имен. Так что, если кто-то пишет инвалид a+b где ни один Base, тогда ваша перегрузка не будет способствовать появлению сообщения об ошибке в 1000 строк. Бесплатная версия типа черта делает.


Что касается того, почему эта подпись: значения для изменчивых, постоянных и неизменных. && действительно только для конструкторов перемещения и нескольких других особых случаев.

 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`.

Если ходы выполняются намного быстрее, чем копии, и вы имеете дело с коммутативным оператором, вы можете оправдать перегрузку этих четырех. Однако это применимо только к коммутативным операторам (где A?B==B?A, то есть + и *, но не -, /, или%). Для некоммутативных операторов нет причин не использовать приведенную выше единственную перегрузку.

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
Другие вопросы по тегам