一、sfinae 知乎
SFINAE(stubstitution failure is not an error)是一種C++編譯器技術,用於在編譯時根據條件包含或排除模板參數。在模板元編程中,SFINAE通常用於在編譯期間選擇模板函數重載。該技術的實行依賴於編譯器的模板類型推斷和重載解析規則。
對於那些包括函數特化和模板特化等類型選擇的模板函數,編譯器必須能夠將提供的參數連接到正確的函數或模板實例上。SFINAE技術可以讓編譯器在這個選擇過程中轉而繼續嘗試,而不會引發編譯時錯誤。
例如:
template <typename T> void foo(T t) { typename T::type* ptr; //這裡的typename就是用於把表達式T::type為類型說明符而不是成員說明符 } struct S { using type = int; }; int main() { foo(S()); foo(int()); //1 return 0; }
上面代碼中,foo(int())
是不合法的,因為int類型沒有type成員。SFINAE規則使得編譯器選擇忽略這個錯誤,所以代碼可以成功編譯。
二、sfinae overload
使用SFINAE技術,一個函數可以通過特定的方式來處理在編譯器選擇錯誤重載時必然會出現的情況。這通常通過創建一個函數的包含額外模板參數的重載來實現,而這些參數具有定義為typename std::enable_if<bool expr, T>::type
的形式。在這個結構中,expr會被編譯器檢查,如果為false,那麼std::enable_if不能生成任何類型,這意味着編譯器將跳過該特定函數,直到找到一個合適的重載。
例如:
template <typename T> typename std::enable_if<std::is_floating_point<T>::value, void>::type foo(T t) { std::cout << "foo(T)\n"; } template <typename T> typename std::enable_if<std::is_integral<T>::value, void>::type foo(T t) { std::cout << "foo(T)\n"; } int main() { foo(3.14f); //1 foo(42); return 0; }
在這個例子中,兩個foo函數都接受一個參數T,但他們都有不同的enable_if類型,並且在不同的類型條件下啟用。這個程序將輸出兩次”foo(T)”,分別適用於浮點型和整型參數。
三、sfinae 發音
SFINAE 所代表的單詞很長,這也使得它的發音頗受爭議。筆者曾經經常發音為「sifnae」,但實際上,最常見的發音是「s-fin-ay」,並且有時候被縮寫為「S」或「SFINAE」。
四、sfinae 成員函數
使用SFINAE技術,可以很方便地為函數重載提供額外條件。但要注意的是,SFINAE在成員函數中的使用和使用於外部函數有所不同,這與模板參數決定了如何編寫這樣的代碼。作為成員函數,我們需要使用decltype來使用其所屬的類,在enable_if的條件語句中,我們要使用std::declval來代替未知類型的實例,在enable_if後面的語句裏面可以調用我們想要的函數。
例如:
struct A { template <typename T> typename std::enable_if<std::is_same<T, int>::value, void>::type foo() { std::cout << "int - A::foo()\n"; } template <typename T> typename std::enable_if<std::is_same<T, float>::value, void>::type foo() { std::cout << "float - A::foo()\n"; } }; int main() { A a; a.foo<int>(); //1 a.foo<float>(); return 0; }
在這個示例中,類A包含兩個使用SFINAE技術的成員函數。在函數中,我們使用std::enable_if來使我們的成員函數只能被調用一定類型的實例。這個程序將輸出一次「int – A::foo()」和一次「float – A::foo()」。
五、sfinae 與 重載的區別
與使用函數重載相比,SFINAE技術優點在於,使用SFINAE時錯誤不會直接導致程序崩潰。重載不像使用SFINAE那樣靈活,因為一旦重載解析失敗,編譯器只會報錯並停止編譯,而使用SFINAE技術則可以使解析失敗的函數不會被編譯器選中,然後繼續選擇其他適當的重載,即它可以保持程序正常進行。
六、sfinae對類內部聲明有效嗎
類內部包含的SFINAE聲明實際上是不允許的。這個時候我們必須在類外部聲明SFINAE的模板特化。例如:
template <typename T, typename = void> struct foo { static const bool value = false; }; template <typename T> struct foo<T, typename std::enable_if<sizeof(T) % 2 == 0>::type> { static const bool value = true; }; struct A { int x; }; int main() { std::cout << std::boolalpha << foo<A>::value; //1 return 0; }
上面代碼中,如果我們在A內部使用SFINAE,會報錯,但我們可以用foo在類外部聲明SFINAE的模板特化。
七、sfinae和type traits
C++11的type traits庫中包含一系列的預定義類型特性,例如is_pointer<T>和is_reference<T>,這些類型特性常用於SFINAE技術的實現。
例如:
template <typename T> typename std::enable_if<std::is_pointer<T>::value, void>::type foo(T t) { std::cout << "pointer - foo()\n"; } template <typename T> typename std::enable_if<std::is_reference<T>::value, void>::type foo(T t) { std::cout << "reference - foo()\n"; } int main() { int x = 0; int* p = &x; foo(p); //1 foo(x); return 0; }
在這個例子中,我們使用了std::is_pointer
和std::is_reference
。與std::enable_if
一起使用可以使重載函數不被錯誤的選擇。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/283222.html