1. 程式人生 > >【C++11學習筆記】型別判斷的type_traits學習

【C++11學習筆記】型別判斷的type_traits學習

一、簡單的type_traits

我理解的type_traits是利用C++模板特性和static、enum特性定義編譯器常量,例如

//std::integral_constant原始碼
typelate<class T, T v>
struct integral_constant
{
    static const T value = v;
    typedef T value_type;
    typedef integral_constant<T, v> type;
    operator value_type() {return value;}
};

這裡利用的是static常量為編譯器常量的特,定義了value。使用方法:從std::integral_constant派生,無需自己定義static const常量或enum型別,例如

template<typename T>
struct GetSize : std::integral_constant<int, 1>
{
};

std有兩個定義好的std::integral_constant例項,分別定義了編譯期的true和false型別,用途很廣:

typedef integral_constant<bool, true> true
_type; typedef integral_constant<bool, false> false_type;

二、常見型別判斷type_traits原始碼學習

1.is_void

宣告:

template<class T>
struct is_void;

作用:

T是否為void型別

原始碼:

template<class T, class U>
struct is_same : std::false_type
{};

template<class T>
struct is_same : std::true_type
{}; template<class T> struct is_void : std::is_same<void, typename std::remove_cv<T>::type> {};

說明:首先利用模板的匹配實現用以判斷兩種型別是否一致的is_name,再將T去除c(const)、v(volatile)限定符後與void型別判斷是否一致。下面有些簡單的程式碼就不解釋了。

2.is_floating_point

宣告

template< class T >
struct is_floating_point;

作用

T是否為浮點型別

原始碼

template< class T >
struct is_floating_point : std::integral_constant<bool,std::is_same<float, typename std::remove_cv<T>::type>::value || std::is_same<double, typename std::remove_cv<T>::type>::value || std::is_same<long double, typename std::remove_cv<T>::type>::value> 
{};

3.is_array

宣告

template<class T>
struct is_array;

作用

T是否為陣列型別

原始碼

template<class T>
struct is_array : std::false_type {};

template<class T>
struct is_array<T[]> : std::true_type {};

template<class T, std::size_t N>
struct is_array<T[N]> : std::true_type {};

4.is_pointer

宣告

template< class T > 
struct is_pointer;

作用

T是否為指標型別(包括函式指標,但不包括成員(函式)指標)
原始碼

template< class T > struct is_pointer_helper     : std::false_type {};
template< class T > struct is_pointer_helper<T*> : std::true_type {};
template< class T > struct is_pointer : is_pointer_helper<typename std::remove_cv<T>::type> {};

5.is_member_pointer

宣告

template< class T >
struct is_member_pointer

作用

T是否為成員函式指標、指向成員變數指標型別
原始碼

template< class T >
struct is_member_pointer_helper : std::false_type {};

template< class T, class U >
struct is_member_pointer_helper<T U::*> : std::true_type {};

template< class T >
struct is_member_pointer : is_member_pointer_helper<typename std::remove_cv<T>::type> 
{};

為什麼is_member_pointer_helper< T U::*>這個就是成員函式指標、指向成員變數指標型別呢?

可以見我另外一篇文章:C++如何宣告類成員函式指標或類成員變數指標(A::*)
這個引數T U::*怎麼理解,其實就理解成T *——T型別指標,但是是類U中的,即類U的成員函式指標或成員變數指標,看下面的測試程式碼:

#include <iostream>
#include <type_traits>

int main() {
    class cls {};
    std::cout << (std::is_member_pointer<int(cls::*)>::value
                     ? "T is member pointer"
                     : "T is not a member pointer") << '\n';
    std::cout << (std::is_member_pointer<int cls::*>::value
                     ? "T is member pointer"
                     : "T is not a member pointer") << '\n';
}

輸出是

T is member pointer
T is member pointer

注意,並不是判斷類T中是否真的有返回值為int的函式,或者是否有int型變數,而是隻是判斷T這個寫法是否是成員函式指標、指向成員變數指標型別。

6.is_class

宣告:

template <class T>
struct is_class;

作用

T是否為類型別,且不是union型別
原始碼

namespace detail {
    template <class T> char test(int T::*);
    struct two { char c[2]; };
    template <class T> two test(...);
}

template <class T>
struct is_class : std::integral_constant<bool, sizeof(detail::test<T>(0))==1 && !std::is_union<T>::value> 
{};

解釋一下,定義了兩個模板函式,一個形參是int T::*(指向int型類成員變數的指標),返回值是char(大小是1);另一個形參是所有型別,返回值是struct two(大小是2)。

is_class繼承了std::integral_constant< T, T v >(內部定義了一個static const T型別變數value,取值為v),value的型別為bool,當detail::test(0)的大小為1時(只要T是class型別,就符合第一個模板函式test,則其返回值大小就為1,否則返回值大小為2),並且不為union型別時(加上這個是因為,union型別類似struct型別,也支援T::*),則為class(或struct)型別。

7.is_base_of

宣告

template <typename Base, typename Derived>
class is_base_of;

作用

Base是否是Derived的基類

原始碼

template <typename Base, typename Derived, 
    bool = (is_class<Base>::value && is_class<Derived>::value)>
class is_base_of
{
    template <typename T>
    static char helper(Derived, T);
    static int helper(Base, int);
    struct Conv
    {
        operator Derived();
        operator Base() const;
    };
public:
    static const bool value = sizeof(helper(Conv(), 0)) == 1;
};


template <typename Base, typename Derived>
class is_base_of<Base, Derived, false>
{
public:
    static const bool value = is_same<Base, Derived>::value;
};


class B
{
};

class D : public B
{
};

int main()
{
    cout << boolalpha << is_base_of<B, D>::value << endl;
}

程式碼中最“厲害”的地方就是對helper函式的匹配了。

  1. 如果Base不是Derived的基類,那麼Conv()做隱式轉換時,兩個候選型別Base和Derived都是平等的,兩個helper函式都可以匹配,但在這裡按照規則,會去優先匹配非模板的函式。於是得到了我們想要的結果。

  2. 如果Base是Derived的基類,這種情況比較複雜。

這種情況下,除非Conv物件是一個const的,否則它的隱式轉換是隻會去呼叫operator ()Derived的,因為operator ()Base const後面所帶的const。於是這樣的情況下,Conv()總是隱式轉換成一個Derived物件(當然,上面的只是學習程式碼,如果真正要實用的話,還要做很多工作,比如說在這裡就要首先確保Derived型別本身不是const的),這時候對於兩個helper的第一個引數,一個是精確匹配,一個是要轉換為基類,一開始我異想天開地以為這種情況下就會去先匹配第一個了,因為這也是所需要的正確結果,結果自然是沒有錯,不過我的想法卻是太天真了,因為我完全抹殺了第二個引數的貢獻,倘若沒有它,那第一個helper函式都不會被具現化,更別說讓它去被匹配了。