Implicit conversion when overloading operators for template classes
我想知道为什么隐式类型转换不适用于类模板上的外部运算符重载。以下是工作的非模板化版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class foo { public: foo() = default; foo(int that) {} foo& operator +=(foo rhs) { return *this; } }; foo operator +(foo lhs, foo rhs) { lhs += rhs; return lhs; } |
如预期的那样,以下行正确编译:
1 2 3 4 5 | foo f, g; f = f + g; // OK f += 5; // OK f = f + 5; // OK f = 5 + f; // OK |
另一方面,当类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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; } |
以下行编译时出错:
1 2 3 4 5 | 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+) |
我想了解为什么编译器(GCC4.6.2)无法使用类模板版本的转换构造函数执行隐式类型转换。这是预期的行为吗?除了手动创建所有必需的重载之外,还有其他解决方法吗?
它不仅起作用的原因是隐式类型转换(即通过构造函数)在模板参数推导期间不适用。但是,如果您使外部操作符成为朋友,那么它就会工作,因为类型t是已知的,这样编译器就可以研究什么可以强制转换,以使参数匹配。
我做了一个基于你的例子(但是删除了C++ 11的东西),灵感来自于Scott Meyers有效C++(ED 3)中的条目46(一个有理数类)。你的问题几乎与那件事完全吻合。斯科特还指出……"使用friend与访问类的非公共部分无关。"
这也将允许与foo
还看这个帖子:C++加法超载歧义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | #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; } |
我把这个问题交给了微软的图书馆作者,得到了斯蒂芬·拉瓦维的一个非常有用的回答,所以我完全相信他提供的信息。
模板案例中出现的编译错误是由于模板参数推导在重载解决方案之前运行,模板参数推导需要完全匹配才能向重载集添加任何内容。
具体来说,模板参数推导会查看每对参数类型p和参数类型a,并试图找到模板替换,使其与p完全匹配。在找到每个参数的匹配项后,它会检查一致性(因此,如果您使用t=int作为第一个参数调用
只有在所有普通函数(通过名称查找找到)和匹配的函数模板签名添加到重载集之后,才运行重载解决方案,此时所有这些函数签名都将被评估为"最佳匹配",在此期间将考虑隐式转换。
对于
这里的观点似乎是,这通常是一件好事,因为它使模板更具可预测性,从而使奇怪的隐式行为领域成为过载解决方案。
标准对此有很多话要说:
14.8.2.1从函数调用中推导模板参数
"模板参数推导是通过将每个函数模板参数类型(称为p)与调用的相应参数的类型(称为a),如下所述。…
…一般来说,推导过程试图找到模板参数值,使推导出的与a相同(在按照上述方式转换类型a之后)
接下来将列出一些特殊情况,其中该规则有涉及cv限定符的异常(因此t&;将与const&;兼容),以及派生类的匹配(在某些情况下,它可以将派生类与基类匹配),但基本上,精确匹配是规则。
所有可能的