日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

【转】C++中的SFINAE

發布時間:2024/10/12 c/c++ 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【转】C++中的SFINAE 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
C++中的SFINAE?2011-05-11 15:45:20

分類:?C/C++

?

這幾天神游到一段is_base_of的代碼迷惑了很久, 在查資料的過程當中, 發現C++中一種稱之為SFINAE的技巧, 全稱為"匹配失敗并不是一種錯誤(Substitution Failure Is Not An Error)". 這是一種專門利用編譯器匹配失敗來達到某種目的的技巧.

在說明之前先說說模板匹配的原則:?
非模板函數具有最高優先權, 如果不存在匹配的非模板函數的話, 那么最匹配的和最特化的具有最高的優先權.

C++中,函數模板與同名的非模板函數重載時,應遵循下列調用原則:
  • 尋找一個參數完全匹配的函數,若找到就調用它。若參數完全匹配的函數多于一個,則這個調用是一個錯誤的調用。
  • 尋找一個函數模板,若找到就將其實例化生成一個匹配的模板函數并調用它。
  • 若上面兩條都失敗,則使用函數重載的方法,通過類型轉換產生參數匹配,若找到就調用它。
  • 若上面三條都失敗,還沒有找都匹配的函數,則這個調用是一個錯誤的調用。
?

至于函數的選擇原則, 可以看看C++ Primer中的說明:

  • 創建候選函數列表,其中包含與被調用函數名字相同的函數和模板函數。
  • 使用候選函數列表創建可行的函數列表。這些都是參數數目正確的函數,并且有一個隱式的轉換序列(參數類型轉化),其中包括實參類型與相應的形參類型完全匹配情況。
  • 確定是否有最佳的可行函數,有則調用它,沒有則報錯。
??? 可行函數的最佳性,主要是判斷使用函數的參數與可行性函數的參數的轉換規則進行判斷,從最佳到最差的順序如下所示:
  • 完全匹配,但常規函數優先于顯示定義模板函數,而顯示定義模板函數優先于模板函數。
  • 提升轉換,即從小精度數據轉換為高精度數據類型,如char/short 轉換為int , int轉化為long,float轉換為double。
  • 標準轉換,如int轉化為char,long轉化為double等
  • 用戶自定義轉換。

下面先看看帶有默認值的模板函數特化和非特化的問題
1 template<typename T, bool C = true> 2 struct if_ { 3 static const int value = 1; 4 }; 5 6 template<typename T> 7 struct if_<T, true> { 8 static const int value = 2; 9 }; 10 11 int main() { 12 printf("value: %d\n", if_<int>::value); 13 }

?

上面的輸入結果是: value: 2. 編譯器在進行匹配的時候, 就如Prime上說的, 編譯器會先創建候選函數列表, 在創建候選列表的過程中C已經被賦予默認是true, 然后在進行匹配, 最高優先級是函數, 顯然這里沒有. 然后是最匹配和最特化的模板函數, 所以, 就匹配到第二個函數了, 因此value等于2.?

實際上, 上面的例子相當于
1 template<typename T, bool C = true> 2 struct if_ {}; 3 4 template<typename T> 5 struct if_<T, false> { 6 static const int value = 1; 7 }; 8 9 template<typename T> 10 struct if_<T, true> { 11 static const int value = 2; 12 }; 13 14 int main() { 15 printf("value: %d\n", if_<int>::value); 16 }

?

因此, 編譯器總是先找到函數和模板, 然后根據默認值, 類型轉換進行匹配, 然后再根據優先級選擇最可行的.

-----------------------------------------華麗的分割線--------------------------------------------------

說到這里應該大致明白重載函數, 函數模板, 特化和偏特化的一些匹配問題. (其實還有一個問題沒有說清楚, 后面會提到). 現在來看看SFINAE的一個應用.

要實現一個通用的序列化函數叫做toString, 它可以實現把任何類型序列化成字符串.

1 template<typename T> std::string toString(const T &x); 但是我們希望能檢測到T是否有自己的toString方法, 如果有就直接調用, 如果沒有就調用通用的方法, 例如僅僅返回類的名稱 typeid(T).name().?

現在有兩個類
1 class A { 2 public: 3 std::string toString() const { 4 return std::string("toString from class A"); 5 } 6 }; 7 8 class B { 9 }; 這時代碼里面有A::toString 就沒有問題, 但是編譯器找不到B::toString, 利用這個錯誤來跳過模板的匹配, 從而使得別的模板得以匹配. 1 template<typename T> 2 struct HasToStringFunction { 3 template<typename U, std::string (U::*)() const > 4 struct matcher; 5 6 template<typename U> 7 static char helper(matcher<U, &U::toString> *); 8 9 template<typename U> 10 static int helper(...); 11 12 enum { value = sizeof(helper<T>(NULL)) == 1 }; 13 };

?

這里有兩個helper方法, 第一個匹配精確度要高于第二個. 因此, 編譯器會先嘗試用U和U::toString去實例化一個matcher去匹配helper, 對于A這是能通過的, 但是對于B, 由于B::toString不存在, 這個時候編譯器實際上就已經發現錯誤了,但是根據SFINAE原則這個只能算是模板匹配失敗,不能算錯誤,所以編譯器會跳過這次對matcher的匹配。但是跳過了以后也就沒有別的匹配了,所以整個第一個helper來說對B都是不能匹配成功的,這個時候優先級比較低的第二個helper自然就能匹配上了。

利用這點就可以實現toString方法
1 template <bool> 2 struct ToStringWrapper {}; 3 4 template<> 5 struct ToStringWrapper<true> { 6 template<typename T> 7 static std::string toString(T &x) { 8 return x.toString(); 9 } 10 }; 11 12 template<> 13 struct ToStringWrapper<false> { 14 template<typename T> 15 static std::string toString(T &x) { 16 return std::string(typeid(x).name()); 17 } 18 }; 19 20 template<typename T> 21 std::string toString(const T &x) { 22 return ToStringWrapper<HasToStringFunction<T>::value>::toString(x); 23 } 24 25 int main() { 26 A a; 27 B b; 28 29 std::cout << toString(a) << std::endl; 30 std::cout << toString(b) << std::endl; 31 }

這里有兩個小技巧, 一個是sizeof()一個函數, 返回的是函數返回值的大小.?另外一個是U::*表示類成員函數指針.?比如std::string (*)() const 表明這是一個函數指針, 而std::string (U::*)() const表示這是一個類的成員函數.?


這樣我們可以實現一個判斷類型是基本類型還是一個類的類模板is_class
1 template <typename T> 2 class is_class { 3 template <typename U> 4 static char helper(int U::*); 5 template <typename U> 6 static int helper(...); 7 public: 8 static const bool value = sizeof(helper<T>(0)) == 1; 9 }; 這里的原因不再重復.

------------------------------------------------華麗的分割線--------------------------------------------
現在終于輪到大名鼎鼎的is_base_of出場
1 template <typename T1, typename T2> 2 struct is_same { 3 static const bool value = false; 4 }; 5 6 template <typename T> 7 struct is_same<T, T> { 8 static const bool value = true; 9 }; 10 11 template<typename Base, typename Derived, bool = (is_class<Base>::value && is_class<Derived>::value)> 12 class is_base_of { 13 template <typename T> 14 static char helper(Derived, T); 15 static int helper(Base, int); 16 struct Conv { 17 operator Derived(); 18 operator Base() const; 19 }; 20 public: 21 static const bool value = sizeof(helper(Conv(), 0)) == 1; 22 }; 23 24 template <typename Base, typename Derived> 25 class is_base_of<Base, Derived, false> { 26 public: 27 static const bool value = is_same<Base, Derived>::value; 28 }; 29 30 template <typename Base> 31 class is_base_of<Base, Base, true> { 32 public: 33 static const bool value = true; 34 }; 先來看看is_base_of模板的第三個參數, 如果前兩個類型都是類的話, 則為true, 否則為false. 下面一個個情況來分析:

第一種情況是有一個基本類型, 顯然, is_base_of第三個參數為false, 按照上面說到的原則匹配到了第二個類模板. 在該情況下只有當兩個類型一致時is_base_of的value才為true, 否則為false.?


第二種情況是Base和Derived是同一個類型, 則會匹配到第三個模板.

第三種情況
是Base和Derived有繼承關系, 此時編譯器只能匹配第一個類模板. 在helper(Conv(), 0)中, 顯然沒有helper的第一個參數無法直接匹配, Conv()默認也無法轉換成Base或者是Derived. 因此需要調用自定義的轉換函數. 當試圖匹配int helper(Base, int)的時候, 編譯有兩個途徑, 一個是: Conv()->Derived()->Base(), 第三步是默認, 另一個是Conv()->const Conv()->Base(), 這實際上是一個SFINAE, 根據原則編譯器會繼續匹配下一個模板, 匹配成功, 因此value的值為true.

第四種情況是Base和Derived沒有繼承關系,?
當試圖匹配int helper(Base, int)的時候能通過Conv()->const Conv()->Base()匹配成功, 因為兩者不是繼承關系, 因此Derived()->Base()的默認轉換不會成功, 因此該情況下此路徑不存在. 所以編譯器選擇的函數是int helper(Base, int), 最后, value的值為false.


----------------------------------- 華麗的分割線 ------------------------------------------------------

結束語:?
無語.

轉載于:https://www.cnblogs.com/gx520/p/4614717.html

總結

以上是生活随笔為你收集整理的【转】C++中的SFINAE的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。