確定類型和表達式在編譯期信息的模板技術

《c++必知必會》“item 52 針對類型信息的特化”提供了通過類模板的部分特化獲得一個類型是否爲一個指針的方法:

template <typename T>
struct IsPtr // T不是一個指針
{
    enum { result=false };
};
template <typename T>
struct IsPtr<T*> // 一個不帶修飾符的指針
{
    enum { result=true };
};
template <typename T>
struct IsPtr<T* const> // const指針
{
    enum { result=true };
};
template <typename T>
struct IsPtr<T* volatile> // volatile指針
{
    enum { result=true };
};
template <typename T>
struct IsPtr<T* const volatile> // const volatile指針
{
    enum { result=true };
};

爲其設想一個使用實例:

struct Key
{
    Key(int k):key(k) {}
    int key;
};

template <typename T>
void print_key(T t)
{
    if(IsPtr<T>::result)
    {
        cout << t->key;
        delete t;
    }
    else
    {
        cout << t.key;
    }
}

int main()
{
    print_key(new Key(11));
    print_key(Key(13));
}

然而,上邊的代碼根本就不能通過編譯,print_key(new Key(11));的展開是這樣的:

void print_key(Key *t)
{
    if(true)
    {
        cout << t->key;
        delete t;
    }
    else
    {
        //error: request for member 'key' in 't', which is of pointer type 'Key*'
        cout << t.key;
    }
}

沒有模板的時候,這明顯就是錯的,我們可以抱怨編譯器不夠智能,可規則就是這樣的,還是和它鬥智鬥勇吧。
可以通過函數模板的重載來解決這個問題,完整實例如下:

struct Yes {};
struct No {};
template <typename T>
struct IsPtr // T不是一個指針
{
    enum { result=false };
    typedef No Result;
};
template <typename T>
struct IsPtr<T*> // 一個不帶修飾符的指針
{
    enum { result=true };
    typedef Yes Result;
};
template <typename T>
struct IsPtr<T* const> // const指針
{
    enum { result=true };
    typedef Yes Result;
};
template <typename T>
struct IsPtr<T*volatile> // volatile指針
{
    enum { result=true };
    typedef Yes Result;
};
template <typename T>
struct IsPtr<T*const volatile> // const volatile指針
{
    enum { result=true };
    typedef Yes Result;
};

struct Key
{
    Key(int k):key(k) {}
    int key;
};

template <typename T>
void print_key(T t)
{
    print_key(typename IsPtr<T>::Result(), t);
}

template <typename T>
void print_key(Yes, T t)
{
    cout << t->key;
    delete t;
};

template <typename T>
void print_key(No, T t)
{
    cout << t.key;
};

int main()
{
    print_key(new Key(11));
    print_key(Key(13));
}

還可以利用SFINAE實現IsPtr。SFINAE: substitution failure is not an error,當試圖使用函數模板實參推導機制在多個函數模板和非模板函數中進行選擇時,只要發現了一個正確的替換,其他對函數模板嘗試過的錯誤替換都不會導致報錯。(c++必知必會 item 59)
This rule applies during overload resolution of function templates: When substituting the deduced type for the template parameter fails, the specialization is discarded from the overload set instead of causing a compile error.(http://en.cppreference.com/w/cpp/language/sfinae

用SFINAE來重新實現IsPtr:

typedef struct {char a;} True;
typedef struct {char a[2];} False;
template <typename T> True isPtr(T*);
False isPtr(...);
#define is_ptr(e) (sizeof(isPtr(e)) == sizeof(True))

isPtr可以判斷被const volatile限定的指針,作爲函數模板實參推導的一個組成部分,編譯期將會忽略cv修飾符,也會忽略引用修飾符。
而且isPtr不用有實現,sizeof操作符可以在編譯時返回一個類型或表達式的大小,所以isPtr根本就沒有被調用,也就是說在編譯期就已經對is_ptr的值進行了計算。

然後怎麼使用is_ptr呢,可以像上面那樣,通過函數模板重載來解決,這裏我們用類模板的偏特化(函數模板不支持偏特化):

template <bool,typename T>
struct print_t
{
    void operator()(T t);
};
template <typename T>
struct print_t<true,T>
{
    void operator()(T t)
    {
        cout << t->key;
        delete t;
    }
};
template <typename T>
struct print_t<false,T>
{
    void operator()(T t)
    {
        cout << t.key;
    }
};

struct Key
{
    Key(int k):key(k) {}
    int key;
};

template <typename T>
void print_key(T t)
{
    print_t<is_ptr(t),T>()(t);
}

int main()
{
    print_key(new Key(11));
    print_key(Key(13));
}

SFINAE可用於揭示關於類型和表達式在編譯期的信息。
下面是一個判斷對象中是否有特定類型的例子:

typedef struct {char a;} True;
typedef struct {char a[2];} False;

template <class C>
True hasIterator(typename C::iterator *);
template <typename T>
False hasIterator(...);
#define has_iterator(C) (sizeof(hasIterator<C>(0))==sizeof(True))

template<bool B>
struct enable_if {};

template<>
struct enable_if<true> { typedef void type; };

//對use的調用,會從兩個候選use中選擇一個
template <typename T>
typename enable_if<has_iterator(T)>::type use(T t)
{
    typename T::iterator it;
    cout << "has iterator\n";
}

template <typename T>
typename enable_if<!has_iterator(T)>::type use(T t)
{
    cout << "has no iterator\n";
}

int main()
{
    vector<int> v;
    use(v);
    use(5);
}

以上,判斷的結果都是一個bool的值,如何繼續使用該值,三個例子提供了三種不同的方式:
可以讓模板不同的版本提供不同的類型定義,根據這些不同的類型利用函數的重載機制;
可以用類模板的偏特化,對true和false分別定義不同的特化;
第三種既用了類模板的完全特化,也讓不同的特化提供不同的類型定義。


判斷一個變量是不是一個對象:

struct isClass
{
    typedef struct {char a;} True;
    typedef struct {char a[2];} False;

    template <class C>
    static True& test(void (C::*)());
    template <typename T>
    static False test(...);

    template <typename T>
    static const bool value(T){ return sizeof(True) == sizeof(test<T>(0));}
};
或
template <typename T>
struct isClass
{
    typedef struct {char a;} True;
    typedef struct {char a[2];} False;

    template <class C>
    static True& test(void (C::*)());
    template <typename C>
    static False test(...);

    static const bool value = sizeof(True) == sizeof(test<T>(0));
};

用法略有不同,我比較喜歡第一種。


判斷對象有沒有特定成員函數:

/*判斷對象是否有serialize方法的有些意義的實例*/
#include <iostream>
#include <vector>
#include <list>
#include <cstdio>
#include <cstring>
using namespace std;

template <typename T>
string to_string(T)
{
    return string("has no specialization");
}

template <template<typename,typename> class T,typename Alloc>
string to_string(T<int,Alloc> &u)
{
    string s;
    for(typename T<int,Alloc>::iterator it(u.begin());it!=u.end();++it)
    {
        char buf[10];
        sprintf(buf, "%d,", *it);
        s += buf;
    }
    return s;
}

class Fraction
{
    int numerator;
    int denominator;
public:
    Fraction(int n, int d):numerator(n),denominator(d){}
    string serialize()
    {
        char buff[60];
        sprintf(buff,"%d/%d",numerator,denominator);
        return string(buff,buff+strlen(buff));
    }
};

template <typename T>
struct hasSerialize
{
    typedef struct {char a;} yes;
    typedef struct {char a[2];} no;
    template <typename U, U u> struct reallyHas;
    template <typename C> static no test(...);
    template <typename C>
    static yes test(reallyHas<string (C::*)(),&C::serialize> *);//參數必然是一個指針
    template <typename C>
    static yes test(reallyHas<string (C::*)()const,&C::serialize> *);
    static const bool value = sizeof(yes)==sizeof(test<T>(0));
};

template <bool B>
struct enable_if {};

template <>
struct enable_if<true> { typedef string type; };

//如果對象有serialize方法,則調用該方法;如果沒有,則調用全局的to_string方法
template <typename T>
typename enable_if<hasSerialize<T>::value>::type serialize(T t)
{
    return t.serialize();
}
template <typename T>
typename enable_if<!hasSerialize<T>::value>::type serialize(T t)
{
    return to_string(t);
}

int main()
{
    Fraction f(1,2);
    cout << serialize(f) << endl;

    list<int> cv;
    cv.push_back(1);
    cv.push_back(2);
    cv.push_back(3);
    cv.push_back(4);
    cout << serialize(cv) << endl;

    vector<char> ci;
    ci.push_back(9);
    cout << serialize(ci) << endl;

    cout << serialize(4) << endl;
}

reference:
c++必知必會
http://en.cppreference.com/w/cpp/language/sfinae
http://blog.csdn.net/godcupid/article/details/50420925
https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章