Может ли рефакторинг перегруженного оператора в функцию, не являющуюся членом, нарушить какой-либо код?

Рассмотрим устаревший шаблон класса с перегруженными операторами сложения += а также +

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)

9

Решение

Резюме

Ответ: да, всегда будет поломка. Важным компонентом является то, что вычет аргумента шаблона функции не учитывает неявные преобразования. Мы рассматриваем три сценария, охватывающие три синтаксические формы, которые может принимать перегруженный оператор.

Здесь мы используем неявный конструктор внутри 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
3

Другие решения


По вопросам рекламы [email protected]