注意:本系列博文需要对templates和STL有一定掌握。
Typelist
续前篇,这回介绍Typelists。typelists的作用,是操作大群型别的工具,就像lists对数组提供各种基本操作一样,typelists对型别也会提供相同操作。
如果以传统的编程技术操作一大群型别,将是全然的重复性工作,如此重复会导致隐蔽的程式膨胀,多数人不会想到其实它可以比现在更好。typelist带给你一种能力,可以将经常性的巨集工作自动化。typelists将来自外星球的强大威力带到了c++中,让它得以支援新而有趣的一些手法。
1.0:Typelists的必要性
有时候必须针对某些型别重复撰写相同的程式码,而且template无法帮上忙。假设你需要实作一个Abstract Factory,其规定你必须针对设计期间已知的一群型别中的每一个型别定义一个虚拟函数,代码如下:
class widgetfactory
{
pubilc:
virtual window* createwindow()=0;
virtual button* createbutton()=0;
virtual scrollbar* createbar()=0;
};
虽然一开始其似乎没有提供太多的泛化与抽象的机会,但其还是有一些事情值得去研究:
1:如果你不试图泛化基础概念,就不太会有机会泛化这些概念的具象实体。这是很重要的原则。如果你没能将本质泛化,你仍然得与本质所衍生的具象实体纠缠。在上述的factory中,虽然抽象的基类十分简单,但你会在会在实作各式各样的函数时遇到很多烦人而又重复的程式码。
2:你无法轻易的操作wodgetfactory的成员函数。本质上我们不可能以泛型方法处理一群虚拟函数标记式。例如:
template <class T>
T *makeredwidget(widgetfactory &factory)
{
T* pw=factory.createT();//蛤?这是什么?
pw->setcolor(red);
return pw;
}
3:最后一点,优秀的程式库可摆脱【命名的争议】。他们引入一个更好的,更标准化的方法来做这些事情,大致而言,abstract factory的确有这样的良好副作用。
现在,把以上的三点放入目前的需求当中。为了满足第一点。我们最好能够将一串参数传给abstract factory template,产出一个widgetfactory,代码如下:
typedef abstractfactory<window,buton,scrollbar> widgetfactory;
template <class T>
T* makedwidget(widgetfactory &factory)
{
T* pw=factory.create<T>();//啊哈,行了
pw->setcolor(red);
return pw;
}
透过以上写分析,我想你已经看到了,我们有好多的抽象化和复用化的机会,开发这些机会时,我们又将遭遇多麻烦的语言限制?
Typelists将使Abstract Factory泛化成真,并带来更多的其他利益。
1.1:定义Typelists
Typelists的本质,其实十分的简朴,这跟c++本身的语义丰富性是一致的。
template <class T, class U>
struct Typelist
{
typedef T Head;
typedef U Tail;
};
<strong><span style="font-size:14px;color:#999999;">typedef Typelist<char,Typelist<signed char,unsigned char> > charlist;</span></strong>
Typelists 内部没有任何的数值:它们的实体是空的,不含任何状态,也未定义任何成员。执行期间typelist 不带任何数值。它们存在的理由只是为了携带型别资讯。因此对typelist 的任何处理都一定发生在编译期而非执行期。Typelists 并未打算被具现化。
这里使用的性质是:template参数可以是任何型别,包含该template的其他具现体。这是一个众所周知额度template特性,尤其常被用作实作的array,例如vector<vector<double> >。由于typelist接受两个参数。所以我们藉由“其中一个为typelis”t的做法来达到无限延伸参数的目的。
然而有个小问题。虽然我们可以表达出持有两个型别或更多型别的typelist,但我们无法表达出持有零个或一个型别的typelist。因此,我们需要一个null type,当然,我们不需要对其感兴趣,只需要作出定义即可:
struct NullType;
typedef Typelist<int,NullType> onetypeonly;
如果为内含三个char变体的typelist则如下:
typedef Typelist<char,Typelist<signed char,Typelist<unsigned char,NullType> > > allchartypes;
#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) >........
1.2:Typelist 的长度计算
这其实是一个简单的操作。假设有一个typelist,他有一个编译期常熟代表其长度。这个常数理所应当是个编译期常熟,因为typelist 是一个静态构件,我们预期所有与typelist相关的计算都将在编译期完成。
下面为计算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 };
};
以c++的语法解释是【null type 的长度为0,其他的typelist的长度是tail的长度加1】。
实作Length时需要运用到template的偏特化特性来区分nulltype和typelist。上述第一个版本为Length的全特化,只匹配nulltype。第二个版本是Length的偏特化,可以匹配任何的typelist<T,T1>.包括复合型的typelist,即本身T2也是一个typelist的情况。
第二份特化版本完成了recursive 运算。它将value定义为数值1加上tail的长度。当tail编程typelist时。吻合第一份特化定义,当即停止recursive并巧妙地传回结果。这便是长度的计算过程。传回typelist的长度可以这么写:
Length<typelist_3(int,char,double) >::value
1.3:Typelist的索引存取
透过索引来获取typelist元素,这种能力令人向往。这使得typelist的存取线性化。使我们能更加轻松的操作typelist。当然了,就像我们在static世界中操作所有物件一样,索引必须是一个编译期的数值。
一个带有索引操作的template 定义式会像这样:
template <class TList,unsigned int index> struct TyptAT;
-------------------------------------------------------------------------------------------------------------------------------------------
输入:typelist TList,索引值index
输出:内部某种型别 result
如果TList部位null,且index为0,那么result就是TList的头部。
否则
如果TList部位null且index不为0,那么result就是【将TypeAt用于TList尾部以及index-1】的结果。
否则 越界存取,编译错误。
-------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------
那么,TypeAT的代码就应该如下:
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 i>
struct TypeAt<Typelist<Head, Tail>, i>
{
typedef typename TypeAt<Tail, i - 1>::Result Result;
};
1.4:搜寻Typelists
如果在typelist中寻找某个型别呢?那么我们就继续实作出这个功能吧。
用于搜寻typelist的IndexOf功能的定义如下:
template <class TList, class T> struct IndexOf;
即演算算法如下:
-------------------------------------------------------------------------------------------------------------------------------------------
输入:typelist TList,type T
输出:内部编译期常熟 value
如果TList是nulltype,令value为-1.
否则
如果TList的头部是T,令value为0.
否则
将IndexOf用于【TList尾部以及T】,并将结果置于一个暂时的temp。
如果temp为-1,令value为-1.
否则 令value为1+temp。
------------------------------------------------------------------------------------------------------------------------------------------- 这是个相对简单的演算法。需要特别注意的是如何将【没找到】往外传为计算结果。我们需要三个特化版本,每个版本对应算法中的分支点。最后一分支是个数值计算。
那么,实作出indexof的代码如下:
template <class TList, class T> struct IndexOf;
template <class T>
struct IndexOf<NullType, T>
{
enum { value = -1 };
};
template <class T, class Tail>
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>::value };
public:
enum { value = (temp == -1 ? -1 : 1 + temp) };
};
1.5:附加元素至Typelist
我们还需要一个可将“某个型别附加到整个typelist”的功能。先前已经讨论过,修改typelist是不可能的,但我们将以value的方式传回一个我们所期待的新typelist。
-------------------------------------------------------------------------------------------------------------------------------------------
输入:typelist TList,type T
输出:内部某型别的result
如果TList是nulltype而且T是nulltype,那么令result未nulltype。
否则
如果TList是nulltype,且T是个type但却非typelist,那么result将是【只含唯一元素T】的typelist。
否则
如果TList是nulltype,且T是一个typelist,那么result便是T本身。
否则 result将会是个typelist,以TList::head为其头部,并以【T附加至TList::tail】为结果为其尾部。
-------------------------------------------------------------------------------------------------------------------------------------------
那么这个功能演算法代码如下:
template <class TList, class T> struct Append;
template <> struct Append<NullType, NullType>
{
typedef NullType Result;
};
template <class T> struct Append<NullType, T>
{
typedef Typelist<T,NullType> 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特化版本,它recursive具现append,每次recursive都将tail和【待附加型别】传递进去。
现在,我们可以利用append操作了:
typedef Append<Typelist<int,Nulltype>,typelist_2(char,double) > A;