确定类型和表达式在编译期信息的模板技术

《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(Key*);的实例化是这样的:

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))

//c++11起标准库已包含enable_if
template <bool, typename T=void>
struct enable_if {};
template <typename T>
struct enable_if<true, T> { typedef T type; };

//对use的调用,会从两个候选use中选择一个
template <typename T>
typename enable_if<has_iterator(T)>::type use(T t)
{
    typename T::iterator it;
    (void)it;
    std::cout << "has iterator\n";
}

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

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

以上判断的结果都是一个二元值,如何继续使用该值,三个例子提供了三种不同的方式:
可以让模板不同的版本提供不同的类型定义,根据这些不同的类型利用函数的重载机制;
可以用类模板的偏特化,对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 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, typename T=void>
struct enable_if {};
template <typename T>
struct enable_if<true, T> { typedef T type; };

//如果对象有serialize方法,则调用该方法;如果没有,则调用全局的to_string方法
template <typename T>
typename enable_if<hasSerialize<T>::value, string>::type serialize(T t)
{
    return t.serialize();
}
template <typename T>
typename enable_if<!hasSerialize<T>::value, string>::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;
}

注意要在c++98标准下编译,c++11提供了std::enable_if和std::to_string。
如果Fraction::serialize变成private则无法编译通过。


《C++11 标准库源代码剖析:连载之二》结尾提出“判断某类型是否有public tostring函数”的例子:

#include <string>

//标准库有std::true_type和std::false_type
namespace nstd {
template<class T, T v>
struct integral_constant {
	static constexpr T value = v;
};
using true_type = integral_constant<bool, true>;
using false_type = integral_constant<bool, false>;
}

template <typename, typename>
struct has_tostring : nstd::false_type {};

template <typename T>
struct has_tostring<T, decltype(std::declval<T>().tostring())> : nstd::true_type {};

struct A {
	std::string tostring();
};

class B {
	std::string tostring();
};

struct C {
	const char* tostring();
};

#include <iostream>
int main()
{
	std::cout << std::boolalpha;
	std::cout << has_tostring<A, std::string>::value << std::endl;//语句1
	std::cout << has_tostring<B, std::string>::value << std::endl;
	std::cout << has_tostring<C, std::string>::value << std::endl;//语句2
	std::cout << has_tostring<C, const char *>::value << std::endl;
	std::cout << has_tostring<int, int>::value << std::endl;//语句3
	return 0;
}

对于“语句1”,先尝试匹配has_tostring的偏特化版本,第二个模板参数推断为A::tostring的返回类型std::string,特化出has_string<A, std::string>的版本,正好需要的也是这个版本。
对于“语句2”,也是先看has_tostring的偏特化版本,第二个模板参数推断为B::tostring的返回类型const char *,特化出has_string<B, const char *>的版本,但需要的却是has_string<B, std::string>的版本,于是最后使用通用的has_tostring。
对于“语句3”,也是先看has_tostring的偏特化版本,因为int类型没有tostring函数,推断第二个模板参数的过程中出现错误,转而使用通用的has_tostring。

也可以为has_tostring添加一个默认模板参数:

template <typename T, typename = std::string>
struct has_tostring : nstd::false_type {};

语句1和语句2可以变为:

std::cout << has_tostring<A>::value << std::endl;
std::cout << has_tostring<B>::value << std::endl;

如何判断类中是否有指定名称的成员变量?

template <typename T>
struct has_public_member_s {
	template <typename U>
	static auto check(U&&)->typename std::decay<decltype(U::s)>::type;
	static void check(...);
	//type为s成员的类型(value为true是有效)
	using type = decltype(check(std::declval<T>()));
	static constexpr bool value = !std::is_void<type>::value;
};
//std::declval<void>无效 所以需要一个特化
template<>
struct has_public_member_s<void> {
    static constexpr bool value = false;
};

union c1
{
	int s[2];
};
struct c2
{
	int s;
};
class c3
{
	int s;
};

int main()
{
	cout << boolalpha;
	cout << "c1 : " << has_public_member_s<c1>::value << endl;
	cout << "c2 : " << has_public_member_s<c2>::value << endl;
	cout << "c3 : " << has_public_member_s<c3>::value << endl; //false
	cout << "int : " << has_public_member_s<int>::value << endl;
	has_public_member_s<c1>::type p = new int; //int*
	delete p;
	return 0;
}

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值