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賦值的。返回的結構定義可以依實際情況再進行擴展,按需要區分的狀態多定義幾種不同字節數的類型即可。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章