c++ 模板超程式設計的一點體會

twoon發表於2014-10-04

趁著國慶長假快速翻了一遍傳說中的、大名鼎鼎的 modern c++ design,鈦合金狗眼頓時不保,已深深被其中各種模板奇技淫巧傷了身。。。論語言方面的深度,我看過的 c++ 書裡大概只有 insight c++ object model 能與之一戰吧?難怪 Herb 老喜歡調侃 Andrei 在模板方面是個可怕的傢伙,就從這本書的質量來看,Andrei 當之無愧。

c++ 模板超程式設計的能量遠比第一眼印象裡所能想像得要強大,當然,這個結論並不明顯,很多時候人們也就拿模板當作減少重複程式碼的工具簡單使用,很少有人會像寫 STL, boost, loki 那樣子正兒八經完全以模板為根基進行創作,箇中原因是多樣的,一個結果就是,模板的威力即使在很多 c++ 熟手的手下也不容易充分展現出來,當然,從工程的角度來講,這並非就是壞事,並不見得非得窮盡語言的高階特性,寫出讓人驚歎的程式碼才是好程式碼,實戰中更多時候講究的是簡潔,易讀,好上手好維護這些基本原則,特別是當團隊中人員在語言水平上參差不齊時更是如此,杜甫的詩也許更工整嚴謹,但白居易的詩則顯然更老少皆宜,這個體會我是在閱讀 boost.proto 的程式碼時得來的,好奇的讀者可以自行去了解一下,注意做好安全措施。

從語言特性上來說,模板超程式設計具備了一個完備的程式語言所必需的一些基本結構,比如說,迴圈,分支,判斷等,當然,這些結構在模板中可能也不太明顯,例如迴圈,它的實現的關鍵在於使用遞迴,出口點在特化,typedef 和 enum 則可以當成是編譯時的變數,它們都能在編譯時遞迴式地依賴於別的模板,尤其是 enum, 甚至可以通過使用三元操作符(:?)實現編譯時判斷,而至於分支(if/else),它們的實現顯然就在於特化了。

以上的說辭可能有些虛無,下面以 Loki 中光彩奪目的 typelist 為例簡單展示一下模板都能做些什麼。

typelist 是這樣一個好東西,你可以把它看成是一個連結串列,該連結串列中放的是型別,具體結構定義如下:

template <class T, class U>
struct Typelist
{
  typedef T Head;
  typedef U Tail;
};

Head 是 typelist 中當前節點所儲存的 type, Tail 是 typelist 中該節點之後的其它部分,可以看成是一般連結串列中的 next 指標,怎麼判斷哪個節點是 typelist 的最後一個節點呢?在一般連結串列中,next 為空的節點是最後一個節點,同理,在 typelist 中我們可以定義一個空的結點:

struct NullType
{
};

有了如上的定義,於是我們可以按如下樣子來使用 typelist 了:

typedef Typelist<char, Typelist<int, Typelist<short, NullType> >

但是上面的寫法怎麼看都很難用很彆扭,而且極端不美觀,在這個看臉的時代這樣怎麼行,所以 Loki 定義了一大堆巨集來減輕使用者的負擔,看這裡,因為當時 c++ 的標準還不支援 variadic template parameter,這些巨集事實上是相當死板噁心的,但這也是沒法的事,不看它的實現就好了。

就這麼一個簡單的結構,現在我們要讓它支援查詢,定位,插入,刪除等常規操作,是不是覺得有些為難甚至覺得不可能? 答案是這些操作都是可行的,比如說查詢,現在給定一個如上定義的 typelist,怎麼判斷該 typelist 中是否存在某一個特定型別呢?答案如下,其它的操作本質上差不多,有興趣的讀者可自行挑戰一下,這裡就不在囉嗦:

template<class TL, class T> struct IndexOf;

// 當搜尋空的 typelist 時,結果為 -1.

template<>
struct IndexOf<NullType, T>
{
  enum { value = -1 };
};

// 當前節點的型別為所想要搜尋的型別時

template<class T, class Tail>
struct IndexOf<Typelist<T, Tail>, T>
{
  enum { value = 0 };
};

// 當前節點不是所查詢的型別時,遞迴地在 Tail 中進行查詢。

template<class Head, class Tail, class T>
struct IndexOf<Typelist<Head, Tail>, T>
{
  enum { in_tail = IndexOf<Tail, T>::value };
  
  // 使用三元操作符進行判斷,T 是否在 Tail 中存在。
  enum { value = (in_tail >= 0)? 1 + in_tail: -1 };
};

從上面的例子我們可以看到,因為 c++ 支援對模板遞迴式的解析(也就是一個模板依賴於另一個模板時,先解釋被依賴的模板),尤其是 template template parameter,使得模板事實上有了很強的編譯時執行的能力,這種能力表面上看起來可能不容易操控,但卻顯然是潛力無窮的,不過它的缺點也比較明顯:

  1. 編譯時程式碼與執行時程式碼攪在一起,在處理複雜問題時,程式的邏輯可能不容易讀懂。
  2. 編譯時除錯現階段的支援還不夠好,錯誤資訊異常地冗餘而且不精確。

網路上關於 c++ 模板超程式設計的討論有很多,模板的各種能力技巧也漸漸被越來越多的人所發現所挖掘,但是在實際的工作中,對很多人來說模板超程式設計卻仍一直處於比較保守的階段,說到底過分依賴模板超程式設計所帶來的缺點還是太明顯,就我粗陋的見聞來說,完全基於模板超程式設計做出來的比較出名的工具型的東西,主要有幾個(都來自 boost):boost spirit,boost proto 及 boost mpl,東西都非常非常棒,但使用的體驗嘛,老實說都不是很好。。。特別是 spirit。而至於它們的實現,對有興趣練習深入這方面技能的程式猿來說,這兩者倒確實是不可多得好素材,尤其是 proto, 代表了一個高峰。

好訊息是,伴隨著 c++11 的到來,好些眾人期盼以久的新特性終於從理想照進現實,尤其是 variadic parameter 的加入,可以預見將再度大大提升模板的能力,c++ 標準沉寂近10年後迎來了一個新時期,甚至還有人曾經提議要加入 static_if,concept 等概念也在醞釀中了,變化是唯一永恆不變的東西,你,作好準備了嗎?

相關文章