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