一、前言 C++标準庫幾乎每樣東西都以template為根基。為了更多地支援template程式設計,彼岸準哭提供了template通用工具,協助應用程式開發人員和程式庫作者 Type Trait,由TR1引入,在C++11中被大幅度擴充,定義出因type而異的行為。它們可被用來針對type優化代碼,以便提供特别能力 其他工具如reference和function wrapper,也為程式設計帶來若幹幫助 二、Type Trait的目的 目的:提供一種用來處理type屬性的方法。它是個template,可在編譯期根據一個或多個template實參産出一個type或value 示範案例1(std::is_pointer<>) #include <type_traits>
template<typename T>
void foo(const T& val)
{
if (std::is_pointer<T>::value) {
std::cout << "foo() called for a pointer" << std::endl;
}
else {
std::cout << "foo() called for a value" << std::endl;
}
}
int main()
{
int num;
int *p = #
foo(num);
foo(p);
}
C++(标準庫):08---Type Trait和Type Utility(type_traits庫) 這裡的std::is_pointer<>屬于一個traits類。如果傳入的參數類型為指針類型,其傳回std::true_type;如果傳入的參數類型不是指針類型,其傳回std::false_type。然後std::true_type::value或std::false_type::value傳回相對應的true或false 第一個foo()調用傳入的參數為非指針類型 示範案例2(std::true_type、std::false_type) 我們修改示範案例1中的foo()函數,讓其列印傳入的元素的值,于是設計了下面的代碼: 但是這是錯誤的,編譯不通過 假設val不是指針類型,而代碼中卻有*val的操作,這顯然是錯誤的 template<typename T>
void foo(const T& val)
{
std::cout << (std::is_pointer<T>::value ? *val : val) << std::endl;
}
如果想要達到上面的目的,可以借助std::true_type和std::false_type來完成。代碼如下: #include <type_traits>
//如果是指針,調用這個
template<typename T>
void foo_impl(const T& val,std::true_type)
{
std::cout << "foo() called for a pointer:" << *val << std::endl;
}
//如果不是指針,調用這個
template<typename T>
void foo_impl(const T& val, std::false_type)
{
std::cout << "foo() called for a value:" << val << std::endl;
}
template<typename T>
void foo(const T& val)
{
foo_impl(val, std::is_pointer<T>());
}
int main()
{
int num = 10;
int *p = #
foo(num);
foo(p);
}
C++(标準庫):08---Type Trait和Type Utility(type_traits庫) 直接調用foo_impl()函數也是可以的。例如: int main()
{
int num = 10;
int *p = #
foo_impl(num, std::is_pointer<int>());
foo_impl(p, std::is_pointer<int*>());
}
示範案例3(std::is_integral<>,針對整數類型的彈性重載) 例如現在我們有一批重載函數,一部分是針對于整數類型的,一部分是針對于浮點數類型的。例如: void foo(short);
void foo(unsigned short);
void foo(int);
void foo(float);
void foo(double);
void foo(long double);
上面的代碼有很大的缺點:這樣做重複工作很多,代碼比較備援,并且如果加入了新資料類型那麼還需要重新定義新的foo()函數 一種做法是使用trait機制提供的模闆類,例如此處使用std::integral<>模闆。定義的代碼如下: //針對于整數類型設計的
template<typename T>
void foo_impl(T val, std::true_type);
//針對于浮點數類型設計的
template<typename T>
void foo_impl(T val, std::false_type);
template<typename T>
void foo(T val)
{
//通過is_integral萃取類型
//如果T為整數類型,std::is_integral傳回std::true_type;否則傳回std::false_type
foo_impl(val, std::is_integral<T>());
}
示範案例4(std::common_type<>,處理通用類型) 假設我們有個函數來比較兩個值的最小值,并将最小值進行傳回,如果T1和T2的資料類型不一緻,那麼傳回值該如何定義哪? C++(标準庫):08---Type Trait和Type Utility(type_traits庫) 我們可以借助std::common_type<>解決這個問題。例如: 如果傳入的兩個實參都是int,或傳入的都是long,或者傳入的一個是int一個是long,那麼std::common_type<>傳回int,是以下面的min的傳回值為int類型 如果傳入的參數一個是string而另一個是字元串字面常量,那麼下面的min的傳回值為std::string template<typename T1, typename T2>
typename std::common_type<T1, T2>::type min(const T1& x, const T2& y);
使用std::common_type<>的前提是,兩個實參它們有共同的資料類型。前提是程式員自己保證的(看下面的實作原理) std::common_type<>的實作原理如下: 其内部使用?:運算符,直接傳回T1的資料類型。是以上面不論min()的T2參數屬于什麼類型,其隻傳回T1所表示的資料類型 其内部使用了一個std::declval<>模闆,特屬于trait的一種,其根據傳入的類型提供一個值,但不去核算它(最終傳回一個該值的rvalue reference) 然後再使用decltype關鍵字導出表達式的類型 C++(标準庫):08---Type Trait和Type Utility(type_traits庫) 通過上面的comm_type<>就可以找出一個共同類型,如果找不到,可以使用common_type<>的重載版本(這正是chrono程式庫的作為,使它得以結合duration;詳情見後面的chrono程式庫介紹) 三、Type Trait的分類 Type trait大多數定義于<type_traits>頭檔案中,有些定義在别的頭檔案中(下面會注釋) ①類型判斷式 C++(标準庫):08---Type Trait和Type Utility(type_traits庫) C++(标準庫):08---Type Trait和Type Utility(type_traits庫) std::true_type、std::false_type: 上面的類型的傳回值是std::true_type或std::false_type std::true_type和std::false_type都是std::integral_constant的特化,是以它們相應的value成員可以傳回true或false。如下圖所示: C++(标準庫):08---Type Trait和Type Utility(type_traits庫) bool和所有character類型(char、char16_t、char32_t、wchar_t)都屬于整數類型 std::nullptr_t為基礎資料類型 上面大部分都是單參數形式的,但并非全部都是 一個“指向const類型”的非常量pointer或reference,其本身并不是一個常量(見下面示範案例) 用以檢驗copy和move語義的那些trait,隻檢驗是否相應的表達式為可能。例如,一個“帶有copy構造函數(接受常量實參)但沒有move構造函數”的類型,仍然是move constructible is_nothrow..type trait特别被用來闡述noexcept異常聲明 std::cout << boolalpha;
std::cout << "is_const<int>::value " << is_const<int>::value << std::endl;
std::cout << "is_const<const volatile int>::value " << is_const<const volatile int>::value << std::endl;
std::cout << "is_const<int* const>::value " << is_const<int* const>::value << std::endl;
std::cout << "is_const<const int*>::value " << is_const<const int*>::value << std::endl;
std::cout << "is_const<const int&>::value " << is_const<const int&>::value << std::endl;
std::cout << "is_const<int[3]>::value " << is_const<int[3]>::value << std::endl;
std::cout << "is_const<const int[3]>::value " << is_const<const int[3]>::value << std::endl;
std::cout << "is_const<int[]>::value " << is_const<int[]>::value << std::endl;
std::cout << "is_const<const int[]>::value " << is_const<const int[]>::value << std::endl;
C++(标準庫):08---Type Trait和Type Utility(type_traits庫) ②用以檢驗類型關系的Trait 下圖列出的type trait可以檢查類型之間的關系,包括檢查class type提供了哪一種構造函數和哪一種指派操作等等 C++(标準庫):08---Type Trait和Type Utility(type_traits庫) 注意,基本資料類型(例如int)可以表現出lvalue或是rvalue,是以你不能夠直接指派,例如“42=77”,這是錯誤的。是以is_assignable<>第一個類型如果是一個nonclass類型,永遠會獲得false_type 如果是class類型,以其尋常類型作為第一類型是可以的,因為存在一個有趣的舊規則:你可以調用“類型為class”的rvalue的成員函數。例如: std::cout << boolalpha;
std::cout << "is_assignable<int,int>::value " << is_assignable<int, int>::value << std::endl;
std::cout << "is_assignable<int&,int>::value " << is_assignable<int&, int>::value << std::endl;
std::cout << "is_assignable<int&&,int>::value " << is_assignable<int&&, int>::value << std::endl;
std::cout << "is_assignable<long&,int>::value " << is_assignable<long&, int>::value << std::endl;
std::cout << "is_assignable<int&,void*>::value " << is_assignable<int&, void*>::value << std::endl;
std::cout << "is_assignable<void*,int>::value " << is_assignable<void*, int>::value << std::endl;
std::cout << "is_assignable<const char*,std::string>::value " << is_assignable<const char*, std::string>::value << std::endl;
std::cout << "is_assignable<std::string,const char*>::value " << is_assignable<std::string, const char*>::value << std::endl;
C++(标準庫):08---Type Trait和Type Utility(type_traits庫) 下面是is_constructible<>的示範案例: std::cout << boolalpha;
std::cout << "is_constructible<int>::value " << is_constructible<int>::value << std::endl;
std::cout << "is_constructible<int,int>::value " << is_constructible<int, int>::value << std::endl;
std::cout << "is_constructible<long,int>::value " << is_constructible<long, int>::value << std::endl;
std::cout << "is_constructible<int,void*>::value " << is_constructible<int, void*>::value << std::endl;
std::cout << "is_constructible<void*,int>::value " << is_constructible<void*, int>::value << std::endl;
std::cout << "is_constructible<const char*,std::string>::value " << is_constructible<const char*, std::string>::value << std::endl;
std::cout << "is_constructible<std::string,const char*>::value " << is_constructible<std::string, const char*>::value << std::endl;
std::cout << "is_constructible<std::string,const char*,int,int>::value " << is_constructible<std::string, const char*, int, int>::value << std::endl;
C++(标準庫):08---Type Trait和Type Utility(type_traits庫) std::use_allocator<>被定義在<memory>頭檔案中 ③類型修飾符 C++(标準庫):08---Type Trait和Type Utility(type_traits庫) 如果想要為某一類型添加一個屬性,前提是該屬性尚未存在 如果想要為某一類型移除一個屬性,前提是該屬性已經存在 C++(标準庫):08---Type Trait和Type Utility(type_traits庫) C++(标準庫):08---Type Trait和Type Utility(type_traits庫) 一個“指向某常量類型”的reference本身并不是常量,是以你不可以移除其常量性 add_pointer<>必然包含使用remove_reference<> 然後make_signed<>和make_unsigned<>要求實參若非整數類型就必須是枚舉類型,bool除外,是以如果你傳入reference會導緻不明确行為 add_value_reference<>把一個rvalue reference轉換為一個lvalue reference,然而add_rvalue_reference<>并不能把一個lvalue reference轉換為一個rvalue reference(類型保持不變)。是以,必須這麼做才能将一個lvalue轉換為一個rvalue reference: C++(标準庫):08---Type Trait和Type Utility(type_traits庫) ④其他trait 下圖列出了其餘所有type trait。它們用來查詢特殊屬性、檢查類型關系、或提供更複雜的類型變換 C++(标準庫):08---Type Trait和Type Utility(type_traits庫) decay<>允許你講“以by value傳入”的類型T轉換為其相應類型。以此方式,它轉換array和function類型稱為pointer,把lvalue轉換為rvalue——其中包括移除const和volatile common_type<>為所有被傳入的類型提供一個共同類型(它可以有1個、2個或更多個類型實參) 下面是一些示範案例: C++(标準庫):08---Type Trait和Type Utility(type_traits庫) 四、Reference Wrapper(外覆器) std::reference_wrapper<>:可以将傳值調用改為傳reference調用 std::ref():可以将類型T隐式轉換為T& std::cref():可以将類型T隐式轉換為const T& template<typename T>
void foo(T val) { val++; }
int main()
{
int num1 = 1, num2 = 1;
foo(num1);
std::cout << "num1:" << num1 << std::endl;
foo(std::ref(num2)); //改為T&調用
std::cout << "num2:" << num2 << std::endl;
}
C++(标準庫):08---Type Trait和Type Utility(type_traits庫) make_pair()用此特性,于是能夠建立一個pair<> of references make_tuple()用此特性,于是能夠建立一個tuple<> of references Binder用此特性,于是能夠綁定reference Thread用此特性,于是能夠以by reference形式傳遞實參 注意事項:class reference_wrapper使你得以使用reference作為最進階對象,例如作為array或STL容器的元素類型(示範案例參閱 五、Function Type Wrapper(外覆器) std::function<>聲明于<functional>,提供多态外覆器,可以概括functional pointer記号 這個模闆允許你把可調用對象當做最進階對象 std::function<>在另外一篇文章單獨介紹過 int func(int x, int y)
{
return x + y;
}
int main()
{
std::vector<std::function<void(int, int)>> tasks;
tasks.push_back(func);
tasks.push_back([](int x, int y) { return x + y; });
for (std::function<void(int, int)> f : tasks) {
f(33, 66);
}
}
如果使用member function,那麼必須将“調用它們”的那個對象作為參數1進行傳遞。例如: class C {
public:
void memfunc(int x, int y) {}
};
int main()
{
std::function<void(const C&, int, int)> mf;
mf = &C::memfunc;
mf(C(), 42, 77);
}
這個東西的另一個應用:聲明某個函數傳回一個lambda(詳情見《C++标準庫》P31) 注意:執行一個函數調用,卻沒有标的物可調用,将會抛出std::bad_function_call異常。例如: