Typelists是一个用来操作一大群型别的C++工具。就像list对数值提供的各种基本操作一样,typelists对型别也提供相同的操作。
有些设计模式具体制定并操作一群型别,其中也许有继承关系,显著的例子是abstract factory和visitor。如果以传统编程技术来操作一大群型别,将式全然重复的工作,如此重复将会导致隐微的代码膨胀。多数人不会想到其实它可以比现在更好。Typelists带给你一种能力,可以讲将经常性的宏工作自动化。Typelists将来自外星球的巨大威力带到C++中,让它得以支持新而有趣的一些手法。
3.1 Typelists的必要性
有时候你必须针对某些型别重复撰写相同代码,而templates无法帮上忙。假如有一个abstract factory,像这样:
class WidgetFactory { public: virtual Window* CreateWindow() = 0; virtual Button* CreateButton() = 0; virtual ScrollBar* CreateScrollBar() = 0; };
如果想将abstract factory概念泛化,不止生成window、bottom、scrollbar,而是任意型别。怎么办?
如果你不试图泛化基本概念,就不太会有机会泛化这些概念的具体实体,虽然抽象基类很简单,但是你会陷入无穷无尽的派生类生成器中。
我们希望能够这样去使用一个abstract factory:
template<class T> T* MakedWidget(WidgetFactory& factory) { T* pW=factory.create<T>(); pW=SetColor(RED); return pW; }
实际这是不可能的,如果Create不是虚函数,那么你就陷入了长长的Switch...case...之中,如果它是虚函数,那么虚函数不能是模板。
Typelists将使Abstract Factories泛化成真,并带来更多其它利益。
定义Typelists
template< class T,class U> struct Typelist { typedef T head; typedef T Tail; };
Typelists 内部没有任何数值:他的实体是空的。它们存在的理由只是为了携带型别信息。因此,对Typelists的任何处理都发生在编译期。此后我们提到一个typelist,指的是一个型别而非一个对象。虽然typlist只有两个参数,但我们可以把任意一个替换为另一个typelist,来达到无限延伸的目的。
另外我们需要一个0型别以及一个型别的Typelists,对于0型别,前面提到的NullType就可以,对于一个型别,我们可以这样定义:
typedef Typelist<int ,NullType> oneTypeOnly;
将Typelist生成线性化
像这样一个Typelist
typedef Typlist<int, Typelist<int, Typelist<int, Typelist<int, int> > > > Int4Type;
它太Lisp了,我们定义一系列的宏来完成它,这样我们可以忘记尖括号间的空格。
#define TYPELIST_1(T1) Typelist<T1, NullType> #define TYPELIST_2(T1, T2) Typelist<T1, TYPELIST_1(T2) > #define TYPELIST_3(T1, T2, T3) Typelist<T1, TYPELIST_2(T2, T3) > #define TYPELIST_4(T1, T2, T3, T4) Typelist<T1, TYPELIST_3(T2, T3, T4) > //etc #define TYPELIST_50(/* */) //
这样前面的 Int4Type 就可以这样定义
typedef Int4Type TYPELIST_4(int, int, int, int) Int4Type;
这只是包装手法的一个开端。我们访问Int4Type的最后一个元素还比较麻烦,需要
Int4Type::Tail::Tail::Tail;
计算长度
我们用下面代码来计算Typelist的长度
template<class TList>struct Length; template<> struct Length<NullType>{ enum{value=0}; }; template<class T,class U> struct Length<Typelist<T,U>> { enum{value=1+Length<U>::value}; };
由Length<T>::value得到的是一个编译期常数,我们可以用它来定义数组大小,
std::type_info* intsRtti[Length<SignedIntegrals>::value];
这个模板代码依靠递归来完成,第一个版本是全特化,第二个版本是偏特化。
间奏曲
这里有一个问题,在Length的实现中,我们能否用迭代来取代递归?答案是否定的。因为我们在编译期编程中,可以使用到的仅仅是:template、编译期整数计算、typedef。
1、template:更明确的说是模板特化,提供编译期的if叙述。
2、整数计算:提供数值计算能力,用以从性别转为数值,但数值是不可改变的。
3、typedef:可视为用来引进“具名的型别常数”,他们也是定义之后就冻结,你不能将typedef定义的符号重新定义为另一个型别。
这些特性决定了我们无法使用迭代,所谓迭代是持有一个迭代器,并改变它,知道某些条件吻合,由于编译期我们没有“可资变化的任何东西”,所以无法实现“迭代”。所以编译期运算只能象那些纯粹函数型语言,使用大量的递归。
索引式访问
索引式访问是Typelist访问线性化,像在static世界中,索引必须是编译期常数,一个带有索引的template声明如下:
template<class TList,unsigned int index> struct TypeAt;
实现如下:
template<class Head,Class Tail> struct TypeAt<Typelist<Head,Tail>,0> { typedef Head Result; } template<class Head,class Tail,unsigned int> struct TypeAt<Typelist<Head,Tail>,i> { typedef typename TypeAt<Tail,i-1>::Reslut Result; }
如果你试着越界访问,会编译出错。对Typelist进行索引访问,花费的时间和typelist大小有关,但这个时间全部花在编译期。
查找Typelist
我们用index0f来查找Typelist中的一个型别,返回其位置,找不到就返回-1。
template <class TList, class T> struct IndexOf; template <class T> struct IndexOf<NullType, T>{ enum{value = -1}; }; template <class Tail, class T> struct IndexOf<Typelist<T, Tail>, T>{ enum{value = 0;} }; template <class Head, class Tail, class T> struct IndexOf<Typelist<Head, Tail>, T>{ private: enum{temp = IndexOf<Tail, T>::vale}; public: enum{value = temp == -1 ? -1 : 1+temp}; };
最后一个特化版本是用了问号表达式来实现分支判断
追加元素至Typelist
修改typelist是不可能的,我们以by value方式传回一个新typelist。
template <class TList, class T> struct Append; template <> struct Append<NullType, NullType>{ typedef NullType Result; }; template <class T> struct Append<NullType, T>{ typedef TYPELIST_1(T) Result; }; template <class Head, class Tail> struct Append<NullType, Typelist<Head, Tail> >{ typedef Typelist<Head, Tail> Result; }; template <class Head, class Tail, class T> struct Append<Typelist<Head, Tail>, T>{ typedef Typelist<Head, typename Append<Tail,T>::Result> Result; };
这个实现比较复杂,特别是最后一个偏特化版本,他递归具现Append,每次递归都将tail和待追加型别传递进去
移除typelist中的某个元素
template <class TList, class T> struct Erase; template <class T> struct Erase<NullType, T>{ typedef NullType Result; }; template<class T, calss Tail> struct Erase<Typelist<T, Tail> T>{ typedef Tail Result; }; template <class Head, class Tail, class T> struct Erase<Typelist<Head, Tail>, T>{ typedef Typelist<Head, typename Erase<Tail, T>::Result> Result; };