- 類型萃取
- 類型判斷
- typeid
- decltype和declval
- enable_if
類型萃取
通過type_traits可以實現在編譯期計算、查詢、判斷、轉換和選擇,增強了泛型編程的能力,也增強了我們程序的彈性,讓我們能夠在編譯期就能夠優化改進甚至排錯,進一步提高代碼質量。
頭文件 #include
類型判斷
type_trits提供了豐富的編譯期計算、查詢、判斷、轉換和選擇的幫助類,在很多場合中會使用到這些特性。
type_trits的類型選擇功能,在一定程度上可以消除冗長的switch-case或者if-else的語句,降低程序的復雜程度。
這些類型判斷的方法從std::integral_constant派生,用來檢查模板類型是否為某種類型,通過這些trait可以獲取編譯期檢查的bool值結果。
下面的表格是一些常用的判斷類型traits。更過從網址 點擊獲取。
traits類型 | 說明 |
---|---|
template struct is_void; | T是否為void類型 |
template struct is_floating_point; | T是否為浮點類型 |
template struct is_array; | T是否為數組類型 |
template struct is_pointer; | T是否為指針類型(包括函數指針,但不包括成員(函數)指針) |
template struct is_enum; | T是否為枚舉類型 |
template struct is_union; | T是否為非union的class/struct類型 |
template struct is_class; | T是否為類類型而不是union類型 |
template struct is_funtion; | T是否為函數類型 |
template struct is_reference; | T是否為引用類型(左值引用或者右值引用) |
template struct is_arithmetic; | T是否為整型和浮點類型 |
template struct is_fundamental; | T是否為整型、浮點、void、或nullptr_t類型 |
template struct is_object; | T是否為一個對象類型(不是函數、不是引用、不是void) |
template struct is_scalar; | T是否為arithmetic、enumeration、pointer、pointer to member或std::nullptr_t類型 |
template struct is_compound; | T是否非fundamental類型構造的 |
template struct is_member_pointer; | T是否為成員函數指針類型 |
template struct is_polymorphic; | T是否有虛函數 |
template struct is_abstract; | T是否為抽象類 |
template struct is_signed; | T是否是有符號類型 |
template struct is_unsigned; | T是否是無符號類型 |
template struct is_const; | T是否為const修飾的類型 |
使用方法:
#include <iostream>
#include <type_traits>int main()
{std::cout << "is_const:" << std::endl;std::cout << "int: " << std::is_const<int>::value << std::endl;std::cout << "const int: " << std::is_const<const int>::value << std::endl;return 0;
}輸出結果為: is_const:
int: 0
const int: 1
判斷類型的traits一般和std::enable_if結合起來使用,通過SFINAE特性來實現功能更強大的重載。后面會講到。
判斷兩個類型之間的關系traits
traits | 說明 |
---|---|
template struct is_same; | 判斷兩個類型是否相同 |
template struct is_base_of; | 判斷Base類型是否為Derived類型的基類 |
template struct is_convertible; | 判斷前面的模板參數類型能否轉換為后面的模板參數類型 |
簡單介紹一下is_same的用法:
#include <iostream>
#include <type_traits>int main()
{std::cout << "int: " << std::is_same<int, int>::value << std::endl;//這里使用了decltype可以獲取變量的類型為intstd::cout << "int: " << std::is_same<decltype(a), int>::value << std::endl;std::cout << "const int: " << std::is_same<int, unsigned int>::value << std::endl;return 0;
}輸出結果為:
int: 1
int: 1
const int: 0
類型的轉換traits
常用的類型轉換traits包括對const的修改—-const的移除和添加,引用的修改—–引用的移除和添加,數組的修改和指針的修改。
下表為類型轉換的方法:
traits | 說明 |
---|---|
template struct remove_const; | 移除const |
template struct add_const; | 添加const |
template struct remove_reference; | 移除引用 |
template struct add_lvalue_reference; | 添加左值引用 |
template struct add_rvalue_reference; | 添加右值引用 |
template struct remove_extents; | 移除數組頂層的維度 |
template struct remove_all_extents; | 移除數組所有的維度 |
template struct remove_pointer; | 移除指針 |
template struct add_pointer; | 添加指針 |
template struct decay; | 移除cv或添加指針 |
template struct common_type; | 獲取公共類型 |
簡單介紹一下使用方法:
具體可以參考c++11深入理解93頁。
#include <iostream>
#include <type_traits>int main()
{std::cout << "int: " << std::is_same<int, add_const<int>>::value << std::endl;return 0;
}輸出結果為:
int: 0
typeid
包含頭文件 #include
在講解typeid神秘面紗之前,我們先了解一下,RTTI(Run-Time Type Identification),中文為運行時類型識別,它使程序能夠獲取由基指針或引用所指向的對象的實際派生類型。即允許 “用指向基類的指針或引用來操作對象” 的程序能夠獲取到 “這些指針或引用所指對象” 的實際派生類型。
在C++中,為了支持RTTI提供了兩個操作符:dynamic_cast和typeid。
- dynamic_cast允許運行時刻進行類型轉換,從而使程序能夠在一個類層次結構中安全地轉化類型,與之相對應的還有一個非安全的轉換操作符static_cast,因為這不是本文的討論重點,所以這里不再詳述,感興趣的可以自行查閱資料。
- typeid是C++的關鍵字之一,等同于sizeof這類的操作符。typeid操作符的返回結果是名為type_info的標準庫類型的對象的引用。
我們來看一下如何使用:
#include <typeinfo>struct Base { virtual ~Base() = default; };
struct Derived : Base {};int main()
{Base b1;Derived d1;const Base *pb = &b1;std::cout << typeid(*pb).name() << '\n';pb = &d1;std::cout << typeid(*pb).name() << '\n';std::cout << typeid(1).name() << '\n';std::cout << typeid(2.444).name() << '\n';return 0;
}輸出結果:
4Base
7Derived
i
d
上面是在gcc編譯上編譯的,結果與vc++,clang都大不相同。
decltype和declval
有時候要獲取函數的返回類型是一件比較困難的事情:
比如下面代碼:
template <typename F, typename Arg>
?? func(F f, Arg arg)
{return f * arg;
}
由于函數的入參都是兩個模板參數,導致我們不能直接確定返回類型,那么我們可以通過decltype來推斷函數返回類型。
template <typename F, typename Arg>
decltype((*(F*)0)*((*(Arg*)0))) func(F f, Arg arg)
{return f * arg;
}
上面的比較繁瑣,所以我們可以使用返回類型后置去簡化。
template <typename F, typename Arg>
auto func(F f, Arg arg)->decltype(f * arg )
{return f * arg;
}
這樣看起來就舒服多了。
但是有些時候我們不能通過decltype來獲取類型了,如下面:
#include <type_traits>class A
{A()=delete;
public:int operator() ( int i ){return i;}
};int main()
{int a = A()(3);decltype( A()(0) ) i = 4;std::cout << i << std::endl;std::cout << a << std::endl; //輸出結果為3return 0;
}
上面的代碼將會編譯報錯,因為A沒有默認構造函數,對于這種沒有默認構造函數的類型,我們如果希望能推導其成員函數的返回類型,則需要借助std::declval。
修改為:
decltype( std::declval<A>()(std::declval<int>())) i = 4;
上面的代碼可以通過,因為std::declval能夠獲取任何類型的臨時值,不管它有沒有默認構造函數。因為我們通過declval()獲取了A的臨時對象。需要注意一點,declval獲取的臨時值不能用于求值,因此必須使用decltype來推斷出最終的返回類型。
其實上面做了這么多,還是比較麻煩,C++11提供了另外一個trait——std::result_of,用來在編譯期獲取一個可調用對象的返回類型。
上面的代碼改寫如下:
std::result_of<A(int)>::type i = 4;
這段代碼實際上等價于 decltype( std::declval()(std::declval()))。
enable_if
在講enable_if之前我們先來了解什么是SFINAE,它是Substitution failure is not an error 的首字母縮寫。
我們通過一個例子來了解一下SFINAE機制:
template<typename T>
void Fun(T *t)
{*t *+= 1;
}template<typename T>
void Fun(T t)
{t += 1;
}int main()
{Fun(1);return 0;
}
上面運行的時候,將會匹配到第二個重載函數,在匹配的過程中,當匹配到void Fun(T t)時,將一個非0的整數來替換T 是錯誤的,此時編譯器并不會報錯,此時就叫failure,然后繼續匹配其他的重載函數,如果最后發現void Fun(T t)能匹配上,整個過程就不會報錯,如果匹配不到就會報error,這就是為什么叫Substitution failure is not an error。
這個規則就叫SFINAE。
std::enable_if利用SFINAE實現根據條件選擇重載函數,std::enable_if的原型如下:
template<bool B, class T = void>
struct enable_if;
簡單介紹一下使用的方法:
//is_arithmetic為判斷是否為整型和浮點類型的traits
//這里在使用的時候需要加上typename在enable_if前面
//是要告訴編譯器后面的標識符是一個類型名來處理,否則會被編譯器當做靜態變量處理
template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type foo(T t)
{return t;
}int main()
{auto r = foo(1);auto r1 = foo(1.2);std::cout << r <<std::endl; //1,整數std::cout << r1 <<std::endl; //1.2,浮點數//auto r2 = foo("test"); //編譯錯誤return 0;
}
上面的函數模板通過enable_if做了限定,只能接受整型和浮點型,我們來看一下foo(1)運行步驟:
1. 根據傳入的實參1,推斷出T為int類型
2. std::is_arithmetic<T>::value變為std::is_arithmetic<int>::value,此時返回值為true
3. std::enable_if<std::is_arithmetic<T>::value, T>中的最后一個T為int,通過::type獲取類型。
4. foo函數的返回值被確定為int類型