我有一个班级模板CFoo<T>
。我想允许隐式转换为CFoo
的其他实例化,但仅限于那些模板参数是T
基类的人。
我尝试使用SFINAE,但我的尝试都没有用于我尝试的任何编译器(VC 2012或gcc):
#include <type_traits>
template <class T> class CFoo {
public:
template <class Q> operator
// typename std::enable_if<std::is_base_of<Q, T>::value, CFoo<Q>&>::type // SHOULD WORK?
// typename std::enable_if<1, CFoo<Q>&>::type // SHOULD WORK?
CFoo<Q>& // compiles, but doesn't restrict on Q like I want
() const {
return *(CFoo<Q>*)this;
}
};
class A {};
class B : public A {};
int main(int argc, char* argv[])
{
CFoo<B> b;
CFoo<A>& a = b;
return 0;
}
为什么SFINAE上任何一个已注释的尝试都没有在这里工作?在这两种情况下,我只是因a
的无效初始化而出错,就好像我的运算符没有被调用一样。
答案 0 :(得分:7)
根据[temp.deduct.conv]:
模板参数推导是通过比较转换函数模板的返回类型(调用)来完成的 P)具有转换结果所需的类型(称之为A;见8.5,13.3.1.5和13.3.1.6) 用于确定该类型的,如14.8.2.5所述。
在简单的情况下:
template <class Q>
operator CFoo<Q>& const;
这很简单,我们尝试针对CFoo<Q>&
推断CFoo<A>&
。该部分还有其他规则,但最终扣除成功Q == A
。
出于同样的原因,你的其他两次尝试都失败了。我会选择更简单的一个:
template <class Q>
operator typename std::enable_if<1, CFoo<Q>&>::type const;
在此,我们尝试推断typename std::enable_if<1, CFoo<Q>&>::type
。这是一个非推断的上下文(它是使用 qualified-id 指定的类型的嵌套名称说明符),因此推断失败。因此,不会考虑此转换函数,因此分配失败,因为没有找到转换。
您需要将返回类型作为推断的上下文,因此SFINAE必须转到此处:
template <class Q,
typename = std::enable_if_t<std::is_base_of<Q, T>::value>>
operator CFoo<Q>& const;
这样,我们就可以推断(CFoo<Q>&
) - 并且扣除可以成功(如果Q
是T
的基础):
CFoo<A>& a = b; // OK
CFoo<int>& i = b; // deduction failure on Q, so there's no viable conversion function
// so this is an error
那就是说,当我解决模板谜题时,正如T.C.指出的那样,这真的不是一个好的解决方案,因为:
return *(CFoo<Q>*)this;
只做一个reinterpret_cast
(以及一个const_cast
)所以它真的不可能做任何合理的事情,你几乎肯定会(除非CFoo
是微不足道的)最终得到未定义的行为试图以错误的类型访问其成员。
您可能想要添加转化构造函数而不是转换函数:
template <typename Q,
typename = std::enable_if_t<std::is_base_of<T, Q>::value>>
CFoo(CFoo<Q> const& ) { }
这样,当你这样做时:
CFoo<A> a = b; // a is not a reference anymore
您正在构建一个必然有效的 new 对象。