Generic design | Typelists

本文介绍了 C++ 中 Typelist 的基本概念及其操作方法,包括定义 Typelist、计算长度、索引存取等,并提供了具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注意:本系列博文需要对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;

};

      如果你想将其概念化,并将它纳入程式库中,你必须让使用者得以产生针对任意型别(而不只是window,button跟bar)的工厂。templates无法支援此特性。

      虽然一开始其似乎没有提供太多的泛化与抽象的机会,但其还是有一些事情值得去研究:

      1:如果你不试图泛化基础概念,就不太会有机会泛化这些概念的具象实体。这是很重要的原则。如果你没能将本质泛化,你仍然得与本质所衍生的具象实体纠缠。在上述的factory中,虽然抽象的基类十分简单,但你会在会在实作各式各样的函数时遇到很多烦人而又重复的程式码。

      2:你无法轻易的操作wodgetfactory的成员函数。本质上我们不可能以泛型方法处理一群虚拟函数标记式。例如:
template <class T>
T *makeredwidget(widgetfactory &factory)
{
    T* pw=factory.createT();//蛤?这是什么?
    pw->setcolor(red);
    return pw;

}

      你得根据T是个window,button或者bar,各别callcreatewindow(),createbutton(),createbar()。但c++不允许你做这样的文字替换。

      3:最后一点,优秀的程式库可摆脱【命名的争议】。他们引入一个更好的,更标准化的方法来做这些事情,大致而言,abstract factory的确有这样的良好副作用。

      现在,把以上的三点放入目前的需求当中。为了满足第一点。我们最好能够将一串参数传给abstract factory template,产出一个widgetfactory,代码如下:
typedef abstractfactory<window,buton,scrollbar> widgetfactory;

      为了满足第二点,我们需要对各种createXXx()函数有一个类似template的call形式,例如create<window>(),create<button>()等等,然后就可以在泛型程式中这样call它们:
template <class T>
T* makedwidget(widgetfactory &factory)
{
    T* pw=factory.create<T>();//啊哈,行了
    pw->setcolor(red);
    return pw;

}

      然而,我们无法满足这样的需求,首先,先前的widgetfactory型别定义是不可能的,因为template无法拥有不定长参数。其次template语法creat<xxx>不合法,因为虚拟函数不可以是template。

      透过以上写分析,我想你已经看到了,我们有好多的抽象化和复用化的机会,开发这些机会时,我们又将遭遇多麻烦的语言限制?

Typelists将使Abstract Factory泛化成真,并带来更多的其他利益。

1.1:定义Typelists

      Typelists的本质,其实十分的简朴,这跟c++本身的语义丰富性是一致的。
template <class T, class U>
struct Typelist
{
    typedef T Head;
    typedef U Tail;
};

      Typelists持有两个型别,我们可透过其内部型别head跟tail加以存取,就是这么简单!我们不需要持有三个或者更多型别的typelist,因为我们已经有了。例如下面就是有着c++ char型别变体的typelist(注意,>之间需要有空格,虽然这很烦,但必须要注意):
<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;

      现在,我们就可以指定一个使用习惯:每一个typelist的结尾都必须以nulltype结尾,nulltype就被视为一个结束的符号。类似于传统c字符串的\0。现在我们就可以定义一个只持有一个型别的typelist 了:
typedef Typelist<int,NullType> onetypeonly;

       如果为内含三个char变体的typelist则如下:
typedef Typelist<char,Typelist<signed char,Typelist<unsigned char,NullType> > > allchartypes;

      由此我们得以拥有一个无限的typelist template,藉由组合【基本单元】而持有任意量型别。如下:
#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操作函数的基本概念都是“开拓 recursive template”,这是指某种templates以本身具现化当作其定义的一部分。当这么做的时候,它们会传回一个不同的template 参数列。这种形式所产生的recursive 将会终止于一个用于边界情况特化体。

      下面为计算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;

      当然,typelists的操作不应该就这几个功能而已,但看到这里相信大家对template 设计已有一定了解,其余的typelist功能也就留给大家去思考实作吧。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值