模板类的非外联运算符为何无法触发隐式转换?
问题描述
我尝试用两种方式给类定义运算符:一种是类内inline定义,另一种是类外非inline定义。通常推荐尽量在类外定义运算符,这样能利用左操作数的隐式转换。但把类改成模板类后,不仅左操作数的隐式转换失效,右操作数的也没法正常工作,是不是我操作错了?
测试代码
namespace N1 { template <class T> class C1 { public: double value; /* implicit */ C1(double value) : value{value} {} C1(const C1<T>& other) : value{other.value} {} C1<T>& operator=(const C1<T>& other) { this->value = other.value; return *this; } // 类内inline定义operator+ inline C1<T> operator+(const C1<T>& other) const { return this->value + other.value; } }; template <class T> class C2 { public: double value; /* implicit */ C2(double value) : value{value} {} C2(const C2<T>& other) : value{other.value} {} C2<T>& operator=(const C2<T>& other) { this->value = other.value; return *this; } }; // 类外定义C2的operator+ template <class T> inline C2<T> operator+(const C2<T>& self, const C2<T>& other) { return self.value + other.value; } } // namespace N1 namespace { using C1 = N1::C1<int>; using C2 = N1::C2<int>; // double f1(double x, const C1& y) { // return (x + y).value; // 本来就不指望能运行 // } double f2(const C1& x, double y) { return (x + y).value; // 正常运行 } double f3(double x, const C2& y) { // C2是普通类时能运行,改成模板类后失效 return (x + y).value; } double f4(const C2& x, double y) { // C2是普通类时能运行,改成模板类后失效 return (x + y).value; } } // namespace
期望客户端代码
void my_main() { N1::C2<Anything> x{4}; auto y = x + 2.0; auto z = 2.0 + x; }
原因与解决方法
这不是操作错误,问题根源在于C++模板参数推导的规则限制:
当编译器处理模板版本的operator+时,需要推导模板参数T,但推导过程中不会利用隐式转换来匹配参数类型。比如调用x + 2.0(x是C2<int>),编译器看到operator+的两个参数都是const C2<T>&,它无法通过把double隐式转换成C2<int>来推导T,因为推导阶段不会触发隐式转换,最终导致匹配失败。
而普通类的operator+是固定类型的函数,编译器可以直接把double隐式转换成类类型来匹配参数,所以能正常运行。
解决这个问题最简洁的方案是把运算符声明为友元并在类内定义,这样编译器在处理调用时已经知道模板参数T的具体类型,允许触发隐式转换:
template <class T> class C2 { public: double value; /* implicit */ C2(double value) : value{value} {} C2(const C2<T>& other) : value{other.value} {} C2<T>& operator=(const C2<T>& other) { this->value = other.value; return *this; } // 友元运算符,类内定义 friend inline C2<T> operator+(const C2<T>& self, const C2<T>& other) { return self.value + other.value; } };
这种方式下,无论是左操作数还是右操作数的隐式转换都能正常生效,同时保留模板类的通用性。
另外补充:类内成员版本的operator+(比如C1的)能支持右操作数转换,是因为成员函数调用时左操作数类型确定,编译器只需要把右操作数转换成参数类型,不需要模板推导;但它无法支持左操作数的隐式转换,因为成员函数必须由类类型对象调用,编译器不会为了调用成员函数把其他类型转成类类型。
内容的提问来源于stack exchange,提问作者Neal Gafter
相关产品推荐
相关产品推荐

