《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