C++技巧: SFINAE

SFINAE是Substitution Failure Is Not An Error的缩写,即利用编译器的模板匹配失败来实现某些功能。C++中模板匹配遵循尽可能精确的原则,当最精确的模板匹配失败时会再尝试候选模板,因此利用同名但模板参数不同的多个模板函数可以达到编译器检查或判断的目的。
比如,对于多个module_xx类,需要检查其是否存在get_name函数,以采用不同的处理。
假设两个类的定义如下:
class module_good
{
public:
    const char* get_name(){}
};
 
class module_bad
{
};
则可以构建如下的检查器:
struct has_get_name
{
    template<typename T, const char* (T::*)()>
    struct func_matcher;
 
public:
    template<typename T>
    static bool test(func_matcher<T, &T::get_name>*)
    {
        return true;
    }
 
    template<typename T>
    static bool test(...)
    {
        return false;
    }
};
 
void test3()
{
    bool b_ret = false;
    b_ret = has_get_name::test<module_good>(NULL);
    b_ret = has_get_name::test<module_bad>(NULL);
}
对module_good,其包含get_name函数,按照精确匹配的规则其会优先匹配到第一个test函数定义,而对module_bad因其不包含get_name函数故在匹配失败第一个test定义后匹配到第二个。
以上的检查是在运行时检查的,如果经常将会有许多不必要的开销,因为类的定义是在编译时已经确定的,我们可以把这个检查改为在编译时进行以优化性能。
注意, static bool test(func_matcher<T, &T::get_name>*),这里如果使用默认值=NULL,虽然调用时会简洁一些,但在匹配类中的静态函数时有可能出现无法匹配的情况。我测试时在VS下正常,但在GCC下会出现ambiguous,即编译器无法确认哪个更精确,所以保险起见推荐不要使用默认值。
template<typename T>
struct has_get_name2
{
    typedef char true_t[1];
    typedef char false_t[2];
 
    template<typename T2, const char* (T::*)()>
    struct func_matcher;
    
    template<typename T2>
    static true_t& test(func_matcher<T2, &T2::get_name>*);
 
    template<typename T2>
    static false_t& test(...);
 
    enum
    {
        result = (sizeof(test<T>(NULL)) == sizeof(true_t))
    };
};
 
void test4()
{
    bool b_ret = false;
    b_ret = has_get_name2<module_good>::result;
    b_ret = has_get_name2<module_bad>::result;
}
通过区分函数的返回值类型来得到检查结果,这样就把检查的性能成本放在了编译阶段,查看汇编代码可以发现是直接将1或者0赋值的。返回的结构定义可以依实际情况再进行扩展,按需要区分的状态多定义几种不同字节数的类型即可。

你可能感兴趣的:(C++,error,技巧,编译器,SFINAE)