簡體   English   中英

部分專業結構vs重載功能模板

[英]Partially Specialized Structs vs Overloaded function template

眾所周知,函數模板不能部分專門用於C ++。 當您在概念上嘗試實現此目標時,您可以使用兩種可能的解決方案。 其中一個是使用帶有靜態函數的結構,可選地用模板函數包裝,如下所示:

template <class T, class U>
struct BarHelper
{
    static void BarHelp(T t, const U& u)
    {
        std::cerr << "bar general\n";
    }
};

template <class T>
struct BarHelper<T, double>
{
    static void BarHelp(T t, const double& u)
    {
        std::cerr << "bar specialized\n";
    }
};
template <class T, class U>
void bar(T t, const U& u)
{
    BarHelper<T, U>::BarHelp(t, u);
};

bar這里是可選的,你可以直接使用struct的靜態成員(盡管你必須明確指定所有參數)。

另一種方法是重載函數模板:

template <class T, class U>
void func(T t, const U& u)
{
    std::cerr << "func general\n";

}
template <class T>
void func(T t, const double& u)
{
    std::cerr << "func specialized\n";
}

對我來說,似乎第二種方法更可取。 對於初學者來說,它更加冗長,而且對於意圖更加清晰(我們正在編寫函數,所以讓我們使用函數而不是無意義的包裝器結構)。 此外,您可以使用一些很好的技巧來控制重載分辨率。 例如,您可以在繼承層次結構中包含非模板化“標記”參數,並使用隱式轉換來控制函數的優先級。 每當你在重載中具體指定一個類型時,你也會得到隱式轉換,如果你不喜歡這種行為,你可以在你的重載上使用enable_if來阻止它(讓你回到與結構相提並論)。

是否有理由偏愛部分專業結構? 這些原因有多普遍? 即哪個應該是你的“默認”? 如果您:a)計划自己實現所有特化,而b)這是否用作用戶可以注入自己行為的自定義點,這會有所不同嗎?

Herb Sutter有一篇關於避免功能模板專業化的着名博文。 在其中,他還建議(接近結尾)偏愛部分專門的結構來重載功能模板,但他似乎沒有給出任何具體的理由: http//www.gotw.ca/publications/mill17.htm

道德#2:如果你正在編寫一個函數庫模板,寧願把它寫成一個永遠不應該專門化或過載的單個函數模板

(重點補充)。

讓我們先列出創建同一模板方法的幾個變體的選項:

模板函數特化:不是一個選項,因為模板函數不能部分專門化 (參見此處此處此處的 SO線程)。

  1. 簡單的重載:這可以起作用, 正如提問和演示的問題
    但是,它並不總是很好,我們將在下面看到。

  2. 使用仿函數類部分特化:這是沒有模板函數特化的直接替代方法。

  3. along with template functions overloading: this approach can be selected when the simple template overloading doesn't work, see below. 使用以及模板函數重載:當簡單模板重載不起作用時,可以選擇此方法,請參見下文。

編輯:添加@Nir選項4

  1. 使用基於模板的函數參數:這個方法,如Nir在評論中提出的並在下面給出,可以實現模板函數重載,但是在調用方需要一些繁瑣的語法,見下文。

---編輯結束---

該問題提出了一種情況,當模板參數從調用中推導出來時,模板函數重載工作正常。 但是,如果對模板函數的調用直接提供模板參數,並且需要根據模板參數的關系或條件匹配實現,則重載不再有用。

考慮以下:

template <typename T, T val1, T val2>
void isSame1() {
    cout << "val1: " << val1 << ", val2: " << val2 << " are "
         << (val1==val2?" ":"NOT ") << "the same" << endl;
}

雖然在編譯時val1和val2是已知的,但是我們無法在編譯時知道它們是相同的情況下部分特殊化。 在這種情況下,函數重載沒有幫助,在兩個非類型模板參數具有相同值的情況下沒有重載。

通過類部分特化,我們可以做到:

template <typename T, T val1, T val2>
struct IsSameHelper {
    static void isSame() {
        cout << "val1: " << val1 << ", val2: " << val2 << " are NOT the same" << endl;
    }
};

// partial specialization
template <typename T, T val>
struct IsSameHelper<T, val, val> {
    static void isSame() {
        cout << "val1: " << val << ", val2: " << val << " are the same" << endl;
    }
};

template <typename T, T val1, T val2>
void isSame2() {
    IsSameHelper<T, val1, val2>::isSame();
}

we can do: 或者,使用我們可以:

template<typename T, T val1, T val2>
struct is_same_value : std::false_type {};

template<typename T, T val>
struct is_same_value<T, val, val> : std::true_type {};

template <typename T, T val1, T val2>
typename std::enable_if<!is_same_value<T, val1, val2>::value, void>::type isSame3() { 
    cout << "val1: " << val1 << ", val2: " << val2 << " are NOT the same" << endl;
}

template <typename T, T val1, T val2>
typename std::enable_if<is_same_value<T, val1, val2>::value, void>::type isSame3() {
    cout << "val1: " << val1 << ", val2: " << val2 << " are the same" << endl;
}

上面所有選項的主要部分如下:

int global1 = 3;
int global2 = 3;

//======================================================
// M A I N
//======================================================
int main() {
    isSame1<int, 3, 4>();
    isSame1<int, 3, 3>();
    isSame1<int*, &global1, &global1>();
    isSame1<int*, &global1, &global2>();

    isSame2<int, 3, 4>();
    isSame2<int, 3, 3>();
    isSame2<int*, &global1, &global1>();
    isSame2<int*, &global1, &global2>();

    isSame3<int, 3, 4>();
    isSame3<int, 3, 3>();
    isSame3<int*, &global1, &global1>();
    isSame3<int*, &global1, &global2>();
}

編輯:添加@Nir選項4

template <class T, T v> struct foo{
    static constexpr T val = v;
};

// in a .cpp
template <class T, T v>
constexpr T foo<T, v>::val; // required for non-integral / non-enum types

template <class T, T v1, T v2> void isSame4(foo<T, v1> f1, foo<T, v2> f2) {
    cout << "val1: " << f1.val << ", val2: " << f2.val << " are NOT the same" << endl;
}

template <class T, T v> void isSame4(foo<T, v> f1, foo<T, v> f2) {
    cout << "val1: " << f1.val << ", val2: " << f2.val << " are the same" << endl;
}

此選項的主要內容如下:

int global1 = 3;
int global2 = 3;

//======================================================
// M A I N
//======================================================
int main() {
    isSame4(foo<int, 4>(), foo<int, 3>());
    isSame4(foo<int, 3>(), foo<int, 3>());
    isSame4(foo<int*, &global1>(), foo<int*, &global1>());
    isSame4(foo<int*, &global1>(), foo<int*, &global2>());
}

我認為選項4的語法沒有任何優勢。 但是人們可以不這么認為......

注意在選項4中需要一個.cpp文件,對於T foo::val的聲明,在所有其他選項中,一切都適用於.h文件。

---編輯結束---


總結一下:

基於模板元編程,我們可以獲得編譯時解析的情況,需要部分專業化。 這可以通過類部分特化或使用enable_if來實現(對於其條件,它又需要其自己的類部分特化)。

請參閱代碼: http//coliru.stacked-crooked.com/a/65891b9a6d89e982

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM