打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
SFINAE

SFINAE

什么是SFINAE

在C++中有很多的编程技巧(Trick),SFINAE就是其中一种,他的全义可以翻译为“匹配失败并不是一个错误(Substitution failure is not an error)”。简单来说他就是专门利用编译器匹配失败的一种技巧。

案例

比如我们想实现一个通用的函数叫AnyToString,他可以实现任意类型的数据转成字符串:

1
2
template<typename ValueType>
char* AnyToString(const ValueType& value);

我们更希望这个函数能检查ValueType类型自己有没有ToString方法,如果有就直接调用,没有的话就采取通用的处理方案。但是C++没有反射机制,不能像C#那样通过TypeInfo来检查,更没有像Java那样纯粹的OOP,从最基类就定义了ToString方法,下面的子类只用负责重载。
所以我们希望能有一种方法能让C++也能检查某个类型是否定义了某个成员函数,这就可以用到SFINAE。

解决方案

C++的模板匹配有个特点,编译器始终会寻找类型匹配最精确的模板。当然并不一定所有的模板都能匹配,一旦有某个模板匹配不成功,编译器会自动尝试别的候选模板。要是所有的都不成功那编译器就匹配失败。有的时候我们想故意跳过某些精确度高模板匹配,而使用精确度低的模板,这个时候就可以利用SFINAE故意让编译器匹配失败。
回到案例,我们希望检查一个类型是否有ToString方法,例如:

1
2
3
4
5
6
7
8
class A
{
    char* ToString();
};
 
class B
{
};

这时我们在代码里面写A::ToString,自然没有什么问题,但是如果写B::ToString的话编译将告诉你找不到这个符号。我们可以利用这个错误来跳过某些模板的匹配,而使得别的模板可以得到匹配。例如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<typename ClassType>
struct HasToStringFunction
{
    typedef struct { char[2]; } Yes;
    typedef struct { char[1]; } No;
 
    template<typename FooType, char* (FooType::*)()>
    struct FuncMatcher;
 
    template<typename FooType>
    static Yes Tester(FuncMatcher<FooType, &FooType::ToString>*);
 
    template<typename FooType>
    static No Tester(...);
 
    enum
    {
        Result = sizeof(Tester<ClassType>(NULL)) == sizeof(Yes)
    };
};
 
bool a_has_tostring = HasToStringFunction<A>::Result;   // True
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方法:

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
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);
}

Posted by Jay

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
编译期检查class是否有继承关系
编译期的断言(Assertion)
C标准库思维导图、C 关键字概览
C++类模板的成员函数模板写法收藏 ---------- 转 - 大龙的博客 - C++博...
C++语言的15个晦涩特性
<转载>独一无二的C++模板
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服