3 回答
TA貢獻1811條經驗 獲得超4個贊
另一種方式是這種方式,它也依賴于SFINAE表達式。如果名稱查找導致歧義,編譯器將拒絕該模板
template<typename T> struct HasX {
struct Fallback { int x; }; // introduce member name "x"
struct Derived : T, Fallback { };
template<typename C, C> struct ChT;
template<typename C> static char (&f(ChT<int Fallback::*, &C::x>*))[1];
template<typename C> static char (&f(...))[2];
static bool const value = sizeof(f<Derived>(0)) == 2;}; struct A { int x; };struct B { int X; };int main() {
std::cout << HasX<A>::value << std::endl; // 1
std::cout << HasX<B>::value << std::endl; // 0}它基于usenet上有人的精彩想法。
注意:HasX檢查任何名為x的數據或函數成員,具有任意類型。引入成員名稱的唯一目的是使成員名稱查找可能存在歧義 - 成員的類型并不重要。
TA貢獻1871條經驗 獲得超13個贊
這里是一個解決方案不是簡單的 約翰內斯·紹布- litb的一個。它需要C ++ 11。
#include <type_traits>template <typename T, typename = int>struct HasX : std::false_type { };template <typename T>struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };更新:一個簡單的例子和解釋如何工作。
對于這些類型:
struct A { int x; };struct B { int y; };我們有HasX<A>::value == true和HasX<B>::value == false。讓我們看看為什么。
首先回想一下,std::false_type并且std::true_type有一個static constexpr bool名為的成員,分別value設置為false和true。因此,HasX上面的兩個模板繼承了這個成員。(來自std::false_type的第一個模板和來自的第二個模板std::true_type。)
讓我們開始簡單,然后一步一步地進行,直到我們得到上面的代碼。
1)起點:
template <typename T, typename U>struct HasX : std::false_type { };在這種情況下,毫不奇怪:HasX派生于std::false_type和因此HasX<bool, double>::value == false而且HasX<bool, int>::value == false。
2)違約U:
// Primary templatetemplate <typename T, typename U = int>struct HasX : std::false_type { };鑒于U默認值int,Has<bool>實際上意味著HasX<bool, int>,因此,HasX<bool>::value == HasX<bool, int>::value == false。
3)添加專業化:
// Primary templatetemplate <typename T, typename U = int>struct HasX : std::false_type { };// Specialization for U = inttemplate <typename T>struct HasX<T, int> : std::true_type { };一般來說,感謝主要模板,HasX<T, U>源于std::false_type。但是,存在一種U = int衍生自的專業化std::true_type。因此,HasX<bool, double>::value == false但是HasX<bool, int>::value == true。
感謝默認的U,HasX<bool>::value == HasX<bool, int>::value == true。
4)decltype和一種奇特的說法int:
這里有點偏離,但是,拜托,請耐心等待。
基本上(這不完全正確),decltype(expression)產生 表達式。例如,因此0具有類型int,decltype(0)意味著int。類似地,1.2具有類型double,因此,decltype(1.2)意味著double。
考慮具有此聲明的函數:
char func(foo, int);
哪些foo是類類型。如果f是類型的對象foo,則decltype(func(f, 0))表示char(返回的類型func(f, 0))。
現在,表達式(1.2, 0)使用(內置)逗號運算符按順序計算兩個子表達式(即首先1.2和然后0),丟棄第一個值并產生第二個值。因此,
int x = (1.2, 0);
相當于
int x = 0;
一起把這個decltype給出decltype(1.2, 0)的手段int。有沒有什么特別的地方1.2或者double在這里。例如,true也有類型bool和decltype(true, 0)手段int。
班級類型怎么樣?對于instace,decltype(f, 0)意味著什么?人們很自然地認為這仍然意味著int可能并非如此。實際上,逗號運算符可能有一個重載類似于func上面的函數,它接受a foo和a int并返回a char。在這種情況下,decltype(foo, 0)是char。
我們如何避免使用逗號運算符的重載?好吧,沒有辦法為void操作數重載逗號運算符,我們可以將任何內容轉換為void。因此,decltype((void) f, 0)意味著int。實際上,(void) f鑄f從foo到void基本上什么也不做,但說的表達式必須被視為具有類型void。然后使用內置運算符逗號并生成具有類型的((void) f, 0)結果。因此,意味著。0intdecltype((void) f, 0)int
這個演員真的有必要嗎?好吧,如果逗號運算符沒有超載foo,int那么這是沒有必要的。我們總是可以檢查源代碼,看看是否有這樣的運算符。但是,如果它出現在模板中且f類型V為模板參數,則不再清楚(甚至不可能知道)逗號運算符的這種重載是否存在。無論如何我們都是通用的。
底線:decltype((void) f, 0)是一種奇特的說法int。
5)SFINAE:
這是一門完整的科學;-)好吧,我正在勸告,但這也不是很簡單。所以我會把解釋保持在最低限度。
SFINAE代表替換失敗并非錯誤。這意味著當一個模板參數被一個類型替換時,可能會出現一個非法的C ++代碼,但是在某些情況下,編譯器只是忽略了有問題的代碼,就好像它不存在一樣。讓我們看看它如何適用于我們的案例:
// Primary templatetemplate <typename T, typename U = int>struct HasX : std::false_type { };// Specialization for U = inttemplate <typename T>struct HasX <T, decltype((void) T::x, 0)> : std::true_type { };在這里,再次說,這decltype((void) T::x, 0)是一種奇特的方式,int但有SFINAE的好處。
當T用類型替換時,可能會出現無效的構造。例如,bool::x無效的C ++,因此T用boolin 替換會T::x產生無效的構造。根據SFINAE原則,編譯器不會拒絕代碼,它只是忽略它的(部分)。更確切地說,正如我們所看到HasX<bool>的實際含義HasX<bool, int>。對于專業化U = int應進行選擇,但同時將其實例化,編譯器發現bool::x并完全忽略了模板專業化,就好像它不存在。
此時,代碼基本上與上面僅存在主模板的情況(2)相同。因此,HasX<bool, int>::value == false。
用于相同的論點bool適用于B因為B::x是一個無效的構建體(B沒有成員x)。但是,A::x沒關系,編譯器在實例化U = int(或者更確切地說是for U = decltype((void) A::x, 0))的特化時沒有看到任何問題。因此,HasX<A>::value == true。
6)取消U:
好吧,再次查看(5)中的代碼,我們看到該名稱U不會在其聲明(typename U)中的任何地方使用。然后我們可以取消命名第二個模板參數,并獲得本文頂部顯示的代碼。
- 3 回答
- 0 關注
- 835 瀏覽
添加回答
舉報
