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

  • 相关阅读:
    操作系统上机实验
    选择排序
    插入排序(c++)
    嵌入式原理实验代码集合
    iOS应用程序生命周期(前后台切换,应用的各种状态)详解
    ios Base64编解码工具类及使用
    iOS:横向使用iPhone默认的翻页效果
    ios学习笔记之内存管理
    ios NavBar+TarBar技巧
    IOS设备滑动事件
  • 原文地址:https://www.cnblogs.com/xusd-null/p/3761239.html
Copyright © 2020-2023  润新知