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.spa

3. 解決方案

C++的模板匹配有個特色, 編譯器始終會尋找類型匹配最精確的模板. 固然並不必定全部的模板都能匹配, 一旦有某個模板匹配不成功, 編譯器會自動嘗試別的候選模板, 要是全部的都不成功那編譯器就匹配失敗, 有的時候咱們想故意跳過某些精確度高模板匹配, 而使用精確度低的模板, 這個時候就能夠利用SFINAE故意讓編譯器匹配失敗. 回到案例, 咱們但願檢查一個類型是否有ToString方法, 例如:翻譯

class A { char* ToString(); };

class B { };

這時咱們在代碼裏面寫A::ToString, 天然沒有什麼問題, 可是若是寫B::ToString的話編譯將告訴你找不到這個符號. 咱們能夠利用這個錯誤來跳過某些模板的匹配, 而使得別的模板能夠獲得匹配. 例如如下代碼:code

 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方法, 第一個的匹配精度高於第二個的.blog

當編譯器解析Tester<ClassType>(NULL)的時候, 編譯器首先會嘗試用ClassType以及他的一個ClassType::ToString方法去實例化一個FuncMatcher類型來匹配第一個Tester函數. 對於A來講, 這是能經過的.字符串

可是對於B來講, 由於其沒有ToString方法, 因此不能用B以及不存在的B::ToString來實例化FuncMatcher.編譯器

這個時候編譯器實際上就已經發現錯誤了, 可是根據SFINAE原則這個只能算是模板匹配失敗, 不能算錯誤, 因此編譯器會跳過此次對FuncMatcher的匹配. 可是跳過了之後也就沒有別的匹配了, 因此整個第一個Tester來講對B都是不能匹配成功的, 這個時候優先級比較低的第二個Tester天然就能匹配上了. 咱們就能夠利用這一點來實現咱們最開始的想要AnyToString方法:string

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 };
相關文章
相關標籤/搜索