【问题标题】:Difference between inner and outer overloaded C++ operator内部和外部重载 C++ 运算符之间的区别
【发布时间】:2017-05-05 23:08:50
【问题描述】:

我有以下代码无法编译,抱怨 += 运算符不存在。 += 运算符在这里声明在 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 编译的。

【问题讨论】:

  • 这不是问题,但是以下划线后跟大写字母 (_T) 的名称保留供实现使用。不要在你的代码中使用它们。

标签: c++ class templates operator-overloading


【解决方案1】:

第一个例子编译失败,因为模板参数推导没有做任何转换。与

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

lr 都有助于确定 _T 是什么。当您执行e += f 时,编译器会得到_T 必须是int 对应l 并且它得到r 必须是A&lt;int&gt;,因为这是f 的类型。由于它们不匹配,因此无法编译。

在第二个代码中没有进行模板参数推导。编译器从类的实例化中知道_T 是什么,因此它所需要做的就是将传递给r 的内容转换为_T


我还建议您改掉名称以下划线开头的习惯。有很多关于它们的规则,如果你违反了它们,那么你的程序就会有未定义的行为,因为它们是为实现而保留的。更多内容见What are the rules about using an underscore in a C++ identifier?

【讨论】:

    【解决方案2】:

    尖刻的简短回答是,您在用户代码中使用了_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&lt;_T&gt;&amp; (A&lt;_T&gt;::*)( _T ) 中,另一个类似于A&lt;_T&gt;&amp; (*)(A&lt;_T&gt;&amp;, _T)。它们不相同,也不能相互转换。

    【讨论】:

    • 第一个和第二个颠倒了。
    • @kund 更改为 tsrif 和 dnoces。谢谢!
    【解决方案3】:

    它们不应该是等价的吗?

    不,因为在第一个示例中,_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

    【讨论】:

      【解决方案4】:

      这有点棘手。在第二种情况下,您的运算符是类模板A 的成员函数,而不是模板本身。当您调用e += f 时,会发现匹配为A&lt;int&gt;::operator += (int),它已存在于A&lt;int&gt; 中。从A&lt;int&gt;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,看第二个参数是否有可行的转化。

      【讨论】:

        猜你喜欢
        • 2017-05-24
        • 2013-11-02
        • 2010-12-30
        • 2010-11-08
        • 1970-01-01
        • 2011-07-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多