C++ SFINAE

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

 

 

 

posted on 2014-05-30 15:46 xusd-null 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/xusd-null/p/3761239.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SFINAE(Substitution Failure Is Not An Error)是C++模板元编程中的一种技术,它利用了模板参数替换失败不报错的特性,可以在编译期间自动过滤掉不符合条件的模板实例化。 SFINAE的基本用法是通过函数重载和模板特化来实现的。具体步骤如下: 1. 定义一个模板函数或者模板类,其中包含一个或多个“不确定”的类型参数。 2. 在函数或者类中使用这些模板参数来实现具体功能。 3. 定义一个辅助函数或者类,用于检查某些条件是否成立。 4. 在辅助函数或者类中定义一个静态常量或者静态函数,用于返回bool类型的值表示条件是否成立。 5. 使用函数重载或者模板特化的方式,根据条件成立与否来选择具体的实现。 下面是一个简单的示例代码,演示了如何使用SFINAE来实现一个函数模板,用于计算两个数中较大的那个数: ```cpp #include <type_traits> template<typename T> typename std::enable_if<std::is_integral<T>::value, T>::type max(T a, T b) { return a > b ? a : b; } template<typename T> typename std::enable_if<std::is_floating_point<T>::value, T>::type max(T a, T b) { return a > b ? a : b; } int main() { int i = 1, j = 2; float x = 1.0f, y = 2.0f; max(i, j); // 返回值为2 max(x, y); // 返回值为2.0f return 0; } ``` 在上面的代码中,我们定义了两个模板函数max,一个处理整数类型,另一个处理浮点类型。为了保证模板参数只能是整数类型或者浮点类型,我们使用了std::enable_if和std::is_integral/std::is_floating_point来进行条件判断。如果条件成立,那么函数返回类型就是T,否则函数不会被实例化。在main函数中,我们分别调用了两个max函数,得到了正确的结果。 需要注意的是,SFINAE技术并不适用于所有情况,有些复杂的类型判断可能需要使用更高级的技术。此外,SFINAE还有一些陷阱和注意事项,需要谨慎使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值