[英]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_type
或std::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";
}
};
請注意,我們的輔助函數名稱相同,但重載了第二個參數的類型。 我們沒有給參數命名,因為我們不需要它,希望編譯器在仍然調用正確的函數的同時優化它。
我們的概念強制DoSomethingHelper(T&t, std::true_type)
僅當T
是SomeRandomClass
類型SomeRandomClass
,並為任何其他類型調用另一個。
此處標記分派的主要好處是,如果您只想特化該類中的單個函數,則無需特化整個類。
標記分派將在編譯時發生,如果您嘗試僅在DoSomething
函數內對概念執行分支,則不會發生這種情況。
在C++17 中,使用變量模板 (C++14) 和if constexpr
(C++17),這個問題變得非常容易。
我們使用我們的 type_trait 創建一個變量模板,如果提供的類型T
是SomeRandomClass
類型,它將為我們提供bool
值true
,否則為 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 中,我們可以更進一步,通過在模板化成員函數上使用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) { }
};
這將新方法中的U
和V
映射到SomeRandomClass
T'
和S'
。 在這個設置中, U
或V
可以是與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.