可以重构一个重载的运算符到一个非成员函数中断任何代码? [英] Can refactoring an overloaded operator into a non-member function break any code?
问题描述
考虑一个包含重载添加运算符的旧类模板 + =
和 +
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;
}
$ b < + 和 + =
保留其原始的语义含义。
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)
- 新代码将编译
- 新代码将默认调用不同的<$ c $ c> operator + (通过ADL从基类或关联的命名空间中拖拽)
- 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)
推荐答案
摘要
答案是,是的,总会有破损。基本成分是函数模板参数扣除不考虑隐式转换。我们考虑三种情况,覆盖了重载运算符可以采用的三种语法形式。
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.
这里我们使用 X
,其包含形式显式
,用户可以将 X
的类添加到< c $ c> C T 运算符X
的隐式转换。
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.
一个非成员函数打破了最小的意义,因为它将允许lhs参数隐式转换,不能编译用于类模板的成员函数。非成员函数模板破坏了对rhs参数的隐式转换。
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.
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
非成员友函数
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 */ }
//...
};
由于 friend
函数不是模板,没有参数扣除发生,并且lhs和rhs参数考虑隐式转换
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 */ }
$ b b
在这种情况下,lhs和rhs参数都要经过参数推导,并且不考虑隐式转换:
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
这篇关于可以重构一个重载的运算符到一个非成员函数中断任何代码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!