这是一个很长的问题陈述,但至少我希望这个问题可能对许多人有用,而不仅仅是我.
我的代码如下:
template <typename T> class V; template <typename T> class S; template <typename T> class V { public: T x; explicit V(const T & _x) :x(_x){} V(const S<T> & s) :x(s.x){} }; template <typename T> class S { public: T &x; explicit S(V<T> & v) :x(v.x) {} }; template <typename T> V<T> operator+(const V<T> & a,const V<T> & b) { return V<T>(a.x + b.x); } int main() { V<float> a(1); V<float> b(2); S<float> c( b ); b = a + V<float>(c); // 1 -- compiles b = a + c; // 2 -- fails b = c; // 3 -- compiles return 0; }
表达式1和3完美地工作,而表达式2不能编译.
如果我理解得当,会发生什么:
表达1
> c通过使用标准转换序列(仅包含一个限定转换)隐式转换为const.
调用> V< float>(const S< T>& s)并且使用时间const V< float>生成的对象(让我们称之为t).它已经是const限定的,因为它是一个时间值.
> a与c类似地转换为const.
调用>运算符(const V< float>& a,const V< float>& b),得到const V< float>类型的时间.我们可以打电话给q.
>调用默认的V< float> :: operator =(const& V< float>).
我可以到这儿了吗?如果我做出了最微妙的错误,请告诉我,因为我试图尽可能深入了解隐式铸造……
表达3
> c被转换为V< float>.为此,我们有一个用户定义的转换序列:
1.1.第一标准转换:S< float>到const S< float>通过资格转换.
1.2.用户定义的转换:const S< float>到V< float>通过V< float>(const S< T>& s)构造函数.
1.3秒标准转换:V< float>到const V< float>通过资格转换.
>调用默认的V< float> :: operator =(const& V< float>).
表达2?
我不明白的是为什么第二个表达式存在问题.为什么以下顺序不可能?
> c被转换为V< float>.为此,我们有一个用户定义的转换序列:
1.1.初始标准转换:S< float>到const S< float>通过资格转换.
1.2.用户定义的转换:const S< float>到V< float>通过V< float>(const S< T>& s)构造函数.
1.3.最终标准转换:V< float>到const V< float>通过资格转换.
>步骤2至6与表达式1的情况相同.
读完C标准后我说:’嘿!也许问题必须与13.3.3.1.2.3!’其中说明:
If the user-defined conversion is specified by a template conversion function,the second standard conversion sequence must have exact match rank.
但情况并非如此,因为资格转换具有完全匹配等级……
我真的不知道……
好吧,不管你有没有答案,谢谢你在这里阅读:)
解决方法
template<class T> v<T> operator+(V<T> const&,V<T> const&); ~~~~~~~~~~~ ~~~~~~~~~~~~
但是您尝试使用V< float>调用此函数模板在左侧,S在右侧.模板参数推导导致左侧T =浮动,右侧会出现错误,因为没有T,因此V< T>.等于S< T>.这有资格作为模板参数推导失败,简单地忽略模板.
如果您想允许转换,则您的操作符不应该是模板.有以下技巧:您可以将其定义为V的类模板中的内联朋友:
template<class T> class V { public: V(); V(S<T> const&); // <-- note: no explicit keyword here friend V<T> operator+(V<T> const& lhs,V<T> const& rhs) { ... } };
这样,运算符就不再是模板了.因此,不需要模板参数推导,您的调用应该起作用.通过ADL(参数相关查找)找到运算符,因为左侧是V< float>.右侧适当地转换为V< float>同样.
也可以禁用特定参数的模板参数推断.例如:
template<class T> struct id {typedef T type;}; template<class T> T clip( typename id<T>::type min,T value,typename id<T>::type max ) { if (value<min) value=min; if (value>max) value=max; return value; } int main() { double x = 3.14; double y = clip(1,x,3); // works,T=double }
即使第一个和最后一个参数的类型是int,但在模板参数推断期间不考虑它们,因为id< T> :: type不是所谓的* deducible context`.所以,T只是根据第二个参数推导出来,这导致T = double而没有矛盾.