遞迴之美 - Loki庫TypeList原始碼剖析 (轉)

worldblog發表於2007-12-13
遞迴之美 - Loki庫TypeList原始碼剖析 (轉)[@more@] 

遞迴之美 - Loki庫TypeList原始碼剖析

鄧 輝

 

TypeList概觀

提起List,想必大家都不會陌生,它是一個元素的集合,並且提供了一些對該集合進行操作的方法,比如:計算集合中元素的個數、向集合中新增元素、獲取給定處的元素等。我們所熟知的List中的元素一般都是例項化後的值,相關的操作也都是在執行期間進行的。本文將要剖析的List和上述的List在某種意義上比較相象,不過它所包含的元素都是型別(type),相關的操作是在編譯期間進行的。

本文將要講述的TypeList取自Andrei Alexandrescu的力作《Modern C++ Design》一書相關的Loki庫,關於《Modern C++ Design》,C++的愛好者想必不會陌生,在該書中,作者向我們展現了C++設計的全新思維,把C++的表達能力發揮到了極至,而Loki庫正是這種思維的具體表現。TypeLsit是Loki庫中最為基礎、核心的,理解了TypeList就具備了觀賞這道C++新景觀的基礎。

下面我們先來看看如何定義一個TypeList,這樣可以對於TypeList先有一個感性的認識。

typedef TYPELIST_3(char, int, long) MyTypeList;

定義了一個具有三個元素的TypeLsit,這三個元素分別為:char、int、long。TYPELIST_3為Loki庫中提供的用於定義TypeList的工具,我們會在本文的後面進行介紹。

::Loki::Length::value;

計算MyTypeList中元素的個數,結果為3。

typedef ::Loki::TypeAt::Result MyType;

獲取MyTypeList中第1個元素(從0開始),此時MyType就是int。

typedef ::Loki::Append::Result MyTypeList1;

向MyTypeList中在新增一個元素:float,結果為MyTypeList1。此時::Length::value等於4。

好了,先介紹這麼多,下面我們將介紹TypeList實現的一些相關的背景知識,包括:遞迴的基本概念、模板的特化(tempalte specilization)、模板的偏特化(template partial specilization)以及型別萃取技術(type traits)。

TypeList相關技術

遞迴概述

對於遞迴,大家肯定都不陌生,使用遞迴方法給出的解決方案總是顯得非常的優雅、簡潔。不過遞迴方法所適合解決的問題應該符合下面的條件:

    1. 一個問題的解決依賴於一個較小規模的同樣的問題的解決

    2. 必須有一個明確的結束條件

    3. 這個結束條件是可達的

如果一個問題符合上述的三個條件,我們就可以使用遞迴的方法。首先我們定義一套解決問題的規則,接著縮小問題的規模並應用同樣的規則直到達到結束條件,然後結果層層返回直到原始問題。著名的漢諾塔問題就是一個典型的遞迴問題,如果不使用遞迴方法,解決漢諾塔問題就會顯得非常的複雜,晦澀。

我們在使用遞迴方法設計時,這個遞迴過程的總是在執行期間進行的。本文所介紹的TypeList的實現中,遞迴的是在編譯期間進行的,那麼在編譯期間如何定義每次遞迴的返回結果,如何定義結束條件呢?其中主要使用了下面將要介紹的模板特化、偏特化以及型別萃取技術。

模板特化和偏特化(template specilizaiton、partial specilization)

什麼是模板的特化、偏特化呢?大致的意思為:如果一個template擁有一個或者一個以上的template引數,我們可以針對其中一個或者多個引數進行特化處理(如果全部進行特化處理就是全特化,否則就是偏特化,切記:模板只能進行全特化,不能進行部分特化)。也就是說,我們可以提供一個特別版本,符合泛化條件,但是其中某些(全部)template引數已經由實際型別或者數值取代。

假設我們有一個class template定義如下:

template

class C { … };

對於模板的偏特化,剛剛接觸可能會存在一些誤解:以為模板的偏特化版本一定是對template引數U或者V或者T(或者它們的任意組合)指定某個引數值。其實不是這樣的,所謂模板的偏特化是指另外提供一份template的定義,它的具體含義可以和通用的template定義版本無關。在一個偏特化版本中,template 引數的個數並不需要吻合通用的 template 中的個數。然而,出現於於class 名稱之後的引數個數必須吻合通用的 template 的引數個數。下面舉一個簡單的例子進行說明:

template

class C { … };

這個泛型版本允許T為任意的型別。它的一個偏特化版本如下:

template

class C { … };

這個偏特化版本僅適用於T為原生指標型別的情況。

當我們使用C去定義一個變數時,會自動使用泛型版本,如果使用C去定義一個變數時,編譯器就會自動使用偏特化的版本。有了這個利器,我們就可以解決在編譯期間定義遞迴的結束條件的問題。

型別萃取(type traits)

型別萃取技術是泛型中的一個常用技術,它的思維核心為:把一系列與型別相關的性質包裹於單一的class 之內,這樣我們就可以在編譯期間獲取一些所需要的和該型別相關的東西。其實這個思路就是領域一句著名的諺語:“任何事情都可以透過新增額外的中間層次得以解決”的又一次體現。透過把一系列想得到的型別相關的資訊封裝在另外一個型別定義中,這樣就可以以一致的方式來對這些型別進行處理,提供了強大的可複用性和靈活性。

型別萃取技術一般都和模板的特化、偏特化技術結合在一起運用,這樣它們就可以互相補充發揮出巨大的威力。下面簡單舉一個例子來了解一下型別萃取技術。

我們來看看Boost庫中一個簡單的template class is_pointer的實現。我們需要一個主版本,用來處理T不為指標的所有情況,以及一個偏特化版本,用來處理T是指標的情況:

template

struct is_pointer

{ static const bool value = false; };

template

struct is_pointer

{ static const bool value = true; };

一個簡單的示例如下:

template

void Func(T param)

{

if (is_pointer::value) {

// do something

}

else {

//do something

}

}

透過型別萃取技術,我們就可以在編譯期間保留每次遞迴的結果,供遞迴返回時使用。

關於這些技術的更多、更深入的介紹,請讀者自行參考相關資料,不在此贅述。在下面的原始碼剖析中,讀者將會看到這些技術的實際運用。

TypeList實現剖析

有了上述的背景知識,下面我們就來揭開TypeList的神秘面紗,走進TypeList的原始碼世界。首先,我們來看一下TypeList的定義。

TypeList定義

為了能夠一致的進行TypeList的操作,在Loki庫中定義了一個空型別NullType來標記一個TypeList的結束,NullType和TypeList的定義如下:

class NullType { };

template

struct Typelist

{

typedef T Head;

typedef U Tail;

};

對於規範型TypeList的定義採用了一種尾遞迴的方法:

    1. NullType是規範的TypeLsit

    2. 如果T是規範的TypeList,那麼對於任意原子型別U,TypeList是規範的TypeList

Loki庫中所採用的TypeList均為規範型的TypeList,這樣可以在不減少靈活性的前提下簡化對於TypeList的操作。本文後面所指的TypeList均為規範型的。

如何定義一個TypeList呢?比如:包含:char、int、long三個元素的TypeLsit。根據上面的定義,可以得到如下的定義形式:

TypeList > >; // 注意兩個>間一定要加一個空格,不

// 然編譯器會認為是 “>>” 運算子

這樣的定義方法顯得比較麻煩、羅嗦,為了簡化對於TypeList的使用,Loki庫中採用了宏定義的方式對於大小在1~50的TypeList進行了預定義:

#define TYPELIST_1(T1) ::Loki::Typelist

#define TYPELIST_2(T1, T2) ::Loki::Typelist

#define TYPELIST_3(T1, T2, T3) ::Loki::Typelist

依此類推。

這樣使用者在使用起來就會比較方便一些。

TypeList典型操作實現

瞭解了TypeList的定義,這裡我們將對於TypeList相關的三個典型操作(Length、TypeAt和Append)的實現進行詳細的剖析。掌握了這幾個典型的操作,再學習其他的操作就會變得非常的容易。

我們將透過一個例項進行講解,來看一下編譯器實際的運作過程。我們定義了一個包含兩個元素:int以及long的TypeList。

typedef TYPELIST_2(int, long) MyTypeList;

此時,編譯器會根據TypeList的定義方式產生如下的型別定義結果:

struct TypeList

{

typedef long H;

typedef NullType T;

};

struct TypeList >

{

typedef int H;

typedef TypeList T;

};

 

Length的實現 - 獲取TypeList中的元素個數:

template struct Length; // 僅有宣告,沒有實現,如果所傳入的型別不是TypeLsit

// 的話,會產生一個編譯期錯誤

template <> struct Length // 遞迴呼叫的結束條件,NullType的大小為0,運用了

{ // 模板特化和型別萃取技

// 術

enum { value = 0 };

};

template // 遞迴的規則定義,運用了模板偏特化和型別萃取技術

struct Length< Typelist >

{

enum { value = 1 + Length::value };

};

 

當透過Length::value獲得MyTypeList中的元素個數時,看看編譯器是如何根據我們指定的規則進行遞迴呼叫的。首先編譯器會生成如下幾個版本的Length定以:

struct Length >

{

enum { value = 1 + Length::value };

};

struct Length > >

{

enum { value = 1 + Length >::value };

};

根據Length結束條件的定義可知,Length::value等於0,所以Length >::value就等於Length::value+1,也就是1。透過遞推可知,Length::value也就是Length > >::value等於Length >::value+1,也就是2。在層層的遞推過程中,型別萃取技術得到了充分的體現,value就是我們想要得到的TypeList型別相關的資訊,在每一層的遞迴過程中,都是透過它來保留結果的。

TypeAT的實現 - 獲取給定位置處的元素

template struct TypeAt; // 僅有宣告,沒有實現,如果所傳入

// 的型別不是TypeLsit

// 的話,會產生一個編譯期錯誤

template

struct TypeAt, 0> // 遞迴呼叫的結束條件,如果給定位置為0,則

{ // 返回TypeList中的第一個元素

typedef Head Result;

};

template //遞迴規則定義,注意這裡的返回結果為型別,

struct TypeAt, i> // 運用了型別萃取技術。typename關鍵字的作

{ // 用是告訴編譯器其後的實體是型別。

typedef typename TypeAt::Result Result;

};

下面看看使用TypeAt::Result時,編譯器都產生了那些動作。首先編譯器要根據遞迴規則生成如下的型別定義:

struct TypeAt , 0>

{

typedef long Result;

};

struct TypeAt > , 1>

{

typedef TypeAt , 0>::Result Result;

};

很明顯typedef TypeAt , 0>::Result Result; 就是typedef long Result;所以,TypeAt::Result就是long型別。同樣的,在這個實現中充分使用了型別萃取技術,不過,這裡我們想要的不是value,而是Result。

 

Append的實現 - 在TypeList的末尾新增一個元素

template struct Append; // 僅有宣告,沒有實現,如果所傳入

// 的型別不是TypeLsit

// 的話,會產生一個編譯期錯誤

template <> struct Append // 遞迴結束條件定義,模板的偏特化

{

typedef NullType Result;

};

template struct Append //遞迴結束條件定義,模板的偏特化

{

typedef TYPELIST_1(T) Result;

};

template

struct Append > // 遞迴結束條件定義,模板的偏特化

{

typedef Typelist

Result;

};

template // 遞迴規則定義,注意這裡的返回結果為型別,

struct Append, T> // 運用了型別萃取技術。typename關鍵字的作

{ // 用是告訴編譯器其後的實體是型別。模板的偏特

// 化

typedef Typelist

typename Append::Result>

Result;

};

同樣的,讓我們來看看當使用Append::Result時,編譯器的遞迴執行動作。首先看看編譯器會生成的一些型別定義:

struct Append

{

typedef TYPELIST_1(float) Result;

};

struct Append , float>

{

typedef TypeList::Result > Result;

};

struct Append >, float>

{

typedef TypeList,float >::Result > Result;

};

經過簡單的替換,Append::Result就等於:

typedef TypeList,float >::Result >;

等於:

typedef TypeList::Result > >;

等於:

typedef TypeList > >;

也就是:

TYPELIST_3(int, long, float) ;等同於在原有TypeList的末尾新增了一個新元素float。不用多說了,這裡型別萃取技術同樣發揮了巨大的作用。

 

結束語

本文對於TypeLsit的實現進行了剖析,相信讀者朋友對於TypeList含義以及實現手法已經有所掌握。那麼TypeLsit到底有什麼用處呢?原始碼面前,了無秘密,掌握了TypeList,就掌握了全面理解Loki庫的鑰匙。在Loki庫中,你將會看到Abstract Factory、Visitor模式這些泛型元件是如何在TypeLsit的基礎上搭建起來的。背起你的行囊,拿起這把鑰匙,趕快踏上你的“尋寶”之路吧(Loki庫的可以從)。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992500/,如需轉載,請註明出處,否則將追究法律責任。

相關文章