c++ metaprogramming 入門第二篇

上回說到一個fac的版本, 希望在負數的情況下返回-1, 而不是無限遞歸下去.
還是按照我們的思維, 先寫個對應"運行時世界"的版本.

int safe_fac(int n)
{
  if( n < 1)
     return -1;
  return fac(n);
}

這個if邏輯很簡單, 如果模板參數<1, 那麼直接返回 -1, 否則 還是使用前面的fac那個版本.
好, 轉換成我們的meta 版本.

你想,用個 ?: 運算符不就解決了嗎?

template<int n>
struct safe_fac
{
  enum { value =  (n < 1 ? -1 : fac<n>::value ) };
};

可惜不對,  ?= 只有在"運行時世界"才能使用. 那麼 value 後面的???寫什麼好呢?

先輕鬆輕鬆, 寫一個if的meta 版本, 我敢保證你能看得懂.
template< bool b , class T, class U>
struct if_
{
  typedef T type;
};

注意了, 如果以前我們提到的例如sum_, fac等meta functions(其實就是c++中的模板類, 稱之爲meta function是因爲它們就像是function)
是通過一個 在enum中的value 返回整形的話, 上面剛剛的if_這個例子就展示了 meta中的另外一個武器, 通過typedef 一個type 返回一個類型.

如果我們這樣調用
if_<true, int, double>::type  的結果就是 int 類型, 注意是"類型", 不是對象.

我們想在b爲false的時候返回第二個類型U, 即:
if_<false, int, double>::type 的返回結果是double

那麼還是很簡單, 部分特化 b 參數就可以了.即:
template<class T, class U>
struct if_<false, T, U>
{
  typedef U type;
};

我最前面說了, VC6不支持部分特化, 但是別忘了計算機時間的一條公理:
任何計算機問題都可以通過增加一層來解決. 大部分VC6中的模板的問題還是可以解決的. 如果你不使用VC6, 這部分就不用看了.

VC6是支持全部特化的, 因此我們可以將true, false特化出來

template<bool>
struct if_help
{
   ...
};

template<>
struct if_help<false>
{
   ...
};

這個在vc6中是支持的. 然後我們還需要兩個額外的類型參數T,U, 這可以通過嵌套類來實現. 即

template<bool>
struct if_help
{
  template<class T, class U>
  struct In
  {
     typedef T type;
  };
};

template<>
struct if_help<false>
{
  template<class T, class U>
  struct In
  {
     typedef U type;
  };
};

然後我們真正的if_ "meta 函數"如下定義:

template<bool b, class T, class U>
struct if_
{
   typedef if_help<b>::In<T, U>::type type;
};

先根據b的內容實例化一個對應的if_help, : if_help<b>
然後給其中的In模板投遞T,U參數, ::In<T, U>
然後通過type獲得其中的返回類型 ::type
最後typedef type一下作爲自己的返回類型, 這樣外部就可以通過
if_<true, int, double>::type  獲得返回的類型了.

上面if_ 的實現實際上要用幾個C++關鍵字修飾一下:
typedef if_help<b>::In<T, U>::type type;
 ===>
typedef typename if_help<b>::template In<T, U>::type type;

爲什麼要加上typename 和 template, 這個解釋起來到是很費勁. 有空再說.

好了, 從模板的語法世界中清醒過來, 現在你知道的是, 我們有了一個if_ 的meta函數, 接受3個參數bool b, class T, class U,
如果b爲true, 那麼它的返回值 (通過 if_::type 返回) 就是T,
如果b爲false, 那麼它的返回值 (通過 if_::type 返回) 就是U.

前面我提過了, 參數是通過<>來傳遞的, 因此一個例子就是

if_                          //函數名
if_<true,                    //第一個參數, bool 型
if_<true, int                //第二個參數, 類型
if_<true, int, double        //第3個參數,  類型
if_<true, int, doubble>      //右括號表示參數結束
if_<true, int, double>::type //通過::type獲得返回結果, 不是value了, 當然這僅僅是一個命名慣例.

因此上面的那個 if_<true, int, double>::type 返回的就是 int,
在"運行時世界", 你可以如下使用:

for( if_<true, int, double>::type i = 0; i < 10; i++) {
  cout << "line " << i << "/n";
}

等同於
for( int i = 0; i < 10; i++) {
  cout << "line " << i << "/n";
}

當然對於這個例子這樣使用是"有病". 我們等會會用到if_來實現前面的  safe_fac 實現了.

注意我說的返回值並不是前面sum_例子中的整形了, 這個時候返回一個類型. 類型不是實例對象, 這點我想你應該清楚.
編譯時不可能返回對象, 因爲要調用構造函數, 要確定對象地址, 我們還沒有進入到" 運行時世界" , 對吧?
實際上meta programming 最重要的使用並不是前面我們提到過的sum_, fac這些, 因爲畢竟拿個計算器算一下也花不了幾個時間.
但是返回type就不同了.
那麼type可以是什麼呢? 可以是int, double這樣的基本類型, 也可以我們前面的 sum_ 模板類等等.

然後再看safe_fac的實現:
先不考慮 <1 的情況, 那麼value就應該是直接調用以前的 fac 函數

template<int n>
struct safe_fac
{
  enum { value = fac<n>::value };
};

然後再使得 >= 1時才使用fac函數, 那麼利用我們前面的if_, 先忽略語法錯誤, 那麼可以如下

enum
{
value = if_< n < 1,???,  fac<n> >::type::value
};

首先, n < 1 爲真時返回一個類型???, 暫時我們還沒有實現, 爲false時返回
fac<n>類型, 然後通過::type獲得返回的類型,或者爲???, 或者爲fac<n>,
然後通過::value得到這個類型的整數結果.

那麼 ??? 應該是什麼呢? 當然不能直接是-1, 否則 -1::value 就是語法錯誤.

因此我們可以定義一個簡單的"函數", 返回-1:

struct return_neg1
{
   enum { value = -1 };
};

如果我們需要返回-2怎麼辦? 又定義一個return_neg2 "函數"? 乾脆我們一勞永逸, 定義如下:

template<int n>
struct int_
{
   enum { value = n };
};

這樣int_ 這個"函數"就是你給我什麼, 我就返回什麼. 不過int_是一個類型, 例如:
通過如下調用 int_<3>::value  返回它的結果3.

有了這個, 我們的代碼就如下:

value = if_< n < 1, int_<-1>,  fac<n> >::type::value

原理清楚了, 最終的版本就是:

template<int n>
struct safe_fac
{
  enum { value = if_< n < 1, int_<-1>,  fac<n> >::type::value };
};

試試:
cout << safe_fac<-1>::value 輸出 -1.

循環(遞歸表示), 條件判斷, 順序執行都有了, 剩下的就看你自己了.

--------------------------------------------------------------------------------
Boost中的mpl (meta programming library) 提供了一個專門用於metaprogramming的library, 同時前面提到的if_, int_等等就
是從mpl中拷貝來的, 當然我簡化了很多.

Modern C++ Design 其中的Typelist將meta progamming的循環(就是遞歸)發揮得淋漓盡致, 在侯捷的網站上www.jjhou.com有免費的前4章可讀. Typelist在第三章.
書中序言部分, Effective C++的作者Meyers說到, 如果第3章的typelists沒有讓你感到振奮, 那你一定是很沉悶.
就我親身體驗, 我覺得Meyers可能是說到委婉了些:
如果第3章的typelists沒有讓你感到振奮, 那1%的可能是你是很沉悶, 99%的可能是你沒有看懂. I did.
GoF之一的 John Vlissides同樣提到了typelists這一章就值得本書的價格. I believe.

另外, 我發現即使在這本讓人抓狂的天才之作中, 作者仍然使用了n個TYPELIST_n 這樣的預處理宏來處理多個type的情況, 但是在sourceforge
下的Loki庫中我發現已經有了一個MakeTypelist"函數", 看來 meta programming 確實是, 啊...
是不是作者當時都沒有預見到還能夠以C++編譯器內建的模板能力, 而不是依賴預處理宏來處理typelist.?

又, mpl中有專門裝type的容器....


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章