引言
自 C 以來,宏為代碼生成工具。至 C++98 則有模板,泛型編程日益盛行。迄于 C++20,引編譯期表達式,添 Concepts,元編程基礎支持始漸完善。由此元編程之技稍簡。而靜態反射乃元編程系統之核心,卻遲久未至,產生式元編程遂仍繁復。
所述元編程之書文,指不勝屈,其間也以編譯期計算為主,奇技淫巧,小大靡遺。而于產生式元編程,言者寥寥,常見于庫中直用。于是有此系列,略述淺見,供同道者讀閱之。
產生式元編程,即為編譯期代碼生成的技術,各類系統,特性不侔,用法與能力亦有所殊。
問題
代碼生成以宏為先,雖是舊工具,然方今模板元編程的能力尚有不足,在產生式元編程的核心特性「源碼注入」進入標準之前,它仍是一種常用的代碼生成工具。
宏編程得從可變宏參數開始談起,可變模板參數的個數可以通過?sizeof...(args)?獲取,宏里面如何操作呢?便是本章討論的問題。
接著便逐步分析問題,實現需求,其間穿插宏編程之原理。
分析與實現
宏只有替換這一個功能,所謂的復雜代碼生成功能,都是基于這一核心理念演繹出來的。故小步慢走,循序漸進,便可降低理解難度。
問題是獲取宏參數包的個數,第一步應該規范過程。
過程的輸入是一系列對象,對象類型和個數是變化的;過程的輸出是一個值,值應該等于輸入對象的個數。如果將輸入替換為任何類型的其他對象,只要個數沒變,結果也應保持不變。
于是通過宏函數表示出過程原型:
?
#define?COUNT_VARARGS(...)?N
?
根據宏的唯一功能可知,輸出只能是一個值。依此便可否定遍歷迭代之類的常規方式,可以考慮多加一層宏,通過由特殊到普遍的思想來不斷分析,推出最終結果。
?
#define?GET_VARARGS(a)?1 #define?COUNT_VARARGS(...)?GET_VARARGS(__VA_ARGS__)
?
目前這種實現只能支持一個參數的個數識別,可通過假設,在特殊的基礎上逐次增加參數。于是得到:
?
#define?GET_VARARGS(a,?b)?2 #define?GET_VARARGS(a)????1 #define?COUNT_VARARGS(...)?GET_VARARGS(__VA_ARGS__)
?
若假設成立,通過暴力法已能夠將特殊推到普遍,問題遂即解決。但是宏并不支持重載,有沒有可能實現呢?通過再封裝一層,消除名稱重復。得到:
?
#define?GET_VARARGS_2(a,?b)?2 #define?GET_VARARGS_1(a)????1 #define?GET_VARARGS(...)????GET_VARARGS_X(__VA_ARGS__) #define?COUNT_VARARGS(...)??GET_VARARGS(__VA_ARGS__)
?
至此可知,若要實現宏重載效果,必須確定?GET_VARARGS_X?中的?X,而它又和參數個數相同,問題繞了一圈又回到起點,說明此路不通。
因此,回到特殊情況重新分析,函數名稱已確定不能重復,唯一能夠改變的就只剩下輸入和輸出。既然不能重載,那么輸出也就不能直接寫出,先同時改變輸入和輸出,滿足特殊情況再說。
?
#define?GET_VARARGS(N,?...)?N #define?COUNT_VARARGS(...)??GET_VARARGS(1,?__VA_ARGS__)
?
已經支持一個參數,嘗試寫出兩個參數的情況。
?
#define?GET_VARARGS(N1,?N2,?...)?N? #define?COUNT_VARARGS(...)?GET_VARARGS(1,?2,?__VA_ARGS__)
?
輸出是唯一確定的,這種嘗試也以失敗告終,于是排除改變輸出的可能性,只余下輸入是可以改變的。繼續嘗試:
?
#define?GET_VARARGS(N1,?N2,?...)?N2 #define?COUNT_VARARGS(...)?GET_VARARGS(__VA_ARGS__,?1,?2)
?
稍微改變輸入順序,便有了新的發現:當?__VA_ARGS__?個數為 1 時,N2 此時為 1;當為 0 時,N2 為 2。這表明間接層的輸入參數之間具備著某種關系,接著擴大樣本,尋找規律。
?
#define?GET_VARARGS(N1,?N2,?N3,?N4,?N5,?...)?N5 #define?COUNT_VARARGS(...)?GET_VARARGS(__VA_ARGS__,?1,?2,?3,?4,?5)
?
列出表格分析:
參數個數 | N5 |
---|---|
0 | 5 |
1 | 4 |
2 | 3 |
3 | 2 |
4 | 1 |
由此可知,參數個數和輸出順序相反且少 1。故修改實現為:
?
#define?GET_VARARGS(N1,?N2,?N3,?N4,?N5,?...)?N5 #define?COUNT_VARARGS(...)?GET_VARARGS(__VA_ARGS__,?4,?3,?2,?1,?0)
?
通過發現的規律,我們實現了由特殊到普遍的過程,函數的參數個數一般是有限的,只要再通過暴力法擴大數值范圍,便能夠為該需求實現一個通用的工具。
檢驗與優化
普遍性的解決方案還需要實踐的檢驗,因為實現當中可能還會存在技術問題。這里的?__VA_ARGS__?就是一個問題,當輸入參數個數為 0 時,替換之后會留下一個?,,這又打破了普遍性。
通過查閱資源,發現?##__VA_ARGS__?可以消除這種情況下的逗號,但是它不能寫在參數的開頭。根據這個約束,改變參數,進一步優化實現。得到:
?
#define?GET_VARARGS(Ignored,?N1,?N2,?N3,?N4,?N5,?...)?N5 #define?COUNT_VARARGS(...)?GET_VARARGS("ignored",?##__VA_ARGS__,?4,?3,?2,?1,?0)
?
至此,該實現終于具備普遍性,接著整理接口名稱,使其更加規范。變為:
?
#define?GET_VARARGS(_0,?_1,?_2,?_3,?_4,?N,?...)?N #define?COUNT_VARARGS(...)?GET_VARARGS("ignored",?##__VA_ARGS__,?4,?3,?2,?1,?0)
?
在此基礎上,將它由 4 推到 20、50、100…… 都不成問題,只要選擇一個合適夠用的數值就行。
再進一步檢測,將其擴展到其他編譯器進行編譯,并嘗試切換不同的語言版本編譯,觀察結果是否依舊一致。經過檢測,發現?##__VA_ARGS__?的行為并不通用,不同語言版本和編譯期的結果都不盡相同。此時就需要進一步查找解決方案,增強實現的通用性。
最終查找到 C++20 的?__VA_OPT__?已經解決了這個問題,于是替換實現。
?
#define?GET_VARARGS(_0,?_1,?_2,?_3,?_4,?N,?...)?N #define?COUNT_VARARGS(...)?GET_VARARGS("ignored"?__VA_OPT__(,)?__VA_ARGS__,?4,?3,?2,?1,?0) int?main()?{ ????printf("zero?arg:?%d ",?COUNT_VARARGS()); ????printf("one?arg:?%d ",?COUNT_VARARGS(1)); ????printf("two?args:?%d ",?COUNT_VARARGS(1,?2)); ????printf("three?args:?%d ",?COUNT_VARARGS(1,?2,?3)); ????printf("four?args:?%d ",?COUNT_VARARGS(1,?2,?3,?4)); }
?
若要支持 C++20 之前的版本,那么只需要再尋找辦法,增加預處理即可。
通用性增強
經上述分析實現,「計算可變宏參數個數」的需求已基本由?COUNT_VARARGS?實現。
在此先總結一下用到的思想和技術,再往前行。
1.1 通過增加一個間接層,能夠解決無法直接解決的問題。
1.2 小步快走,由特殊逐漸擴展到普遍,能夠降低問題的解決難度。
1.3 規范過程,確認變與不變,逐步控制變量,能夠全面分析問題。
1.4 嘗試改變輸入的順序、大小、個數…… 也許能有新發現。
1.5 初步發現規律時,擴大樣本驗證,能夠將特殊推到普遍。
1.6 可變宏參數作為輸入和欲輸出結果組合起來,其間規律可以表達條件邏輯。
1.7 擴展編譯器及語言版本,能夠更全面地測試解決方案的普遍性。
下面就來進一步完善解決方案,使?COUNT_VARARGS?支持 C++20 以下版本。
問題的關鍵在于?##__VA_ARGS__?不具備通用性,此時一種精妙的技術是分情況討論,即零參和多參分別處理。考慮起初分析時的一條失敗道路:
?
#define?GET_VARARGS_2(a,?b)?2 #define?GET_VARARGS_1(a)????1 #define?GET_VARARGS(...)????GET_VARARGS_X(__VA_ARGS__) #define?COUNT_VARARGS(...)??GET_VARARGS(__VA_ARGS__)
?
這是函數重載的思路,因必須確定?GET_VARARGS_X?中的?X,而?X?又和可變宏參數相關,可變宏參數又屬于無限集,遂無法確定這個?X,致此路不通。但若改變前提條件,使?X?的值屬于有限集,這條路便可走通,這里便能夠利用起來。只考慮零參和多參的情況,X?的取值范圍就可以定在 0 和 1 之間。只需設法判斷可變宏參數屬于哪種情況,就可借此實現重載,分發處理邏輯。
根據假設,寫出基本代碼:
?
#define?_COUNT_VARARGS_0(...)?N #define?_COUNT_VARARGS_1()?0 #define?COUNT_VARARGS_0(...)?_COUNT_VARARGS_1(__VA_ARGS__) #define?COUNT_VARARGS_1(...)?_COUNT_VARARGS_0() #define?OVERLOAD_INVOKE(call,?version)?call?##?version #define?COUNT_VARARGS(...)?OVERLOAD_INVOKE(COUNT_VARARGS_,?IS_EMPTY(__VA_ARGS__))
?
定義兩個重載宏函數?COUNT_VARARGS_1?和?COUNT_VARARGS_0,前者處理無參情況,后者處理多參情況。如此一來,空包時?IS_EMPTY?返回 1,調用分發到?COUNT_VARARGS_1,最終結果直接置為 0;多參時?IS_EMPTY?返回 0,調用分發到?COUNT_VARARGS_1,使用前面的方法處理即可。
于是主要問題就為如何實現?IS_EMPTY。根據結論 1.3 分析新的需求,其輸入是可變宏參數,過程是判斷空或非空,結果是 1 或 0。過程依舊是條件邏輯,而結論 1.6 正好可以表達條件邏輯,于是初步有了實現思路。
根據設想,繼續寫出基礎代碼:
?
#define?HAS_COMMA(_0,?_1,?_2,?...)?_2 #define?IS_EMPTY(...)?HAS_COMMA(__VA_ARGS__,?0,?1)
?
空包時,第一步替換結果為?HAS_COMMA(, 0, 1),第二步替換結果為 1。列出表格,分析更多樣本。
IS_EMPTY()?輸入 | 替換結果 |
---|---|
? | 1 |
1 | 1 |
, | 0 |
1,2 | 0 |
可以發現,空包和 1 個參數情況的結果相等,為 1;多參情況的結果相等,為 0。擴大?HAS_COMMA?的參數之后,結果依然成立。
?
#define?HAS_COMMA(_0,?_1,?_2,?_3,?_4,?_5,?...)?_5 #define?IS_EMPTY(...)?HAS_COMMA(__VA_ARGS__,?0,?0,?0,?0,?1)
?
如今難題變成如何區分空包和 1 個參數,這個問題依舊不好處理。宏的唯一功能只有替換,可以嘗試將空包替換成多參的同時,保持 1 個參數不變,這樣就可以區分。下面的代碼用于說明這個技巧:
?
#define?_TRIGGER_PARENTHESIS(...)?, #define?TEST(...)?_TRIGGER_PARENTHESIS?__VA_ARGS__?()
?
若參數為空,TEST()?第一步被替換為?_TRIGGER_PARENTHESIS (),第二步被替換為?,。而?,?等價于?a, b,屬于兩個參數,便達到了將空包變為多參的需求。若是參數為 1,TEST?第一步被替換為?_TRIGGER_PARENTHESIS 1 (),宏不會繼續展開,只要不使用該參數,它也不會報錯,還是當作 1 個參數。如此一來,問題解決,開始將基礎代碼合并。
?
#define?_TRIGGER_PARENTHESIS(...)?, #define?_COMMA_CHECK(_0,?_1,?_2,?_3,?_4,?_5,?...)?_5 #define?HAS_COMMA(...)?_COMMA_CHECK(__VA_ARGS__,?0,?0,?0,?0,?1) #define?IS_EMPTY(...)?HAS_COMMA(?_TRIGGER_PARENTHESIS?__VA_ARGS__?()?)
?
重新列表格分析:
IS_EMPTY()?輸入 | 替換結果 |
---|---|
? | 0 |
1 | 1 |
, | 0 |
1,2 | 0 |
() | 0 |
當前問題完美解決!暫停下來,調整接口,讓它更加規范。一般 1 為真,0 為假,而現在 1 和 0 的意義完全相反,無法顧名思義,先把它改變過來。調整代碼為:
?
#define?_TRIGGER_PARENTHESIS(...)?, #define?_COMMA_CHECK(_0,?_1,?_2,?_3,?_4,?_5,?...)?_5 #define?HAS_COMMA(...)?_COMMA_CHECK(__VA_ARGS__,?1,?1,?1,?1,?0) #define?IS_EMPTY(...)?HAS_COMMA(?_TRIGGER_PARENTHESIS?__VA_ARGS__?()?)
?
調整后的表格為:
IS_EMPTY()?輸入 | 替換結果 |
---|---|
? | 1 |
1 | 0 |
, | 1 |
1,2 | 1 |
() | 1 |
隨后分析新產生的問題,可以發現多參之間又無法區分是否為空包。解決這個問題的思路是多條件約束,從結果集中依次剔除不滿足的元素。先把使用技巧前后的表格匯總起來,尋找規律。
HAS_COMMA()?參數 輸入 | ? | 1 | , | 1,2 | () |
---|---|---|---|---|---|
__VA_ARGS__ | 0 | 0 | 1 | 1 | 0 |
_TRIGGER_PARENTHESIS __VA_ARGS__ () | 1 | 0 | 1 | 1 | 1 |
這兩個條件組合起來,便可進一步排除包含?,?的情況。在此基礎上,充分利用排列組合,再增加了兩個條件,表格變為:
HAS_COMMA()?參數 輸入 | ? | 1 | , | 1,2 | () |
---|---|---|---|---|---|
__VA_ARGS__ | 0 | 0 | 1 | 1 | 0 |
_TRIGGER_PARENTHESIS __VA_ARGS__ | 0 | 0 | 1 | 1 | 1 |
__VA_ARGS__ () | 0 | 0 | 1 | 1 | 0 |
_TRIGGER_PARENTHESIS __VA_ARGS__ () | 1 | 0 | 1 | 1 | 1 |
首先,使用?_TRIGGER_PARENTHESIS_ __VA_ARGS__ ()?區分了空包和 1 參的情況,得到的結果集包含空包和多參。其次,借助?__VA_ARGS__?區分了空包和?,?(即為多參)的情況,兩相組合,結果集便只剩下空包。
但還有一些特殊情況,比如第一個輸入參數包含?(),此時?HAS_COMMA((1))展開為?HAS_COMMA(, ())?,一個參數替換后變成兩個參數會造成誤判,以?_TRIGGER_PARENTHESIS __VA_ARGS__?能夠進一步排除這類結果。
最后,如果輸入為一個宏或無參函數,例如?HAS_COMMA(Foo),替換后就變成了?HAS_COMMA(_TRIGGER_PARENTHESIS Foo ()),結果可能會出乎意料,通過?__VA_ARGS__ ()?能夠排除這類結果。
對于無參宏/函數,舉個例子,#define Foo() 1, 2,結果加入表格中如下。
HAS_COMMA()?參數 輸入 | ? | 1 | , | 1,2 | () | Foo |
---|---|---|---|---|---|---|
__VA_ARGS__ | 0 | 0 | 1 | 1 | 0 | 0 |
_TRIGGER_PARENTHESIS __VA_ARGS__ | 0 | 0 | 1 | 1 | 1 | 0 |
__VA_ARGS__ () | 0 | 0 | 1 | 1 | 0 | 1 |
_TRIGGER_PARENTHESIS __VA_ARGS__ () | 1 | 0 | 1 | 1 | 1 | 1 |
這四個條件組合起來,也就是說四個條件的結果為?0001,就可以唯一確認空包。現在便可運用老辦法,添加重載,當重載為?0001?時,處理空包,返回 1,其他所有情況返回 0。實現如下:
?
#define?_COMMA_CHECK(_0,?_1,?_2,?_3,?_4,?_5,?_6,?_7,?_8,?_9,?_10,?R,?...)?R #define?HAS_COMMA(...)?_COMMA_CHECK(__VA_ARGS__,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?0) #define?_IS_EMPTY_CASE_0001?, #define?_IS_EMPTY_CASE(_0,?_1,?_2,?_3)?_IS_EMPTY_CASE_?##?_0?##?_1?##?_2?##?_3 #define?_IS_EMPTY_IMPL(_0,?_1,?_2,?_3)?HAS_COMMA(_IS_EMPTY_CASE(_0,?_1,?_2,?_3)) #define?IS_EMPTY(...)? ????_IS_EMPTY_IMPL(? ????????HAS_COMMA(__VA_ARGS__),? ????????HAS_COMMA(_TRIGGER_PARENTHESIS_?__VA_ARGS__),? ????????HAS_COMMA(__VA_ARGS__?()),? ????????HAS_COMMA(_TRIGGER_PARENTHESIS_?__VA_ARGS__?())? ????)
?
四個條件的處理結果進一步傳遞到?_IS_EMPTY_IMPL,它又通過?_IS_EMPTY_CASE?組裝成重載特化?_IS_EMPTY_CASE_0001。其他所有情況都沒有定義相應的重載,因而經由?HAS_COMMA?調用?_COMMA_CHECK?的參數個數都相同,最終只會返回 0。而?_IS_EMPTY_CASE_0001?被替換為?,,相當于參數個數加一,最終返回 1。
子問題解決,現在回到原問題,看看最初的實現:
?
#define?_COUNT_VARARGS_0(...)?N #define?_COUNT_VARARGS_1()?0 #define?COUNT_VARARGS_0(...)?_COUNT_VARARGS_1(__VA_ARGS__) #define?COUNT_VARARGS_1(...)?_COUNT_VARARGS_0() #define?OVERLOAD_INVOKE(call,?version)?call?##?version #define?COUNT_VARARGS(...)?OVERLOAD_INVOKE(COUNT_VARARGS_,?IS_EMPTY(__VA_ARGS__))
?
IS_EMPTY?只有空包時才會返回 1,其他情況都返回 0。因此當前的重載版本已經可以使用,空包時最終的參數直接由?_COUNT_VARARGS_1?將結果替換為 0。對于?_COUNT_VARARGS_0?多參版本,只要把上節的實現添加起來便可以:
?
#define?_COUNT_VARARGS_0_IMPL(_0,?_1,?_2,?_3,?_4,?N,?...)?N #define?_COUNT_VARARGS_0(...)?_COUNT_VARARGS_0_IMPL(__VA_ARGS__,?5,?4,?3,?2,?1)
?
到這一步,這個問題就解決了。根據總結 1.7,進一步切換編譯器檢驗一下方案的普遍性,發現 MSVC 結果錯誤。
原來是 MSVC 每次傳遞?__VA_ARGS__?時,都會將其當作整體傳遞。例如:
?
#define?A(x,?...)?x?and?__VA_ARGS__ #define?B(...)?A(__VA_ARGS__) B(1,?2);?//?替換為?1,?2?and
?
一種解決方案是強制宏展開,實現很簡單:
?
#define?EXPAND(x)?x #define?A(x,?...)?x?and?__VA_ARGS__ #define?B(...)?EXPAND(?A(__VA_ARGS__)?) B(1,?2);?//?替換為?1?and?2
?
為了適配 MSVC,我們需要將所有傳遞的?__VA_ARGS__?都強制展開。最終的實現為:
?
#include?#define?EXPAND(x)?x #define?_TRIGGER_PARENTHESIS(...)?, #define?_COMMA_CHECK(_0,?_1,?_2,?_3,?_4,?_5,?_6,?_7,?_8,?_9,?_10,?R,?...)?R #define?HAS_COMMA(...)?EXPAND(?_COMMA_CHECK(__VA_ARGS__,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?0)?) #define?_IS_EMPTY_CASE_0001?, #define?_IS_EMPTY_CASE(_0,?_1,?_2,?_3)?_IS_EMPTY_CASE_?##?_0?##?_1?##?_2?##?_3 #define?_IS_EMPTY_IMPL(_0,?_1,?_2,?_3)?HAS_COMMA(_IS_EMPTY_CASE(_0,?_1,?_2,?_3)) #define?IS_EMPTY(...)? ????_IS_EMPTY_IMPL(? ????????EXPAND(?HAS_COMMA(__VA_ARGS__)?),? ????????EXPAND(?HAS_COMMA(_TRIGGER_PARENTHESIS?__VA_ARGS__)?),? ????????EXPAND(?HAS_COMMA(__VA_ARGS__?())?),? ????????EXPAND(?HAS_COMMA(_TRIGGER_PARENTHESIS?__VA_ARGS__?())?)? ????) #define?_COUNT_VARARGS_0_IMPL(_0,?_1,?_2,?_3,?_4,?N,?...)?N? #define?_COUNT_VARARGS_0(...)?EXPAND(?_COUNT_VARARGS_0_IMPL(__VA_ARGS__,?5,?4,?3,?2,?1)?) #define?_COUNT_VARARGS_1()?0 #define?COUNT_VARARGS_0(...)?EXPAND(?_COUNT_VARARGS_0(__VA_ARGS__)?) #define?COUNT_VARARGS_1(...)?_COUNT_VARARGS_1() #define?OVERLOAD_INVOKE(call,?version)?call?##?version #define?OVERLOAD_HELPER(call,?version)?OVERLOAD_INVOKE(call,?version) #define?COUNT_VARARGS(...)?EXPAND(?OVERLOAD_HELPER(COUNT_VARARGS_,?IS_EMPTY(__VA_ARGS__))(__VA_ARGS__)?) int?main()?{ ????//?0?1?2?3?4 ????printf("%d?%d?%d?%d?%d ", ????????COUNT_VARARGS(),?COUNT_VARARGS(1),?COUNT_VARARGS('a',?'b'), ????????COUNT_VARARGS('a',?'b',?'c'),?COUNT_VARARGS('a',?'b',?1,?2)); }
?
這種解決方案不僅支持 C++ 所有版本,而且在 C 中也能使用。
合并方案
最后,將兩種方案合并起來,便能得到一個通用的實現。借助預處理分語言版本選擇不同的實現,C++20 以上使用新的簡潔做法,C++20 以下使用這種通用但復雜的做法。
?
#include?#if?defined(__cplusplus)?&&?__cplusplus?>=?202002L ????#define?GET_VARARGS(_0,?_1,?_2,?_3,?_4,?N,?...)?N ????#define?COUNT_VARARGS(...)?GET_VARARGS("ignored"?__VA_OPT__(,)?__VA_ARGS__,?4,?3,?2,?1,?0) #else ????#define?EXPAND(x)?x ????#define?_TRIGGER_PARENTHESIS(...)?, ????#define?_COMMA_CHECK(_0,?_1,?_2,?_3,?_4,?_5,?_6,?_7,?_8,?_9,?_10,?R,?...)?R ????#define?HAS_COMMA(...)?EXPAND(?_COMMA_CHECK(__VA_ARGS__,?1,?1,?1,?1,?1,?1,?1,?1,?1,?1,?0)?) ????#define?_IS_EMPTY_CASE_0001?, ????#define?_IS_EMPTY_CASE(_0,?_1,?_2,?_3)?_IS_EMPTY_CASE_?##?_0?##?_1?##?_2?##?_3 ????#define?_IS_EMPTY_IMPL(_0,?_1,?_2,?_3)?HAS_COMMA(_IS_EMPTY_CASE(_0,?_1,?_2,?_3)) ????#define?IS_EMPTY(...)? ????????_IS_EMPTY_IMPL(? ????????????EXPAND(?HAS_COMMA(__VA_ARGS__)?),? ????????????EXPAND(?HAS_COMMA(_TRIGGER_PARENTHESIS?__VA_ARGS__)?),? ????????????EXPAND(?HAS_COMMA(__VA_ARGS__?())?),? ????????????EXPAND(?HAS_COMMA(_TRIGGER_PARENTHESIS?__VA_ARGS__?())?)? ????????) ????#define?_COUNT_VARARGS_0_IMPL(_0,?_1,?_2,?_3,?_4,?N,?...)?N? ????#define?_COUNT_VARARGS_0(...)?EXPAND(?_COUNT_VARARGS_0_IMPL(__VA_ARGS__,?5,?4,?3,?2,?1)?) ????#define?_COUNT_VARARGS_1()?0 ????#define?COUNT_VARARGS_0(...)?EXPAND(?_COUNT_VARARGS_0(__VA_ARGS__)?) ????#define?COUNT_VARARGS_1(...)?_COUNT_VARARGS_1() ????#define?OVERLOAD_INVOKE(call,?version)?call?##?version ????#define?OVERLOAD_HELPER(call,?version)?OVERLOAD_INVOKE(call,?version) ????#define?COUNT_VARARGS(...)?EXPAND(?OVERLOAD_HELPER(COUNT_VARARGS_,?IS_EMPTY(__VA_ARGS__))(__VA_ARGS__)?) #endif int?main()?{ ????printf("version:?%ld ",?__cplusplus); ????//?0?1?2?3?4 ????printf("%d?%d?%d?%d?%d ", ????????COUNT_VARARGS(),?COUNT_VARARGS(1),?COUNT_VARARGS('a',?'b'), ????????COUNT_VARARGS('a',?'b',?'c'),?COUNT_VARARGS('a',?'b',?1,?2)); }
?
總結
本章以可變宏參數計數為例,穿插講解宏編程的核心原理,又涵蓋問題分析思路與完善步驟,小步慢走,終是實現需求。
由此原理,宏以能表示基本邏輯,亦能演繹出諸多新工具,反過來簡化如今的實現。
評論
查看更多