深入探索SFINAE

一、sfinae 知乎

SFINAE(stubstitution failure is not an error)是一種C++編譯器技術,用於在編譯時根據條件包含或排除模板參數。在模板元編程中,SFINAE通常用於在編譯期間選擇模板函數重載。該技術的實行依賴於編譯器的模板類型推斷和重載解析規則。

對於那些包括函數特化和模板特化等類型選擇的模板函數,編譯器必須能夠將提供的參數連接到正確的函數或模板實例上。SFINAE技術可以讓編譯器在這個選擇過程中轉而繼續嘗試,而不會引發編譯時錯誤。

例如:

template &lttypename T&gt
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&ltbool expr, T&gt::type的形式。在這個結構中,expr會被編譯器檢查,如果為false,那麼std::enable_if不能生成任何類型,這意味着編譯器將跳過該特定函數,直到找到一個合適的重載。

例如:

template &lttypename T&gt
typename std::enable_if&ltstd::is_floating_point&ltT&gt::value, void&gt::type
foo(T t) {
    std::cout &lt&lt "foo(T)\n";
}
template &lttypename T&gt
typename std::enable_if&ltstd::is_integral&ltT&gt::value, void&gt::type
foo(T t) {
    std::cout &lt&lt "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 &lttypename T&gt
    typename std::enable_if&ltstd::is_same&ltT, int&gt::value, void&gt::type
    foo() {
        std::cout &lt&lt "int - A::foo()\n";
    }
    template &lttypename T&gt
    typename std::enable_if&ltstd::is_same&ltT, float&gt::value, void&gt::type
    foo() {
        std::cout &lt&lt "float - A::foo()\n";
    }
};
int main() {
    A a;
    a.foo&ltint&gt(); //1
    a.foo&ltfloat&gt();
    return 0;
}

在這個示例中,類A包含兩個使用SFINAE技術的成員函數。在函數中,我們使用std::enable_if來使我們的成員函數只能被調用一定類型的實例。這個程序將輸出一次「int – A::foo()」和一次「float – A::foo()」。

五、sfinae 與 重載的區別

與使用函數重載相比,SFINAE技術優點在於,使用SFINAE時錯誤不會直接導致程序崩潰。重載不像使用SFINAE那樣靈活,因為一旦重載解析失敗,編譯器只會報錯並停止編譯,而使用SFINAE技術則可以使解析失敗的函數不會被編譯器選中,然後繼續選擇其他適當的重載,即它可以保持程序正常進行。

六、sfinae對類內部聲明有效嗎

類內部包含的SFINAE聲明實際上是不允許的。這個時候我們必須在類外部聲明SFINAE的模板特化。例如:

template &lttypename T, typename = void>
struct foo {
    static const bool value = false;
};

template &lttypename T&gt
struct foo&ltT, typename std::enable_if&ltsizeof(T) % 2 == 0&gt::type> {
    static const bool value = true;
};

struct A {
    int x;
};

int main() {
    std::cout &lt&lt std::boolalpha &lt&lt foo&ltA&gt::value; //1
    return 0;
}

上面代碼中,如果我們在A內部使用SFINAE,會報錯,但我們可以用foo在類外部聲明SFINAE的模板特化。

七、sfinae和type traits

C++11的type traits庫中包含一系列的預定義類型特性,例如is_pointer&ltT&gt和is_reference&ltT&gt,這些類型特性常用於SFINAE技術的實現。

例如:

template &lttypename T&gt
typename std::enable_if&ltstd::is_pointer&ltT&gt::value, void&gt::type
foo(T t) {
    std::cout &lt&lt "pointer - foo()\n";
}
template &lttypename T&gt
typename std::enable_if&ltstd::is_reference&ltT&gt::value, void&gt::type
foo(T t) {
    std::cout &lt&lt "reference - foo()\n";
}
int main() {
    int x = 0;
    int* p = &x;
    foo(p); //1
    foo(x);
    return 0;
}

在這個例子中,我們使用了std::is_pointerstd::is_reference。與std::enable_if一起使用可以使重載函數不被錯誤的選擇。

原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/283222.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-12-22 08:07
下一篇 2024-12-22 08:07

相關推薦

  • 深入解析Vue3 defineExpose

    Vue 3在開發過程中引入了新的API `defineExpose`。在以前的版本中,我們經常使用 `$attrs` 和` $listeners` 實現父組件與子組件之間的通信,但…

    編程 2025-04-25
  • 深入理解byte轉int

    一、位元組與比特 在討論byte轉int之前,我們需要了解位元組和比特的概念。位元組是計算機存儲單位的一種,通常表示8個比特(bit),即1位元組=8比特。比特是計算機中最小的數據單位,是…

    編程 2025-04-25
  • 深入理解Flutter StreamBuilder

    一、什麼是Flutter StreamBuilder? Flutter StreamBuilder是Flutter框架中的一個內置小部件,它可以監測數據流(Stream)中數據的變…

    編程 2025-04-25
  • 深入探討OpenCV版本

    OpenCV是一個用於計算機視覺應用程序的開源庫。它是由英特爾公司創建的,現已由Willow Garage管理。OpenCV旨在提供一個易於使用的計算機視覺和機器學習基礎架構,以實…

    編程 2025-04-25
  • 深入了解scala-maven-plugin

    一、簡介 Scala-maven-plugin 是一個創造和管理 Scala 項目的maven插件,它可以自動生成基本項目結構、依賴配置、Scala文件等。使用它可以使我們專註於代…

    編程 2025-04-25
  • 深入了解LaTeX的腳註(latexfootnote)

    一、基本介紹 LaTeX作為一種排版軟件,具有各種各樣的功能,其中腳註(footnote)是一個十分重要的功能之一。在LaTeX中,腳註是用命令latexfootnote來實現的。…

    編程 2025-04-25
  • 深入剖析MapStruct未生成實現類問題

    一、MapStruct簡介 MapStruct是一個Java bean映射器,它通過註解和代碼生成來在Java bean之間轉換成本類代碼,實現類型安全,簡單而不失靈活。 作為一個…

    編程 2025-04-25
  • 深入探討馮諾依曼原理

    一、原理概述 馮諾依曼原理,又稱「存儲程序控制原理」,是指計算機的程序和數據都存儲在同一個存儲器中,並且通過一個統一的總線來傳輸數據。這個原理的提出,是計算機科學發展中的重大進展,…

    編程 2025-04-25
  • 深入理解Python字符串r

    一、r字符串的基本概念 r字符串(raw字符串)是指在Python中,以字母r為前綴的字符串。r字符串中的反斜杠(\)不會被轉義,而是被當作普通字符處理,這使得r字符串可以非常方便…

    編程 2025-04-25
  • 深入了解Python包

    一、包的概念 Python中一個程序就是一個模塊,而一個模塊可以引入另一個模塊,這樣就形成了包。包就是有多個模塊組成的一個大模塊,也可以看做是一個文件夾。包可以有效地組織代碼和數據…

    編程 2025-04-25

發表回復

登錄後才能評論