(看過了這麼多編譯期演算法之後)還記得運行時吧?我們已經在編譯期的天空飛行了好久,現在是時候腳踏實地了。一個有趣的程式終究還是要在運行時幹點什麼的。本章就是關於怎樣穿越C++編譯期和運行時的邊界——這一層“臭氧層”,如果你想要的話——這樣我們的元程式可以真正的使用者面前施展拳腳。在C++中,進行這趟旅程的辦法恐怕是無窮無盡,但是其中有一些更加有用,下面講到的就是最常用的一些技巧。
9.1 for_each
STL裡最簡單的演算法在MPL裡面也應該有,也的確有。先回顧一下:std::for_each 遍曆一個(運行時的)序列,並且對每一個元素調用某個(運行時的)仿函數。類似的,mpl::for_each 遍曆一個編譯期的序列,並且調用運行時仿函數。儘管 std::for_each 是完全運行時的,mpl::for_each 卻是一個混合體,橫跨在編譯期和運行時世界之間。
為什麼用運行時的仿函數?
如果你在想,為什麼 mpl::for_each 要採用一個運行時的仿函數,而不是一個元函數呢?那麼考慮一下這個:一般來說,std::for_each 用到的仿函數返回 void,即便它真的返回一個什麼值,那個值也是被忽略的。換言之,如果這個仿函數真的要做點什麼事情,那它一定要以某種方式修改程式的狀態。然而,函數式編程 (functional programming) 是天生無狀態的,而元編程是函數式編程,所以對一個序列的每個元素調用某個元函數並沒有多大意義,除非我們要對返回的結果做點什麼。
9.1.1 類型列印 (Type Printing)
你想過要列印你的類型序列 (type sequences) 的內容嗎?如果我們用的編譯器可以輸出可讀的 std::type_info::name ,那我們就可以這麼幹:
struct print_type
{
template <class T>
void operator()(T) const
{
std::cout << typeid(T).name() << std::endl;
}
};
typedef mpl::vector<int, long, char*> s;
int main()
{
mpl::for_each<s>(print_type());
}
這段代碼有幾個東西值得你注意:首先,print_type 的 operator() 是個模板,因為它必須能處理出現在我們的序列中的任何類型。除非你要處理的序列的所有元素都能轉化成某一個類型,否則 mpl::for_each 的仿函數就必須有一個模板化的 operator() 。
其次,注意 for_each 傳給仿函數的是其每個類型的一個值初始化 (value-initialized) 對象。這種形式在序列元素都是整數常量 wrapper 的時候非常方便。然而,對別的類型就要小心了,如果那個元素是個引用,或者是沒有預設建構函式的類型,又或者乾脆就是個 void,這個演算法就沒辦法編譯,因為它們都不能被值初始化。
我們可以避免這個問題,只要加上一個小小的 wrapper:
template <class T>
struct wrap {};
// 包含引用
typedef mpl::vector<int&, long&, char*&> s;
mpl::for_each<
mpl::transform<s, wrap<_1> >::type
>(print_type());
我們也要調整仿函數的簽名,讓它能接受新的參數:
struct print_type
{
template <class T>
void operator()(wrap<T>) const // 推匯出 T
{
std::cout << typeid(T).name() << std::endl;
}
};
由於這個用法如此常見,MPL提供了第二種形式的 for_each ,它接受一個轉換元函數作為第二參數。採用這種形式,我們就不用自己 wrap 序列了:
mpl::for_each<s, wrap<_1> >(print_type());
對 s 中的每一個元素 T,print_type 都被調用,參數是 wrap<T>。
9.1.2 類型訪問 (Type Visitation)
如果需要更加通用的方法來解決“函數調用邊界上的”類型問題,我們可以採用 Vistor 模式。
struct visit_type //generalized visitation function object
{
template <class Visitor>
void operator()(Visitor) const
{
Visitor::visit();
}
};
template <class T> //specific visitor for type printing
struct print_visitor
{
static void visit()
{
std::cout << typeid(T).name() << std::endl;
}
};
int main()
{
mpl::for_each<s, print_visitor<_1> >(visit_type());
}
在這裡,仿函數 visit_type 期望它的參數類型擁有一個 static 成員函數 visit ,我們可以讓 visitor 對象做任何事情。相對於前面的 for_each 例子來說,這是一個很小的變化,但是注意:print_visitor::visit 從來就沒有接受一個真正的 T 類型的對象,相反地,對於序列中的每一個 T,for_each 都把一個 print_visitor<T> 類型的執行個體傳遞給 visit_type,而關於 T 的類型資訊是在 print_visitor 的模版參數裡面傳送的。