内部和外部重载C ++运算符之间的区别

时间:2016-12-20 16:47:38

标签: c++ class templates operator-overloading

我有以下代码无法编译,抱怨+ =运算符不存在。 + =运算符在此处声明为A类之外。

template < typename _T >
class A {
public:
    operator _T () const { return 42 ; }
};

template <typename _T >
A< _T > & operator += ( A< _T > & l, _T r ) { return l ; }


int main() {
    A< int > e, f ;
    e += f ;
    return 0 ;
}

但是,如果我在A类中实现运算符,代码编译并运行:

template < typename _T >
class A {
public:
    operator _T () const { return 42 ; }

    A< _T > & operator += ( _T r ) { return *this ; }
};


int main() {
    A< int > e, f ;
    e += f ;
    return 0 ;
}

这两个代码有什么区别?它们不应该是等价的吗?

这是用gcc 4.4.7-4编译的。

4 个答案:

答案 0 :(得分:6)

第一个示例无法编译,因为模板参数推断不会进行任何转换。与

template <typename _T >
A< _T > & operator += ( A< _T > & l, _T r ) { return l ; }

lr都有助于确定_T是什么。当您执行e += f时,编译器会_T int lrA<int>必须为f,因为这是_T类型r。由于它们不匹配,因此无法编译。

在第二个代码中,没有模板参数推断。编译器知道类的实例化中_T是什么,所以它需要做的就是将传递给display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */ display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */ display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */ display: -ms-flexbox; /* TWEENER - IE 10 */ display: -webkit-flex; /* NEW - Chrome */ display: flex; 的内容转换为parent.layout(true, true);

我还建议你不要习惯用下划线开始名字。有一些关于它们的规则,如果你违反它们,那么你的程序有未定义的行为,因为它们是为实现保留的。有关详情,请参阅What are the rules about using an underscore in a C++ identifier?

答案 1 :(得分:3)

简短的回答是,您在用户代码中使用了_T,因此整个程序生成错误,无需诊断;由_后跟大写字母组成的标识符保留供实现使用。

在这个意义上,两个例子完全相同。

忽略该错误,它们并不相同。

第一个是非会员template +=运营商。

第二个是模板类的非模板成员+=,它采用隐式this作为其第一个参数。

这些是非常不同的东西。 template函数模式匹配不进行转换(除了to-base); template类型的方法可以转换。

在第二种情况下,非模板operator+=能够将其第二个参数转换为_T类型。在第一种情况下,模板operator+=不会尝试转换模式匹配类型。

实际上有第三种可能性,我通常更喜欢这种可能性:

template < class T >
struct A {
  operator T () const { return 42 ; }
  friend A& operator += ( A& l, T r ) { return l; (void)r; }
};

我们作为朋友免费+=。这会创建一个非模板+=,它带有两个参数,因此更加对称。

此类非模板朋友可以通过ADL找到。

live example

另外,它们也有所不同,因为指向一个的指针可以存储在A<_T>& (A<_T>::*)( _T )中,另一个指针可以存储在A<_T>& (*)(A<_T>&, _T)中。这些不相同,也不能转换。

答案 2 :(得分:1)

  

它们不应该是等价的吗?

不,因为在第一个示例中,_T是模板function parameter which gets deduced,而在第二个示例中,它是已知的。

想象一下编译器扩展了第二个例子:

template <>
class A_int {
public:
    operator int () const { return 42 ; }

    A< int > & operator += ( int r ) { return *this ; }
};

g ++ 7.0提供了一个很好的错误消息,解释了扣除失败的原因:

deduced conflicting types for parameter '_T' ('int' and 'A<int>')

可能的解决方案/解决方法是添加额外的模板参数:

template <typename _T, typename _U>
A< _T > & operator += ( A< _T > & l, _U r ) { return l ; }

wandbox example

答案 3 :(得分:0)

这有点棘手。在第二种情况下,您的运算符是类模板A的成员函数,而不是模板本身。当您致电e += f时,会发现匹配为A<int>::operator += (int),该匹配已存在于A<int>中。存在从A<int>int的隐式转换,因此此重载有效。

在第一种情况下,运算符是一个模板:编译器尝试通过仅从调用站点推导出_T参数来实例化它。模板参数推导不考虑用户定义的转换,因此推导失败。

解决方案是通过使用不可导入的上下文(例如通过模板的附加间接)来防止第二个参数参与演绎:

template <class T>
struct NonDeduced_ { using type = T; }

template <class T>
using NonDeduced = typename NonDeduced_<T>::type;

template <typename _T >
A< _T > & operator += ( A< _T > & l, NonDeduced<_T> r ) { return l ; }

然后只有第一个参数参与演绎,成功,然后推导出的_T用于查看第二个参数是否具有可行的转换。