前幾篇我介紹了一種不用 __VA_ARGS__ 實作可變參數 macro 的方式,例如可以這樣寫

PRINTF("%d %f %s", (1)(3.14)("hello"));
//展開後
printf("%d %f %s", 1, 3.14, "hello", '\0');

雖然看起來很接近我們的需求,不過最後插入的多餘字元非常礙眼,令人欲除之而後快。這個看似很簡單的工作卻超乎想像的困難,我耗掉了一些休閒時間仍然解決不了,至少得到的東西太過古怪以致於我都不太確定是否合法。

後來我忽然想到曾經在 Boost.Test 當中看過類似的寫法,但當時並沒有仔細思考他是怎麼實作的,這時想來卻不免好奇。於是我開始追蹤相關的程式碼,才發現 Boost 除了 MPL、Fusion、Lambda 這些新穎的 TMP 程式庫外,還有另一條長期被我忽視的蹊徑 -- Preprocessor Metaprogramming。

 

舉例來說

BOOST_PP_SEQ_ENUM( (B)(O)(O)(S)(T) )
// 展開成 B, O, O, S, T

其實他使用的概念和我這一兩天的想法類似,但我失敗在沒有想到可靠的程式碼串接功能,他則是針對不同的編譯器設計合適的串接功能。甘拜下風

 

又例如

BOOST_PP_SEQ_SIZE( (a)(b)(c) )
// 展開成 3

 

這會不會太 lisp 了...

#define LIST (1, (3, (5, (2, (4, BOOST_PP_NIL)))))

#define OP(d, state, x) BOOST_PP_MAX_D(d, state, x)

#define LIST_MAX(list) BOOST_PP_LIST_FOLD_LEFT(OP, 0, LIST)

LIST_MAX(LIST)
// 展開成 5

還有很多相當神奇的東西例如 foreach、iteration 等等常見的程式設計組件,但全都是用 Preprocessor 做出來的。

不過仔細看一下實作還真不是普通複雜,很多功能都是用相當暴力的方法完成的,並且受編譯器影響很大(我想也是),所以同一個功能會有一堆 #if 判斷該切換哪一個實作。雖然成果很神奇,但和 TMP 裡面的東西一樣,目前還很難想像這些東西的實用性。

novus 發表在 痞客邦 PIXNET 留言(0) 人氣()