1. 什么是SFINAE
在C++中有不少的编程技巧(Trick), SFINAE
就是其中一种, 他的全义能够翻译为”匹配失败并非一个错误(Substitution failure is not an error)“. 简单来讲他就是专门利用编译器匹配失败的一种技巧.编程
2. 案例
好比咱们想实现一个通用的函数叫AnyToString
, 他能够实现任意类型的数据转成字符串:函数
1 template<typename ValueType> 2 char* AnyToString(const ValueType& value);
咱们更但愿这个函数能检查ValueType类型本身有没有ToString
方法, 若是有就直接调用, 没有的话就采起通用的处理方案. 可是C++
没有反射机制, 不能像C#那样经过TypeInfo
来检查, 更没有像Java
那样纯粹的OOP,从最基类就定义了ToString
方法,下面的子类只用负责重载。工具
因此咱们但愿能有一种方法能让C++
也能检查某个类型是否认义了某个成员函数, 这就能够用到SFINAE
.spa
3. 解决方案
C++
的模板匹配有个特色, 编译器始终会寻找类型匹配最精确的模板. 固然并不必定全部的模板都能匹配, 一旦有某个模板匹配不成功, 编译器会自动尝试别的候选模板, 要是全部的都不成功那编译器就匹配失败, 有的时候咱们想故意跳过某些精确度高模板匹配, 而使用精确度低的模板, 这个时候就能够利用SFINAE
故意让编译器匹配失败. 回到案例, 咱们但愿检查一个类型是否有ToString
方法, 例如:翻译
class A { char* ToString(); }; class B { };
这时咱们在代码里面写A::ToString
, 天然没有什么问题, 可是若是写B::ToString
的话编译将告诉你找不到这个符号. 咱们能够利用这个错误来跳过某些模板的匹配, 而使得别的模板能够获得匹配. 例如如下代码:code
1 template<typename ClassType> 2 struct HasToStringFunction { 3 typedef struct { char[2]; } Yes; 4 typedef struct { char[1]; } No; 5 6 template<typename FooType, char* (FooType::*)()> 7 struct FuncMatcher; 8 9 template<typename FooType> 10 static Yes Tester(FuncMatcher<FooType, &FooType::ToString>*); 11 12 template<typename FooType> 13 static No Tester(...); 14 15 enum { 16 Result = sizeof(Tester<ClassType>(NULL)) == sizeof(Yes) 17 }; 18 }; 19 20 bool a_has_tostring = HasToStringFunction<A>::Result; // True 21 bool b_has_tostring = HasToStringFunction<B>::Result; // False
这里有两个Tester方法, 第一个的匹配精度高于第二个的.blog
当编译器解析Tester<ClassType>(NULL)
的时候, 编译器首先会尝试用ClassType
以及他的一个ClassType::ToString
方法去实例化一个FuncMatcher
类型来匹配第一个Tester
函数. 对于A来讲, 这是能经过的.字符串
可是对于B来讲, 由于其没有ToString
方法, 因此不能用B以及不存在的B::ToString
来实例化FuncMatcher
.编译器
这个时候编译器实际上就已经发现错误了, 可是根据SFINAE原则这个只能算是模板匹配失败, 不能算错误, 因此编译器会跳过此次对FuncMatcher
的匹配. 可是跳过了之后也就没有别的匹配了, 因此整个第一个Tester
来讲对B都是不能匹配成功的, 这个时候优先级比较低的第二个Tester
天然就能匹配上了. 咱们就能够利用这一点来实现咱们最开始的想要AnyToString
方法:string
template<bool> struct AnyToStringAdviser; template<> struct AnyToStringAdviser<true> { template<typename ValueType> static char* ToString(const ValueType& value) { return value.ToString(); } } template<> struct AnyToStringAdviser<false> { template<typename ValueType> static char* ToString(const ValueType& value) { /* Generic process */ } } template<typename ValueType> char* AnyToString(const ValueType& value) { return AnyToStringAdviser<HasToStringFunction<ValueType>::Result >::ToString(value); }
4. 再写一个经常使用的使用了该方法的traits工具类
1 template <typename T> 2 struct is_class{ 3 typedef char __one__; 4 typedef struct{ char[2]; } __two__; 5 6 template <typename U> 7 static __one__ test(int U::*){ } 8 9 template <typename U> 10 static __two__ test(...){ } 11 12 const static bool value = (sizeof(test<T>(NULL)) == sizeof(__one__)); 13 };