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
.
3. 解决方案
C++
的模板匹配有个特点, 编译器始终会寻找类型匹配最精确的模板. 当然并不一定所有的模板都能匹配,
一旦有某个模板匹配不成功, 编译器会自动尝试别的候选模板, 要是所有的都不成功那编译器就匹配失败, 有的时候我们想故意跳过某些精确度高模板匹配,
而使用精确度低的模板, 这个时候就可以利用SFINAE
故意让编译器匹配失败. 回到案例,
我们希望检查一个类型是否有ToString
方法, 例如:
class A { char* ToString(); };class B { };
这时我们在代码里面写A::ToString
, 自然没有什么问题,
但是如果写B::ToString
的话编译将告诉你找不到这个符号. 我们可以利用这个错误来跳过某些模板的匹配,
而使得别的模板可以得到匹配. 例如以下代码:
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方法,
第一个的匹配精度高于第二个的.
当编译器解析Tester<ClassType>(NULL)
的时候,
编译器首先会尝试用ClassType
以及他的一个ClassType::ToString
方法去实例化一个FuncMatcher
类型来匹配第一个Tester
函数.
对于A来说, 这是能通过的.
但是对于B来说, 因为其没有ToString
方法,
所以不能用B以及不存在的B::ToString
来实例化FuncMatcher
.
这个时候编译器实际上就已经发现错误了, 但是根据SFINAE原则这个只能算是模板匹配失败, 不能算错误,
所以编译器会跳过这次对FuncMatcher
的匹配. 但是跳过了以后也就没有别的匹配了,
所以整个第一个Tester
来说对B都是不能匹配成功的,
这个时候优先级比较低的第二个Tester
自然就能匹配上了.
我们就可以利用这一点来实现我们最开始的想要AnyToString
方法:
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 };