Херб Саттерс Гуру Недели № 4, «Класс Механики», учит, что оп b «Форма перегруженного оператора должна быть реализована в терминах» a оп = б «форма (см. пункт № 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+
не член)?
Обычный способ реализовать совет Херба заключается в следующем:
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 строк. Бесплатная версия типа черта делает.
Что касается того, почему эта подпись: значения для изменяемого, const& для неизменного. && действительно только для конструкторов перемещения и нескольких других особых случаев.
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