Рассмотрим устаревший шаблон класса с перегруженными операторами сложения +=
а также +
template<class T>
class X
{
public:
X() = default;
/* implicict */ X(T v): val(v) {}
X<T>& operator+=(X<T> const& rhs) { val += rhs.val; return *this; }
X<T> operator+ (X<T> const& rhs) const { return X<T>(*this) += rhs; }
private:
T val;
};
При просмотре кода наблюдается, что +
осуществимо с точки зрения +=
так почему бы не сделать его не членом (и иметь гарантированную симметрию для левого и правого аргументов)?
template<class T>
class X
{
public:
X() = default;
/* implicit */ X(T v): val(v) {}
X<T>& operator+=(X<T> const& rhs) { val += rhs.val; return *this; }
private:
T val;
};
template<class T>
X<T> operator+(X<T> const& lhs, X<T> const& rhs)
{
return X<T>(lhs) += rhs;
}
Это выглядит достаточно безопасно, потому что все допустимые выражения, использующие +
а также +=
сохранить свое первоначальное смысловое значение.
Вопрос: может ли рефакторинг operator+
из функции-члена в функцию, не являющуюся членом, нарушить какой-либо код?
Определение поломки (от худшего к лучшему)
operator+
(из базового класса или связанного пространства имен, перетаскиваемого через ADL)Ответ: да, всегда будет поломка. Важным компонентом является то, что вычет аргумента шаблона функции не учитывает неявные преобразования. Мы рассматриваем три сценария, охватывающие три синтаксические формы, которые может принимать перегруженный оператор.
Здесь мы используем неявный конструктор внутри X<T>
сам. Но даже если мы сделали этот конструктор explicit
пользователи могут добавить в пространство имен X<T>
класс C<T>
который содержит неявное преобразование формы operator X<T>() const
, Сценарии, приведенные ниже, будут продолжать действовать в этом случае.
Функция, не являющаяся членом-другом, ломает меньше всего в том смысле, что она допускает неявные преобразования аргументов lhs, которые не будут компилироваться для функции-члена шаблона класса. Шаблон функции, не являющейся членом, нарушает неявное преобразование аргументов rhs.
template<class T>
class X
{
public:
/* implicit */ X(T val) { /* bla */ }
//...
X<T> operator+(X<T> const& rhs) { /* bla */ }
//...
};
Этот код позволит выражение как
T t;
X<T> x;
x + t; // OK, implicit conversion on non-deduced rhs
t + x; // ERROR, no implicit conversion on deduced this pointer
template<class T>
class X
{
public:
/* implicit */ X(T val) { /* bla */ }
//...
friend
X<T> operator+(X<T> const& lhs, X<T> const& rhs) { /* bla */ }
//...
};
Так как friend
функция не является шаблоном, вычет аргументов не происходит, и аргументы lhs и rhs учитывают неявные преобразования
T t;
X<T> x;
x + t; // OK, implicit conversion on rhs
t + x; // OK, implicit conversion on lhs
template<class T>
class X
{
public:
/* implicit */ X(T val) { /* bla */ }
//...
};
template<class T>
X<T> operator+(X<T> const& lhs, X<T> const& rhs) { /* bla */ }
В этом случае аргументы lhs и rhs подвергаются вычету аргументов, и ни один из них не учитывает неявные преобразования:
T t;
X<T> x;
x + t; // ERROR, no implicit conversion on rhs
t + x; // ERROR, no implicit conversion on lhs