C++11 模板元編程 - TypeList高階算法


針對(duì)list的高階算法,是允許用戶在使用時(shí)傳入別的元函數(shù)做參數(shù)的元函數(shù)掀潮。在函數(shù)式語(yǔ)言里對(duì)于list的高階元函數(shù)(如經(jīng)典的filter菇夸,mapfold...)仪吧,可以輕松地讓用戶通過(guò)函數(shù)組合來(lái)擴(kuò)展對(duì)list的操作庄新。

下面我們先實(shí)現(xiàn)一個(gè)簡(jiǎn)單的__any()高階元函數(shù),它接收一個(gè)list以及一個(gè)單參元函數(shù)Pred做入?yún)ⅲㄔ撛瘮?shù)接受一個(gè)類(lèi)型薯鼠,返回一個(gè)BoolType類(lèi)型)择诈;__any()對(duì)list中每個(gè)元素調(diào)用Pred,如果Pred對(duì)某一個(gè)元素返回TrueType出皇,則__any()返回TrueType羞芍;否則如果Pred對(duì)所有的元素都返回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()的遞歸實(shí)現(xiàn)中荷科,對(duì)list的頭元素調(diào)用Pred:Pred<Head>::Result,如果為真纱注,則返回TrueType畏浆;否則對(duì)list的剩余尾list繼續(xù)調(diào)用__any()Any<Tail, Pred>::Result>::Result;一直遞歸到空l(shuí)ist狞贱,最后返回FalseType刻获。

在這里我們用了元函數(shù)IfThenElse,由于它的惰性瞎嬉,我們可以在Pred為當(dāng)前元素求值為T(mén)rueType的時(shí)候提前停止遞歸蝎毡,后面的元素就不用再算了。另外也可以這樣實(shí)現(xiàn):using Result = __bool(__value(typename Pred<Head>::Result) ? true : __value(typename Any<Tail, Pred>::Result))氧枣。這種實(shí)現(xiàn)用了三元表達(dá)式沐兵,但由于這種運(yùn)行時(shí)技術(shù)缺乏惰性,所以實(shí)際上每個(gè)元素都被求值了便监!

接下來(lái)我們實(shí)現(xiàn)元函數(shù)__all()痒筒。它的入?yún)⒑?code>any()一樣,差別在于所有的元素應(yīng)用Pred后都返回TrueType茬贵,__all()才會(huì)返回TrueType簿透;只要有一個(gè)元素應(yīng)用Pred后返回FalseType,則__all()返回FalseType解藻。

參考了__any()的實(shí)現(xiàn)老充,我們輕松實(shí)現(xiàn)__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()的實(shí)現(xiàn)非常相像螟左!事實(shí)上啡浊,我們可以借助__any()來(lái)實(shí)現(xiàn)__all()觅够,從而消除這兩者之間的重復(fù)。

首先我們需要借助__any()計(jì)算list中是否有某一元素應(yīng)用Pred后為假巷嚣,如果沒(méi)有一個(gè)為假喘先,則__all()返回真。我們知道客戶傳入給__all()的Pred是一個(gè)單參元函數(shù)廷粒,它在滿足條件后返回真窘拯。我們需要把客戶傳入的Pred轉(zhuǎn)變成另一個(gè)在滿足條件后返回假的單參元函數(shù)。于是我們實(shí)現(xiàn)了NegativeOf元函數(shù)坝茎,它是一個(gè)高階函數(shù)涤姊,接受一個(gè)返回值是BoolType的單參元函數(shù),返回一個(gè)取反之后的單參元函數(shù)嗤放。

// 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()的新版本實(shí)現(xiàn)如下:

// "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庫(kù)自身的實(shí)現(xiàn)中對(duì)元函數(shù)的調(diào)用都使用的是其模板格式思喊。如果使用每個(gè)元函數(shù)封裝過(guò)后的宏格式的話,實(shí)現(xiàn)如下:

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

相關(guān)的測(cè)試用例如下:

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中我們使用前面介紹過(guò)的__func_forward_1定義了一個(gè)單參元函數(shù)IsLargerThan2Bytes次酌,它會(huì)判斷入?yún)㈩?lèi)型的size是否大于兩個(gè)字節(jié)恨课。

接下來(lái)我們?cè)賮?lái)實(shí)現(xiàn)一個(gè)高階元函數(shù)__map(),它的用法如下面的測(cè)試用例:

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()的入?yún)⑿枰粋€(gè)list和一個(gè)類(lèi)型映射元函數(shù)岳服。它會(huì)對(duì)入?yún)ist的每個(gè)元素調(diào)用映射函數(shù)庄呈,最終將list映射成一個(gè)新的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()的實(shí)現(xiàn)如下:

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庫(kù)一共實(shí)現(xiàn)了如下針對(duì)list的高階元函數(shù):

  • __any(List, Pred(T)):對(duì)List中每個(gè)元素調(diào)用Pred,如果有一個(gè)返回TrueType斜纪,則__any返回TrueType贫母,否則返回FalseType;

  • __all(List, Pred(T)):對(duì)List中每個(gè)元素調(diào)用Pred盒刚,如果有所有都返回TrueType腺劣,則__all返回TrueType,否則返回FalseType因块;

  • __transform(List1, List2, Func(T1, T2)):遍歷List1和List2橘原,對(duì)兩個(gè)list中每對(duì)元素調(diào)用Func,根據(jù)Func的返回值類(lèi)型組成一共新的List返回涡上;

  • __filter(List, Pred(T)):過(guò)濾List中所有調(diào)用Pred為真得類(lèi)型趾断,組成一個(gè)新的List返回;

  • __map(List, Func(T)):將List中每個(gè)元素調(diào)用Func映射成一個(gè)新的類(lèi)型吩愧,返回映射后類(lèi)型組成的新List芋酌;

  • __fold(List, Acc, Func(T1, T2)):折疊算法;將List所有元素按照Func給的算法折疊成一個(gè)值返回雁佳,Acc是折疊啟動(dòng)參數(shù)脐帝;

  • __sort(List, Compare(T1, T2)):將List按照傳入的Compare規(guī)則進(jìn)行排序同云,返回排序后的新List;

上述沒(méi)有介紹到的List的高階算法的實(shí)現(xiàn)在這里就不再詳述了堵腹,感興趣的話請(qǐng)直接閱讀TLP的源碼炸站。TLP的“tlp/test/TestList.cpp”文件中有這些元函數(shù)的詳細(xì)測(cè)試,通過(guò)測(cè)試也可以看到它們的詳細(xì)用法疚顷。

最后旱易,高階元函數(shù)的強(qiáng)大之處是可以讓代碼在更高的抽象層次上進(jìn)行復(fù)用和組合,通過(guò)更貼近領(lǐng)域的語(yǔ)義描述出操作意圖荡含。如下測(cè)試用例中咒唆,我們想要計(jì)算一組類(lèi)型中大于兩字節(jié)的所有類(lèi)型的size之和,我們使用前面介紹的高階元函數(shù)進(jìn)行組合來(lái)實(shí)現(xiàn):

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));
};

上述測(cè)試是在64位操作系統(tǒng)上的計(jì)算結(jié)果(指針释液、int和long都是8字節(jié)全释,short是4字節(jié))。在計(jì)算中先通過(guò)__filter()在list中過(guò)濾出所有大于2字節(jié)的類(lèi)型误债,然后通過(guò)__map()將過(guò)濾后的類(lèi)型列表映射成對(duì)應(yīng)的size數(shù)值列表浸船,最后通過(guò)__fold()元函數(shù)進(jìn)行累計(jì)求和。上述所有操作的抽象層次都將list作為一個(gè)整體寝蹈,沒(méi)有降級(jí)到針對(duì)list內(nèi)部進(jìn)行算法描述李命。


TypeList應(yīng)用舉例

返回 C++11模板元編程 - 目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市箫老,隨后出現(xiàn)的幾起案子封字,更是在濱河造成了極大的恐慌,老刑警劉巖耍鬓,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阔籽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡牲蜀,警方通過(guò)查閱死者的電腦和手機(jī)笆制,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)涣达,“玉大人在辆,你說(shuō)我怎么就攤上這事《忍Γ” “怎么了匆篓?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)寇窑。 經(jīng)常有香客問(wèn)我奕删,道長(zhǎng),這世上最難降的妖魔是什么疗认? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任完残,我火速辦了婚禮伏钠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谨设。我一直安慰自己熟掂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布扎拣。 她就那樣靜靜地躺著赴肚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪二蓝。 梳的紋絲不亂的頭發(fā)上誉券,一...
    開(kāi)封第一講書(shū)人閱讀 50,096評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音刊愚,去河邊找鬼踊跟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鸥诽,可吹牛的內(nèi)容都是我干的商玫。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼牡借,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼拳昌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起钠龙,我...
    開(kāi)封第一講書(shū)人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤炬藤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后碴里,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體沈矿,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年并闲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谷羞。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帝火,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出湃缎,到底是詐尸還是另有隱情犀填,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布嗓违,位于F島的核電站九巡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蹂季。R本人自食惡果不足惜冕广,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一疏日、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撒汉,春花似錦沟优、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至溯饵,卻和暖如春侵俗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丰刊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工隘谣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藻三。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓洪橘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親棵帽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子熄求,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容