TypeList固然精妙,但是就像链表相对数组有其不足一样,TypeList中的类型递归定义使得取得其中某个类型会变成比较麻烦的事,从而也把算法的复杂性提高了,或许这些都还能忍受,比较Loki::TypeList提供了足够的算法供我们使用,但是TypeList的一个致命弱点却在我编写通讯兵模式时候发现了。
4.1 TypeList的不足
当我的程序需要记录某个函数的参数类型时候我们用到TypeList很方便,
例如下面的程序:
template<typename TList>
class DoSomeThing
{
typedef TypeAt<TList,0>::Result Parm0;
typedef TypeAt<TList,1>::Result Parm1;
typedef TypeAt<TList,2>::Result Parm2;
public:
void Do(){};
void Do(Parm0 p0){};
void Do(Parm0 p0,Parm p1){};
void Do(Parm0 p0,Parm p1,Parm p2){};
};
我们可以用不同类型特化DoSomeThing以后根据参数个数不同调用成员函数Do的重载体,这个技法在我后面的程序中将会出现很多,这就可以让我们在特化DoSomeThing之前不用知道具体会有几个参数或者会是什么类型,这就达到了泛化的目的。
但是问题出来了,如果我们需要Do的重载体接收的参数类型不是一个单数据类型,而是需要接收一个TypeList容器的时候,TypeList似乎就开始无能为力了,因为我们虽然可以写出类型这样的代码
typedef TYPELIST_2(int,unsigned int) IntType;
typedef TYPELIST_2(char,unsigned char) CharType;
typedef TYPELIST_2(IntType,CharType) SomeType;
但是我们却不能用TypeAt提取出SomeType中的IntType,如果我们用下面的代码得到的结果将不是我预期的:
TypeAt<SomeType,0>::Result
我们也许希望得到一个IntType,但是程序会返回一个int给我们,这是因为TypeList类似链表的递归定义会把SomeType展开成
TYPELIST_4(int,unsigned int, char,unsigned char)
对上面这个TypeList求取第一个类型当然就是int。这曾经把我弄得哭笑不得,或许自己再写一些代码把其中的IntType封装一下再存入进去能解决问题,但是为此我花费了若干天时间研究该怎么写这个封装代码,而且越到后面越是被其复杂的递归弄得晕头转向。
于是我一不做二不休,直接抛弃TypeList,自己设计了TypeVector。
4.2 一个似曾相识却又崭新的世界
为了让TypeVector能存储其自身,我必须想办法抛弃递归定义的技法,由此
我想到了下面的代码:
//这里我们只设置了7个类型,你可以写更多,但是在我后面的程序用不了那么多
template< typename Type0 = NullType,typename Type1 = NullType,
typename Type2 = NullType,typename Type3 = NullType,
typename Type4 = NullType,typename Type5 = NullType,
typename Type6 = NullType >
struct TypeVector
{
typedef Type0 Type0;
typedef Type1 Type1;
typedef Type2 Type2;
typedef Type3 Type3;
typedef Type4 Type4;
typedef Type5 Type5;
typedef Type6 Type6;
};
TypeVector的形态和TypeList形成了鲜明对比,TypeVector更为简洁,而且最重要的是不需递归就可以直接从中取出任意一个类型。这个形态很像数组的定义,不过不一样的是它是用来存储类型的。
4.3 TypeVector的算法
当我实作出TypeVector之前完全没有想到会有如此大的收获,因为我仅仅是
能存储其自身而创作的它,可是我却发现其算法是如此简洁明了,和TypeList形成了鲜明对比,在定制算法时候很多都是复制粘贴的操作,虽然代码量看似很多,但是你肯定不用花很多时间在调试代码的正确性上。
为了能兼容使用TypeList的程序,我采用了和TypeList同样的算法命名。
4.3.1 求取TypeVector长度
现在求取TypeVector的长度变得异常简单,只是一些模板的偏特化而已,一
眼就可看懂其算法思想。
//********************************************************************
//名称: Length
//作用: 求取TypeVector中类型个数
//输入(模板参数): TVector,需要求取长度的TypeVector。
//输出: Length<TVector>::value 求取长度值
//算法思想:
// 根据TypeVector的特化方式判断出其类型个数。
//********************************************************************
template<typename TVector> struct Length;
template<>
struct Length<TypeVector<> >
{
enum { value = 0 };
};
template<typename T0>
struct Length<TypeVector<T0> >
{
enum { value = 1 };
};
template<typename T0,typename T1>
struct Length<TypeVector<T0,T1> >
{
enum { value = 2 };
};
template<typename T0,typename T1,typename T2>
struct Length<TypeVector<T0,T1,T2> >
{
enum { value = 3 };
};
template<typename T0,typename T1,typename T2,typename T3>
struct Length<TypeVector<T0,T1,T2,T3> >
{
enum { value = 4 };
};
template<typename T0,typename T1,typename T2,typename T3,typename T4>
struct Length<TypeVector<T0,T1,T2,T3,T4> >
{
enum { value = 5 };
};
template<typename T0,typename T1,typename T2,typename T3,typename T4,typename T5>
struct Length<TypeVector<T0,T1,T2,T3,T4,T5> >
{
enum { value = 6 };
};
template<typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct Length<TypeVector<T0,T1,T2,T3,T4,T5,T6> >
{
enum { value = 7 };
};
使用时候也是和TypeList一样的使用方法:
Length< TypeVector<int,unsigned int, char,unsigned char> >::value;
4.3.2 求TypeVector某个索引值处的类型
其实由于TypeVector类似数组的结构,我们可以很轻易的写出下面这个码:
typedef TypeVector<int,unsigned int,long int> IntType;
当我们要取用里面索引值第1个型别时候可以这样写:
typedef IntType::Type1 MyType;
不过为了兼容TypeList的程序,我们还是要提供同样的接口,哪怕看似无用的。不过即便是这样,还是能从中学到一些神奇的技巧,请你别跳过这,耐心往下看。
//********************************************************************
//名称: TypeAt
//作用: 求取TypeVector中某索引值处的类型
//输入(模板参数): TVector,需要求取的TypeVector;i,索引值。
//输出: TypeAt<TVector,i>::Result 求取长度值
//算法思想:
// 根据i数值不同做出不同的偏特化体
//********************************************************************
template <typename TVector , unsigned int i>
struct TypeAt
{
typedef NullType Result;
};
template <typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct TypeAt<TypeVector<T0,T1,T2,T3,T4,T5,T6>,0>
{
typedef T0 Result;
};
template <typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct TypeAt<TypeVector<T0,T1,T2,T3,T4,T5,T6>,1>
{
typedef T1 Result;
};
template <typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct TypeAt<TypeVector<T0,T1,T2,T3,T4,T5,T6>,2>
{
typedef T2 Result;
};
template <typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct TypeAt<TypeVector<T0,T1,T2,T3,T4,T5,T6>,3>
{
typedef T3 Result;
};
template <typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct TypeAt<TypeVector<T0,T1,T2,T3,T4,T5,T6>,4>
{
typedef T4 Result;
};
template <typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct TypeAt<TypeVector<T0,T1,T2,T3,T4,T5,T6>,5>
{
typedef T5 Result;
};
template <typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct TypeAt<TypeVector<T0,T1,T2,T3,T4,T5,T6>,6>
{
typedef T6 Result;
};
写完上面的代码以后我本没太多想法,后又得到网上一位前辈(网名Intercessor)提示发现其实一切都可以变得更酷,利用宏定义在编译期执行的动作,可以简化很多重复的代码。简化后的代码像这样:
//给一个宏定义,用##(连接符)连接序号,最后就可以做出所有偏特化体
#define META_TYPEAT(atPos) template <typename T0,typename T1,typename T2 /
,typename T3,typename T4,typename T5,typename T6> /
struct TypeAt<TypeVector<T0,T1,T2,T3,T4,T5,T6>, atPos> /
{ /
typedef T##atPos Result; /
};
template <typename TVector , unsigned int i>
struct TypeAt
{
typedef NullType Result;
};
//后面就可以利用宏定义写出重复的代码。
META_TYPEAT(0)
META_TYPEAT(1)
META_TYPEAT(2)
META_TYPEAT(3)
META_TYPEAT(4)
META_TYPEAT(5)
META_TYPEAT(6)
当我们需要查找某个索引值处的类型时候,程序是这样的。
typedef TypeVector<int,unsigned int,long int> IntType;
typedef TypeAt<IntType,1>::Result MyType;
4.3.3 向TypeVector末端添加类型
类似前面的算法,向末端添加一个类型就相当于重新定义一个TypeVector并
返回,主要技巧还是模板的偏特化,下面给出代码:
//********************************************************************
//名称: Append
//作用: 向TypeVector末端添加类型
//输入(模板参数): TVector,需要添加的TypeVector;T,添加类型。
//输出: Append<TVector,T>::Result 添加后得到的TypeVector
//算法思想:
// 根据TypeVector长度不同做出不同的偏特化体
//********************************************************************
template<typename TVector, typename T> struct Append;
template<typename T>
struct Append<TypeVector<>,T>
{
typedef TypeVector<T> Result;
};
template<typename T0,typename T>
struct Append<TypeVector<T0>,T>
{
typedef TypeVector<T0,T> Result;
};
template<typename T0,typename T1,typename T>
struct Append<TypeVector<T0,T1>,T>
{
typedef TypeVector<T0,T1,T> Result;
};
template<typename T0,typename T1,typename T2,typename T>
struct Append<TypeVector<T0,T1,T2>,T>
{
typedef TypeVector<T0,T1,T2,T> Result;
};
template<typename T0,typename T1,typename T2,typename T3,typename T>
struct Append<TypeVector<T0,T1,T2,T3>,T>
{
typedef TypeVector<T0,T1,T2,T3,T> Result;
};
template<typename T0,typename T1,typename T2,typename T3,typename T4,typename T>
struct Append<TypeVector<T0,T1,T2,T3,T4>,T>
{
typedef TypeVector<T0,T1,T2,T3,T4,T> Result;
};
template<typename T0,typename T1,typename T2, typename T3,typename T4,typename T5,typename T>
struct Append<TypeVector<T0,T1,T2,T3,T4,T5>,T>
{
typedef TypeVector<T0,T1,T2,T3,T4,T5,T> Result;
};
当需要向一个TypeList末尾添加一个类型的时候代码像这样:
typedef Append< TypeVector< int,unsigned int > ,long int >::Result IntType;
4.3.4 删除TypeVector中某索引值所在位置的类型
之所以先介绍这个删除算法是因为另外一个删除算法(删除第一次匹配
类型)需要依赖到这个算法
这个算法同样很简单,只需要一些模板面特化技术就能实现。
//********************************************************************
//名称: ErasePose
//作用: 删除TypeVector中某索引值所在类型
//输入(模板参数): TVector,需要添加的TypeVector;i,索引值。
//输出: ErasePose<TVector,i>::Result 删除后的TypeVector
//算法思想:
// 根据索引值i不同做出不同的偏特化体
//********************************************************************
template <typename TVector , int i>
struct ErasePose
{
typedef TVector Result;
};
template<typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct ErasePose<TypeVector<T0,T1,T2,T3,T4,T5,T6>,0>
{
typedef TypeVector<T1,T2,T3,T4,T5,T6> Result;
};
template<typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct ErasePose<TypeVector<T0,T1,T2,T3,T4,T5,T6>,1>
{
typedef TypeVector<T0,T2,T3,T4,T5,T6> Result;
};
template<typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct ErasePose<TypeVector<T0,T1,T2,T3,T4,T5,T6>,2>
{
typedef TypeVector<T0,T1,T3,T4,T5,T6> Result;
};
template<typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct ErasePose<TypeVector<T0,T1,T2,T3,T4,T5,T6>,3>
{
typedef TypeVector<T0,T1,T2,T4,T5,T6> Result;
};
template<typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct ErasePose<TypeVector<T0,T1,T2,T3,T4,T5,T6>,4>
{
typedef TypeVector<T0,T1,T2,T3,T5,T6> Result;
};
template<typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct ErasePose<TypeVector<T0,T1,T2,T3,T4,T5,T6>,5>
{
typedef TypeVector<T0,T1,T2,T3,T4,T6> Result;
};
template<typename T0,typename T1,typename T2,typename T3,typename T4,typename T5,typename T6>
struct ErasePose<TypeVector<T0,T1,T2,T3,T4,T5,T6>,6>
{
typedef TypeVector<T0,T1,T2,T3,T4,T5> Result;
};
用法如下代码:
typedef ErasePose< IntType,1 >::Result E1IntType;
4.3.5 得第一个匹配TypeVector中某类型的索引值
按照我们预先的希望,或者惯性思考,这个算法的代码也应该是若干重复的
模板偏特化而已,但是当实作出代码以后才发现原来没有那么简单,也许你会想到这个算法的代码类似这样:
template<typename TVector , typename T>
struct IndexOf
{
enum {value = -1};
};
template<typename T>
struct IndexOf<TypeVector<T>,T>
{
enum {value = 0};
};
template<typename T0,typename T>
struct IndexOf<TypeVector<T0,T>,T>
{
enum {value = 1};
};
//...and more...
如果是这样,那当我写出这样的代码时候,将会出现错误结果。
typedef TypeVector<int,char,int,double> SomeType;
int index = IndexOf<SomeType,int>::value;
编译器看到上面代码将会提示你,它无法判断该执行哪一个模板偏特化体,因为索引0位置和索引2位置的偏特化体都有条件得到执行(偏特化与你书写的先后顺序无关),这就是所谓的模绫两可。
为了解决这个问题,我们只有能回到该死的偏特化递归去了,不过幸运的是毕竟不是每个算法都必须涉及到递归,否则我们这个TypeVector就没太多存在意义了。
下面介绍正确的IndexOf算法
//********************************************************************
//名称: IndexOf
//作用: 在TypeVector中查找第一个匹配某一类型的索引值
//输入(模板参数): TVector,需要查找的TypeVector;T,所要查找的类型
//输出: IndexOf<TVector,T>::value 查找后得到的索引值
//算法思想:
// 如果TVector为空,则定义value为-1
// 否则
// 如果TVector第一个类型匹配T,则定义value为0
// 否则
// 删除TypeVector的第一个类型再做索引,并且把结果value赋值临时变// 量temp。
// 如果temp为-1,则定义value为-1,
// 否则
// 定义value为 temp + 1 。
//********************************************************************
template <typename TVector , typename T> struct IndexOf;
template<typename T>
struct IndexOf<TypeVector<> ,T>
{
enum { value = -1};
};
template <typename TVector>
struct IndexOf<TVector,typename TypeAt<TVector,0>::Result>
{
enum{ value = 0 };
};
template <typename TVector ,typename T>
struct IndexOf
{
private:
enum{ temp = IndexOf<typename ErasePose<TVector,0>::Result,T>::value };
public:
enum{ value = temp == -1 ? -1 : 1 + temp };
};
如果你需要索引某类型时候,程序代码像这样的:
typedef TypeVector<int,char,int,double> SomeType;
int index = IndexOf<SomeType,int>::value;
上面的index将得到0;
4.3.6 删除TypeVector中第一个匹配的类型
这个算法之所以在最后讲是因为需要用到前面算法的帮助,否则又将是让人
厌烦的模板偏特化递归调用。
//********************************************************************
//名称: Erase
//作用: 删除TypeVector中查找第一个匹配的某类型
//输入(模板参数): TVector,需要删除的TypeVector;T,所要删除的类型
//输出: Erase<TVector,T>::Result 删除后得到的TypeVector
//算法思想:
// 求出类型T的索引位置并赋值value,然后删除value索引所在位置的类型。
//********************************************************************
template<typename TVector,typename T>
struct Erase
{
enum { value = IndexOf<TVector,T>::value };
typedef typename ErasePose<TVector,value>::Result Result;
};
4.4 TypeVector结束语
TypeVector相信大家现在有所了解了,其以类似数组方式的存储形态使得其
支持随即读取其中某个类型,这在很大程度上简化了我们对应算法的复杂程度,而且其有一个优良的特性就是可以将TypeVector类型存储于TypeVector中,且最后可以调用IndexOf寻找出其索引值或者根据索引值调用TypeAt获得其类型,这在某些复杂类型情况下是很方便的。
但是正所谓寸有所短,尺有所长。TypeVector相对于TypeList的缺点就是维护稍显困难,当我们因为程序需要而在库文件中扩展TypeList的长度时候我们并不需要修改其算法,但是如果我们修改TypeVector最大容量以后就必须修改其对应算法,不过值得高兴的是我们可能只需要一些复制粘贴动作就可以修改了,但是即便最小的修改也需要重新编译程序,这在某些时候显得异常麻烦。
再好的算法也必须在最适合的环境下才能得到最大的效率,这也就是我同时介绍TypeList和TypeVector的关系,因为不一定其中一种就是最好的,只是看在什么环境下了。