簡體   English   中英

模板類特化,其中模板參數是模板

[英]Templated class specialization where template argument is a template

我想知道是否有可能發生類似的事情。 基本上,我有一個模板化類,它偶爾會使用模板化類的對象。 我想將它(或只是一個成員函數)專門用於特定的模板化類,但是該類的“通用”形式。

template<typename T, typename S>
class SomeRandomClass
{
    //put something here
};

template<typename T>
class MyTemplateClass
{
    void DoSomething(T & t) {
       //...something
    }
};

template<>
void MyTemplateClass< SomeRandomClass<???> >::DoSomething(SomeRandomClass<???> & t)
{
    //something specialized happens here
}

用適當的類型(double 等)替換問號是有效的,但我希望它保持通用。 我不知道該放什么,因為沒有定義任何類型。 我環顧四周,了解了模板模板參數,並嘗試了各種組合都無濟於事。 感謝您的幫助!

可以像這樣專門化類

template <>
template <typename T,typename S>
class MyTemplateClass <SomeRandomClass<T,S> >
{
    void DoSomething(SomeRandomClass<T,S>& t) { /* something */ }
};

不可能只特化成員方法,因為特化是針對整個類的,您必須定義一個新類。 但是,您可以這樣做

template <>
template <typename T,typename S>
class MyTemplateClass <SomeRandomClass<T,S> >
{
    void DoSomething(SomeRandomClass<T,S>& t);
};

template <>
template <typename T,typename S>
void MyTemplateClass<SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S>& t)
{
    // something
}

拆分聲明和定義。

我不完全確定為什么@Ryan Calhoun以他的方式專業化,但這里有一個更簡潔的例子:

// class we want to specialize with later on
template<typename T, typename S>
struct SomeRandomClass
{
    int myInt = 0;
};

// non-specialized class
template<typename T>
struct MyTemplateClass
{
    void DoSomething(T & t) 
    {
       std::cout << "Not specialized" << std::endl;
    }
};

// specialized class
template<typename T, typename S>
struct MyTemplateClass< SomeRandomClass<T, S> >
{
    void DoSomething(SomeRandomClass<T,S> & t) 
    {
       std::cout << "Specialized" << std::endl;
    }
};

您可以看到您不需要接受的答案中使用的冗余語法:

template<>
template<typename T, typename S>

工作演示


選擇

您可以在非專用類中使用 type_traits 和 tag-dispatch 來專門化該函數。

讓我們首先為is_random_class做一個概念:

// concept to test for whether some type is SomeRandomClass<T,S>
template<typename T>
struct is_random_class : std::false_type{};

template<typename T, typename S>
struct is_random_class<SomeRandomClass<T,S>> : std::true_type{};

然后讓我們再次聲明我們的MyTemplateClass ,但這次沒有模板化(因為我們不是專門的)所以我們稱之為MyNonTemplatedClass

class MyNonTemplatedClass
{
    
    public:
    template<typename T>
    void DoSomething(T & t) 
    {
       DoSomethingHelper(t, typename is_random_class<T>::type());
    }
    // ...

請注意DoSomething現在是如何模板化的,它實際上是在調用幫助器而不是實現邏輯本身?

讓我們分解一下這條線:

DoSomethingHelper(t, typename is_random_class<T>::type());
  • t和以前一樣; 我們正在傳遞T&類型的參數
  • typename is_random_class<T>::type()
    • is_random_class<T>是我們的概念,因為它派生自std::true_typestd::false_type它將在類中定義一個::type (谷歌用於“類型特征”)
    • ::type() '實例化' is_random_class<T>::type指定的is_random_class<T>::type 我用引號引起來是因為我們真的要把它扔掉,正如我們稍后看到的
    • typename是必需的,因為編譯器不知道is_random_clas<T>::type實際上命名了一個類型。

現在我們准備看看MyNonTemplatedClass的其余部分:

    private:
    //use tag dispatch. If the compiler is smart it won't actually try to instantiate the second param
    template<typename T>
    void DoSomethingHelper(T&t, std::true_type)
    {
        std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
    }
    
    template<typename T>
    void DoSomethingHelper(T&t, std::false_type)
    {
        std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
    }
};

完整的工作演示 v2 在這里

請注意,我們的輔助函數名稱相同,但重載了第二個參數的類型。 我們沒有給參數命名,因為我們不需要它,希望編譯器在仍然調用正確的函數的同時優化它。

我們的概念強制DoSomethingHelper(T&t, std::true_type)僅當TSomeRandomClass類型SomeRandomClass ,並為任何其他類型調用另一個。

標簽調度的好處

此處標記分派的主要好處是,如果您只想特化該類中的單個函數,則無需特化整個類。

標記分派將在編譯時發生,如果您嘗試僅在DoSomething函數內對概念執行分支,則不會發生這種情況。


C++17

C++17 中,使用變量模板 (C++14) 和if constexpr (C++17),這個問題變得非常容易。

我們使用我們的 type_trait 創建一個變量模板,如果提供的類型TSomeRandomClass類型,它將為我們提供booltrue ,否則為 false :

template<class T>
constexpr bool is_random_class_v = is_random_class<T>::value;

然后,我們在if constexpr表達式中使用它,該表達式只編譯適當的分支(並在編譯時丟棄另一個,因此檢查是在編譯時,而不是運行時):

struct MyNonTemplatedClass
{
    template<class T>
    void DoSomething(T& t) 
    {
        if constexpr(is_random_class_v<T>)
            std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
        else
            std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
    }
};

type-traits 是一種無需類特化就可以模擬這種情況的方法。

請注意,這里的is_random_class是任意約束的替代品。 通常,如果您只檢查單個非模板化類型,則更喜歡正常重載,因為它在編譯器上更高效。

演示


C++20

C++20 中,我們可以更進一步,通過在模板化成員函數上使用requires子句,使用約束代替if constexpr 缺點是我們再次回到兩個函數; 一個匹配約束,另一個不匹配:

struct MyNonTemplatedClass
{
    template<class T>  requires is_random_class_v<T>
    void DoSomething(T& t)
    {
        std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
    }
    
    template<class T> requires !is_random_class_v<T>
    void DoSomething(T&) 
    {
        std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
    }
};

演示

您需要做的只是模板上要保持通用的內容。 以你開始的方式:

template<typename T, typename S>
void MyTemplateClass< SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S> & t)
{
    //something specialized happens here
}

編輯:

或者,如果您只想保留SomeRandomClass泛型的一部分,您可以:

template<typename T>
void MyTemplateClass< SomeRandomClass<T,int> >::DoSomething(SomeRandomClass<T,int> & t)
{
    //something specialized happens here
}

編輯:這是對不同問題的正確答案。

使用 typename T兩次會使問題有點混亂,因為它們是單獨編譯的,並且沒有以任何方式連接。

您可以重載該方法以采用模板化參數:

template <typename T>
class MyTemplateClass
{
    void DoSomething(T& t) { }

    template <typename U,typename V>
    void DoSomething(SomeRandomClass<<U,V>& r) { }
};

這將新方法中的UV映射到SomeRandomClass T'S' 在這個設置中, UV可以是與T相同的類型,但它們不一定是。 根據您的編譯器,您應該能夠做到

MyTemplateClass<string> mine;
SomeRandomClass<int,double> random;

// note: nevermind the non-const ref on the string literal here...
mine.DoSomething("hello world");
mine.DoSomething(random);

並且模板化調用將被選為匹配重載,而無需顯式重新指定類型。

編輯:

使用模板特化與DoSomething的重載沒有區別。 如果您按如下方式專門化該類

template <>
class SomeRandomClass <int,double>
{
    // something here...
};

那么上面的重載會很高興地吃掉這個專門的實現。 只要確保專用模板和默認模板的接口匹配即可。

如果您想要的是專門化DoSomething以獲取SomeRandomClass的特定類型對,那么您已經失去了一般性……這就是專門化。

如果您想使用提供模板結構作為模板參數(意圖在內部使用它)而不對其進行專門化:

下面是一個示例,它將類型附加到給定模板 sfinae 結構作為模板參數的元組:

template<typename Tuple, typename T, template<typename> class /*SFINAEPredicate*/>
        struct append_if;

        template<typename T, template<typename> class SFINAEPredicate, typename ... Types>
        struct append_if<std::tuple<Types...>, T, SFINAEPredicate>
        {
            using type = typename std::conditional<SFINAEPredicate<T>::value,
                    std::tuple<Types..., T>, std::tuple<Types...>>::type;
        };


    // usage
    using tuple_with_int = append_if<std::tuple<>, int, std::is_fundamental>;

這可以從 C++11 開始使用。

暫無
暫無

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

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