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

pmj*_*bin 20 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;
}
Run Code Online (Sandbox Code Playgroud)

正如所料,以下行正确编译:

foo f, g;
f = f + g; // OK
f += 5; // OK
f = f + 5; // OK
f = 5 + f; // OK
Run Code Online (Sandbox Code Playgroud)

另一方面,当类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;
}
Run Code Online (Sandbox Code Playgroud)

以下行编译时出错:

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+)
Run Code Online (Sandbox Code Playgroud)

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

Joh*_*erg 12

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

我在Scott Meyers Effective C++(第3版)的第46项(一个有理数字类)的启发下,根据你的例子(但删除了C++ 11的东西)做了一个例子.您的问题几乎与该项目完全匹配.斯科特还指出......"朋友的这种使用与课堂上非公共部分的访问无关."

只要可以添加T和U等,这也可以使用foo <T>,foo <U>等混合.

另外看看这篇文章:C++加法过载模糊

#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;             
}
Run Code Online (Sandbox Code Playgroud)


bre*_*anw 8

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

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

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

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

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

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

该标准有很多可以说:

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

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

...一般来说,演绎过程试图找到模板参数值,使得推导出的A与A相同(在如上所述转换类型A之后)"

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