重载模板类的运算符时的隐式转换

时间:2012-01-17 05:20:35

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

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

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)无法使用转换构造函数对类的模板版本执行隐式类型转换。这是预期的行为吗?除了手动创建所有必要的重载外,还有解决方法吗?

3 个答案:

答案 0 :(得分:12)

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

我基于你的一个例子(但删除了C ++ 11的东西),灵感来自Scott Meyers Effective C ++(第3版)中的第46项(有理数字类)。您的问题几乎与该项目完全匹配。斯科特还指出......“朋友的这种使用与课堂上非公共部分的访问无关。”

这也将允许使用foo&lt;的混合物。 T&gt;,foo&lt; U>等等,只要添加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;             
}

答案 1 :(得分:8)

我把这个问题提交给了MS的图书馆作者,得到了Stephan Lavavej的非常好的回复,所以我完全赞同这些信息。

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

详细说明,模板参数推导查看每对参数类型P和参数类型A,并尝试查找模板替换,使A 完全匹配P.找到每个参数的匹配后,它检查一致性(这样如果你用第一个参数的T = int调用bar(foo<T>, foo<T>)而第二个参数调用T = double,它也会失败)。只有在函数签名中成功替换了精确一致的匹配后​​,才会将该签名添加到候选函数集中以进行重载解析。

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

对于operator+(foo<T>, foo<T>) foo<int> + 5的情况,模板参数推断可以找不到T的替代,这将使表达式foo<T> 完全匹配{{1}因此,运算符+的重载作为候选者被抛出,甚至从未看到隐式转换。

这里的观点似乎是,这通常是一件好事,因为它使模板更具可预测性,使得奇怪的隐式行为领域超负荷解决。

该标准有很多可以说明的内容:

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

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

...通常,演绎过程会尝试查找将导出推导出的A的模板参数值 与A相同(在如上所述转换A型之后)“

接着列出了一些特殊情况,其中此规则具有涉及cv限定符的异常(因此T&amp;将与const T&amp;兼容),以及派生类的匹配(在某些情况下,它可以匹配Derived&amp;到Base&amp; amp ;)但基本上,完全匹配是规则。

答案 2 :(得分:2)

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