C++11 模板元编程 - TypeList高阶算法


针对list的高阶算法,是允许用户在使用时传入别的元函数做参数的元函数。在函数式语言里对于list的高阶元函数(如经典的filtermapfold...),可以轻松地让用户通过函数组合来扩展对list的操作。

下面我们先实现一个简单的__any()高阶元函数,它接收一个list以及一个单参元函数Pred做入参(该元函数接受一个类型,返回一个BoolType类型);__any()对list中每个元素调用Pred,如果Pred对某一个元素返回TrueType,则__any()返回TrueType;否则如果Pred对所有的元素都返回FalseType,则__any()返回FalseType。

// "tlp/list/algo/Any.h"

template<typename TL, template<typename T> class Pred> struct Any;

template<template<typename T> class Pred>
struct Any<NullType, Pred>
{
    using Result = FalseType;
};

template<typename Head, typename Tail, template<typename T> class Pred>
struct Any<TypeElem<Head, Tail>, Pred>
{
    using Result = typename IfThenElse<typename Pred<Head>::Result, TrueType, typename Any<Tail, Pred>::Result>::Result;
};

#define __any(...)   typename Any<__VA_ARGS__>::Result

__any()的递归实现中,对list的头元素调用Pred:Pred<Head>::Result,如果为真,则返回TrueType;否则对list的剩余尾list继续调用__any()Any<Tail, Pred>::Result>::Result;一直递归到空list,最后返回FalseType。

在这里我们用了元函数IfThenElse,由于它的惰性,我们可以在Pred为当前元素求值为TrueType的时候提前停止递归,后面的元素就不用再算了。另外也可以这样实现:using Result = __bool(__value(typename Pred<Head>::Result) ? true : __value(typename Any<Tail, Pred>::Result))。这种实现用了三元表达式,但由于这种运行时技术缺乏惰性,所以实际上每个元素都被求值了!

接下来我们实现元函数__all()。它的入参和any()一样,差别在于所有的元素应用Pred后都返回TrueType,__all()才会返回TrueType;只要有一个元素应用Pred后返回FalseType,则__all()返回FalseType。

参考了__any()的实现,我们轻松实现__all()如下:

template<typename TL, template<typename T> class Pred> struct All;

template<template<typename T> class Pred>
struct All<NullType, Pred>
{
    using Result = TrueType;
};

template<typename Head, typename Tail, template<typename T> class Pred>
struct All<TypeElem<Head, Tail>, Pred>
{
    using Result = typename IfThenElse<typename Pred<Head>::Result, typename All<Tail, Pred>::Result, FalseType>::Result;
};

#define __all(...)   typename All<__VA_ARGS__>::Result

可以看到,__all()__any()的实现非常相像!事实上,我们可以借助__any()来实现__all(),从而消除这两者之间的重复。

首先我们需要借助__any()计算list中是否有某一元素应用Pred后为假,如果没有一个为假,则__all()返回真。我们知道客户传入给__all()的Pred是一个单参元函数,它在满足条件后返回真。我们需要把客户传入的Pred转变成另一个在满足条件后返回假的单参元函数。于是我们实现了NegativeOf元函数,它是一个高阶函数,接受一个返回值是BoolType的单参元函数,返回一个取反之后的单参元函数。

// tlp/func/NegativeOf.h

template<template<typename T> class Pred>
struct NegativeOf
{
    template<typename U>
    struct Apply
    {
        using Result = typename Not<typename Pred<U>::Result>::Result;
    };
};

#define __negative_of(...)  NegativeOf<__VA_ARGS__>::template Apply

最终__all()的新版本实现如下:

// "tlp/list/algo/All.h"

template<typename TL, template<typename T> class Pred>
struct All
{
    using Result = typename Not<typename Any<TL, NegativeOf<Pred>::template Apply>::Result>::Result;
};

TLP库自身的实现中对元函数的调用都使用的是其模板格式。如果使用每个元函数封装过后的宏格式的话,实现如下:

template<typename TL, template<typename T> class Pred>
struct All
{
    using Result = __not(__any(TL, __negative_of(Pred)));
};

相关的测试用例如下:

FIXTURE(TestAdvancedAlgo)
{
    __func_forward_1(IsLargerThan2Bytes, __bool((sizeof(_1) > 2)));

    TEST("any one of the list satisfied the given prediction")
    {
        ASSERT_TRUE(__any(__type_list(char, short, int), IsLargerThan2Bytes));
    };

    TEST("none of the list satisfied the given prediction")
    {
        ASSERT_FALSE(__any(__type_list(char, short), IsLargerThan2Bytes));
    };

    TEST("all of the list satisfied the given prediction")
    {
        ASSERT_TRUE(__all(__type_list(int, long), IsLargerThan2Bytes));
    };

    TEST("any of the type in list not satisfied the given prediction")
    {
        ASSERT_FALSE(__all(__type_list(int, long, short), IsLargerThan2Bytes));
    };
}

上面的fixture中我们使用前面介绍过的__func_forward_1定义了一个单参元函数IsLargerThan2Bytes,它会判断入参类型的size是否大于两个字节。

接下来我们再来实现一个高阶元函数__map(),它的用法如下面的测试用例:

TEST("map the list to it's size value list")
{
    __func_forward_1(TypeSize, __int(sizeof(_1)));

    using List = __type_list(int, char, short, long);
    using Expected = __value_list(4, 1, 2, 8);

    ASSERT_EQ(__map(List, TypeSize), Expected);
};

__map()的入参需要一个list和一个类型映射元函数。它会对入参list的每个元素调用映射函数,最终将list映射成一个新的list返回。__map()可以组合使用,如下:

TEST("map the type in list to it's twice pointer type list")
{
    template<typename T> struct TransToPointer { using Result = T*; };

    using List = __type_list(int, const char);
    using Expected = __type_list(int**, const char**);

    ASSERT_EQ(__map(__map(List, TransToPointer), TransToPointer), Expected);
};

__map()的实现如下:

template<typename TL, template<typename T> class Func> struct Map;

template<template<typename T> class Func>
struct Map<NullType, Func>
{
    using Result = NullType;
};

template<typename Head, typename Tail, template<typename T> class Func>
struct Map<TypeElem<Head, Tail>, Func>
{
    using Result = TypeElem<typename Func<Head>::Result, typename Map<Tail, Func>::Result>;
};

#define __map(...) typename Map<__VA_ARGS__>::Result

TLP库一共实现了如下针对list的高阶元函数:

  • __any(List, Pred(T)):对List中每个元素调用Pred,如果有一个返回TrueType,则__any返回TrueType,否则返回FalseType;

  • __all(List, Pred(T)):对List中每个元素调用Pred,如果有所有都返回TrueType,则__all返回TrueType,否则返回FalseType;

  • __transform(List1, List2, Func(T1, T2)):遍历List1和List2,对两个list中每对元素调用Func,根据Func的返回值类型组成一共新的List返回;

  • __filter(List, Pred(T)):过滤List中所有调用Pred为真得类型,组成一个新的List返回;

  • __map(List, Func(T)):将List中每个元素调用Func映射成一个新的类型,返回映射后类型组成的新List;

  • __fold(List, Acc, Func(T1, T2)):折叠算法;将List所有元素按照Func给的算法折叠成一个值返回,Acc是折叠启动参数;

  • __sort(List, Compare(T1, T2)):将List按照传入的Compare规则进行排序,返回排序后的新List;

上述没有介绍到的List的高阶算法的实现在这里就不再详述了,感兴趣的话请直接阅读TLP的源码。TLP的“tlp/test/TestList.cpp”文件中有这些元函数的详细测试,通过测试也可以看到它们的详细用法。

最后,高阶元函数的强大之处是可以让代码在更高的抽象层次上进行复用和组合,通过更贴近领域的语义描述出操作意图。如下测试用例中,我们想要计算一组类型中大于两字节的所有类型的size之和,我们使用前面介绍的高阶元函数进行组合来实现:

TEST("calculate the total size of the types that larger than 2 bytes")
{
    using List = __type_list(char, short, int, long, char*, short*, int*, long*);

    ASSERT_EQ(__fold(__map(__filter(List, IsLargerThan2Bytes), TypeSize) , __int(0), Add), __int(44));
};

上述测试是在64位操作系统上的计算结果(指针、int和long都是8字节,short是4字节)。在计算中先通过__filter()在list中过滤出所有大于2字节的类型,然后通过__map()将过滤后的类型列表映射成对应的size数值列表,最后通过__fold()元函数进行累计求和。上述所有操作的抽象层次都将list作为一个整体,没有降级到针对list内部进行算法描述。


TypeList应用举例

返回 C++11模板元编程 - 目录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容