Can refactoring an overloaded operator into a non-member function break any code?

こ雲淡風輕ζ 提交于 2019-12-30 08:24:24

问题


Consider a legacy class template with overloaded addition operators += and +

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

Upon code review, it is observed that + is implementable in terms of +=, so why not make it a non-member (and have guaranteed symmetry for left and right arguments)?

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

It look safe enough, because all valid expression using + and += retain their original semantic meaning.

Question: can the refactoring of operator+ from a member function into a non-member function break any code?

Definition of breakage (worst to best)

  • new code will compile that did not compile under the old scenario
  • old code will not compile that did compile under the old scenario
  • new code will silently call a different operator+ (from base class or associated namespace dragged in through ADL)

回答1:


Summary

The answer is, yes, there will always be breakage. The essential ingredient is that function template argument deduction does not consider implicit conversions. We consider three scenarios, covering the three syntactic forms that an overloaded operator can take.

Here we use an implicit constructor inside X<T> itself. But even if we made that constructor explicit, users could add to the namespace of X<T> a class C<T> that contains an implicit conversion of the form operator X<T>() const. The scenarios below would continue to hold in that case.

A non-member friend function breaks the least in the sense that it will allow lhs argument implicit conversions that would not compile for a class template's member function. The non-member function template breaks the implicit conversion on rhs arguments.

Class template's member function

template<class T>
class X
{
public:
    /* implicit */ X(T val) { /* bla */ }
//...
    X<T> operator+(X<T> const& rhs) { /* bla */ }
//...
};

This code will allow expression like

T t;
X<T> x;
x + t;  // OK, implicit conversion on non-deduced rhs
t + x;  // ERROR, no implicit conversion on deduced this pointer

Non-member friend function

template<class T>
class X
{
public:
    /* implicit */ X(T val) { /* bla */ }
//...
    friend 
    X<T> operator+(X<T> const& lhs, X<T> const& rhs) { /* bla */ }
//...
};

Since the friend function is a not a template, no argument deduction takes place and both the lhs and rhs argument consider implicit conversions

T t;
X<T> x;
x + t;  // OK, implicit conversion on rhs
t + x;  // OK, implicit conversion on lhs

Non-member function template

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 */ }

In this case, both the lhs and rhs arguments undergo argument deduction, and neither takes implicit conversions into account:

T t;
X<T> x;
x + t;  // ERROR, no implicit conversion on rhs
t + x;  // ERROR, no implicit conversion on lhs


来源:https://stackoverflow.com/questions/26089156/can-refactoring-an-overloaded-operator-into-a-non-member-function-break-any-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!