【问题标题】:Implicit conversion when overloading operators for template classes为模板类重载运算符时的隐式转换
【发布时间】:2012-02-11 23:41:08
【问题描述】:

我想知道为什么隐式类型转换不适用于类模板上的外部运算符重载。这是工作的非模板版本:

class foo
{
public:

    foo() = default;

    foo(int that)
    {}

    foo& operator +=(foo rhs)
    {
        return *this;
    }
};

foo operator +(foo lhs, foo rhs)
{
    lhs += rhs;
    return lhs;
}

正如预期的那样,以下行编译正确:

foo f, g;
f = f + g; // OK
f += 5; // OK
f = f + 5; // OK
f = 5 + f; // OK

另一方面,当 foo 类被声明为这样的简单模板时:

template< typename T >
class foo
{
public:

    foo() = default;

    foo(int that)
    {}

    foo& operator +=(foo rhs)
    {
        return *this;
    }
};

template< typename T >
foo< T > operator +(foo< T > lhs, foo< T > rhs)
{
    lhs += rhs;
    return lhs;
}

以下行编译错误:

foo< int > f, g;
f = f + g; // OK
f += 5; // OK
f = f + 5; // Error (no match for operator+)
f = 5 + f; // Error (no match for operator+)

我想了解为什么编译器 (GCC 4.6.2) 无法使用类模板版本的转换构造函数执行隐式类型转换。这是预期的行为吗?除了手动创建所有必要的重载之外,还有什么解决方法吗?

【问题讨论】:

  • 你可以为 T 添加一个重载: foo operator +(foo lhs, T rhs)

标签: c++ templates operator-overloading implicit-conversion


【解决方案1】:

它不能正常工作的原因是隐式类型转换(即通过构造函数)在模板参数推导期间不适用。 但是如果你让外部运算符成为朋友,那么它就可以工作,因为类型 T 是已知的,允许编译器调查可以强制转换以使参数匹配。

我根据你的例子做了一个例子(但删除了 C++11 的东西),灵感来自 Scott Meyers Effective C++(ed 3)中的 Item 46(一个有理数类)。您的问题几乎与该项目完全匹配。 Scott 还指出……“这种对朋友的使用与类的非公共部分的访问无关。”

这也将允许使用 foo、foo 等的混合,只要可以添加 T 和 U 等。

也可以看看这个帖子:C++ addition overload ambiguity

#include <iostream>

using namespace std;

template< class T >
class foo
{
private:
   T _value;
public:
   foo() : _value() {}

   template <class U>
   foo(const foo<U>& that) : _value(that.getval()) {}

   // I'm sure this it can be done without this being public also;
   T getval() const { return _value ; }; 

   foo(const T& that) : _value(that) {}

   friend const foo operator +(foo &lhs,const foo &rhs) 
      {
     foo result(lhs._value+rhs._value); 
     return result;
      };
   friend const foo operator +(foo &lhs,const T &rhsval) 
      {
     foo result(lhs._value+rhsval); 
     return result;
      };
   friend const foo operator +(const T &lhsval,foo &rhs) 
      {
     foo result(lhsval+rhs._value); 
     return result;
      };

   friend foo& operator +=(foo &lhs,const foo &rhs)
      {
     lhs._value+=rhs._value;
     return lhs;
      };   
   friend std::ostream& operator<<(std::ostream& out, const foo& me){
      return out <<me._value;
   }
};

int main(){
   foo< int > f, g;
   foo< double > dd;
   cout <<f<<endl;
   f = f + g;
   cout <<f<<endl;
   f += 3 ;
   cout <<f<<endl;
   f = f + 5;
   cout <<f<<endl;
   f = 7 + f; 
   cout <<f<<endl;      
   dd=dd+f;
   cout <<dd<<endl;      
   dd=f+dd;
   cout <<dd<<endl;      
   dd=dd+7.3;
   cout <<dd<<endl;             
}

【讨论】:

  • 这正是我所需要的!非常感谢!我绝对需要给自己买一份 Effective C++……
  • 太好了,我试着写一个尽可能具体的问题的答案。
  • 友元函数的语法是什么?如果我删除定义,我会收到一个错误“声明一个非模板函数”。
  • 在这里找到答案:stackoverflow.com/questions/962599/…
【解决方案2】:

我向 MS 的图书馆作者提出了这个问题,并从 Stephan Lavavej 那里得到了非常丰富的回复,因此我完全相信他提供的信息。

您在模板案例中遇到的编译错误是由于模板参数推导在重载解析之前运行,并且模板参数推导需要完全匹配才能将任何内容添加到重载集。

详细来说,模板实参推导会查看每一对形参类型 P 和实参类型 A,并尝试找到使 A 完全匹配 P 的模板替换。找到每个实参的匹配项后,它检查一致性(因此,如果您调用bar(foo&lt;T&gt;, foo&lt;T&gt;),第一个参数为 T=int,第二个参数为 T=double,它也会失败)。只有在函数签名中成功替换完全一致的匹配项后,才将该签名添加到候选函数集以进行重载解析。

只有在所有普通函数(通过名称查找找到)和匹配的函数模板签名都已添加到重载集后,才会运行重载解析,此时所有这些函数签名都会被评估为“最佳匹配”,在此期间将考虑时间隐式转换。

对于operator+(foo&lt;T&gt;, foo&lt;T&gt;)foo&lt;int&gt; + 5 的情况,模板参数推导找不到T 的替代物,这将使表达式foo&lt;T&gt; 完全 匹配int,因此operator+ 的重载得到作为候选者被抛弃,甚至从未见过隐式转换。

这里的观点似乎是,这通常是一件好事,因为它使模板更加可预测,将奇怪的隐式行为领域留给重载决议。

该标准对此有很多话要说:

14.8.2.1 从函数调用中推导出模板参数

"模板参数推导是通过比较每个函数模板参数类型(称为P)与 调用的相应参数的类型(称为 A),如下所述。 ...

... 一般来说,推导过程会尝试找到模板参数值,这些值将使推导的 A 与 A 相同(在类型 A 如上所述转换后)"

它继续列出一些特殊情况,其中该规则有例外涉及 cv 限定符(因此 T& 将与 const T& 兼容)和派生类的匹配(在某些情况下它可以匹配 Derived& 到 Base&)但基本上, 精确匹配是规则。

【讨论】:

    【解决方案3】:

    所有可能的foo&lt;T&gt; 都是来自int 的同样有效的转换,因为构造函数采用int,而不是模板类型。编译器无法使用运算符中的其他参数来猜测您可能指的是哪个参数,因此您会收到错误消息。如果你明确告诉它你想要哪个实例化,我相信它会起作用。

    【讨论】:

    • 但参数是foo&lt;T&gt;foo&lt;T&gt;,而不是foo&lt;T1&gt;foo&lt;T2&gt;。所以除了int 之外的任何T 都不会匹配第一个参数,并且必须被丢弃。对吗?
    • 即使在构造函数中将that 的类型更改为T,这仍然不起作用(至少对于MSVC10)。我试图简单地在标准中找到一些灵丹妙药来解释这一点,但我能猜到的最好的情况是发生了一个鸡和蛋的场景,涉及带有隐式转换的重载解析和模板参数推导。我认为编译器不能在没有另一个的情况下完成一个,反之亦然,因此需要更明确的重载。
    猜你喜欢
    • 1970-01-01
    • 2014-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-10
    • 2014-01-16
    • 2010-12-09
    相关资源
    最近更新 更多