6 min to read
判斷參數數目
目錄: C/C++ Macro 宏系列 | CWKSC’s blog | 博客
來個簡單應用:判斷參數數目
▌選取第 n 項參數的宏
首先需要一個 選取第 n 項參數的宏
#define get1th(a1, ...) a1
#define get2th(a1, a2, ...) a2
#define get3th(a1, a2, a3, ...) a3
#define get4th(a1, a2, a3, a4, ...) a4
#define get5th(a1, a2, a3, a4, a5, ...) a5
// ... getnth(a1, a2, a3, ... , an, ...) an
很容易理解
get1th(a, b, c, d, e)
// a
get2th(a, b, c, d, e)
// b
get3th(a, b, c, d, e)
// c
Visual Studio 在處理 __VA_ARGS__
時會視為一個整體的 Token 來處理
我們需要製作一個新的版本專門給 VS
重掃描可以解決這個問題
我們先定義中間層,也就是 重掃描 那篇的 expend(...)
,不過換個名字
#define midLayer(...) __VA_ARGS__ // 中間層
然後是針對 VS 的新版本:
#define get1thVS(...) midLayer( get1th(__VA_ARGS__) )
#define get2thVS(...) midLayer( get2th(__VA_ARGS__) )
#define get3thVS(...) midLayer( get3th(__VA_ARGS__) )
#define get4thVS(...) midLayer( get4th(__VA_ARGS__) )
#define get5thVS(...) midLayer( get5th(__VA_ARGS__) )
// ... getnthVS(a1, a2, a3, ... , an, ...) midLayer( getnth(__VA_ARGS__) )
效果相同:
get3thVS(a, b, c, d, e)
// #define get3thVS(...) midLayer( get3th(__VA_ARGS__) )
midLayer( get3th(__VA_ARGS__) )
midLayer( get3th(a, b, c, d, e) )
// #define midLayer(...) __VA_ARGS__
__VA_ARGS__
get3th(a, b, c, d, e)
// #define get3th(a1, a2, a3, ...) a3
a3
c
▌判斷參數數目
#define parameterNum(...) get5thVS(__VA_ARGS__, 4, 3, 2, 1)
這裏利用到 __VA_ARGS__
展開時導致 參數項數目改變 的特性
當初學到這個的時候實在是很驚喜、意想不到
來直接看展開流程
parameterNum(a)
// #define parameterNum(...) get5thVS(__VA_ARGS__, 4, 3, 2, 1)
get5thVS(__VA_ARGS__, 4, 3, 2, 1)
get5thVS(a, 4, 3, 2, 1)
// #define get5thVS(...) midLayer( get5th(__VA_ARGS__) )
midLayer( get5th(a) )
// #define midLayer(...) __VA_ARGS__
__VA_ARGS__
get5th(a, 4, 3, 2, 1)
// #define get5th(a1, a2, a3, a4, a5, ...) a5
a5
1
再來其他例子
parameterNum(a, b, c, d)
// #define parameterNum(...) get5thVS(__VA_ARGS__, 4, 3, 2, 1)
get5thVS(__VA_ARGS__, 4, 3, 2, 1)
get5thVS(a, b, c, d, 4, 3, 2, 1)
// #define get5thVS(...) midLayer( get5th(__VA_ARGS__) )
midLayer( get5th(a, b, c, d) )
// #define midLayer(...) __VA_ARGS__
__VA_ARGS__
get5th(a, b, c, d, 4, 3, 2, 1)
// #define get5th(a1, a2, a3, a4, a5, ...) a5
a5
4
還有
parameterNum(?) // 1
parameterNum(?, ?) // 2
parameterNum(?, ?, ?) // 3
parameterNum(?, ?, ?, ?) // 4
__VA_ARGS__
展開時改變了參數項數目
把原本在前面的參數項擠到後面
再配合 選取第 n 項參數的宏
就可以判斷參數數目,是不是很神奇
▌分辨 0 和 1 個參數數目
上面的例子可以看到, 1 個參數數目時
parameterNum(a)
成功展開了 1
但是,目前的 parameterNum
無法分辨 0 個參數數目的情況
就是說:
parameterNum() // 1
parameterNum(?) // 1
我們來試一次
parameterNum()
// #define parameterNum(...) get5thVS(__VA_ARGS__, 4, 3, 2, 1)
get5thVS(__VA_ARGS__, 4, 3, 2, 1)
get5thVS(, 4, 3, 2, 1)
// #define get5thVS(...) midLayer( get5th(__VA_ARGS__) )
midLayer( get5th() )
// #define midLayer(...) __VA_ARGS__
__VA_ARGS__
get5th(, 4, 3, 2, 1)
// #define get5th(a1, a2, a3, a4, a5, ...) a5
a5
1
parameterNum()
和 parameterNum(a)
都是展開了 1
我們預期 parameterNum()
會展開 0
爲了解決這個問題,會利用 Visual Studio 2019 和 C++20 的宏 提到的 __VA_OPT__
這是 C++ 20 的新功能,會判斷 ...
有沒有輸入參數
當可變參數丟失(不僅僅是空),不會展開括弧裏的 Token
反之而言,沒有丟失則展開
#define parameterNum(...) get5th( __VA_ARGS__ __VA_OPT__(,) 4, 3, 2, 1, 0)
試一次
parameterNum()
// #define parameterNum(...) get5thVS( __VA_ARGS__ __VA_OPT__(,) 4, 3, 2, 1, 0)
get5th( __VA_ARGS__ __VA_OPT__(,) 4, 3, 2, 1, 0)
get5th(4, 3, 2, 1, 0)
// #define get5th(a1, a2, a3, a4, a5, ...) a5
a5
0
試 parameterNum(a)
parameterNum(a)
// #define parameterNum(...) get5thVS( __VA_ARGS__ __VA_OPT__(,) 4, 3, 2, 1, 0)
get5th( __VA_ARGS__ __VA_OPT__(,) 4, 3, 2, 1, 0)
get5th(a, 4, 3, 2, 1, 0)
// #define get5th(a1, a2, a3, a4, a5, ...) a5
a5
1
現在的話就沒有問題了:
parameterNum() // 0
parameterNum(?) // 1
parameterNum(?, ?) // 2
parameterNum(?, ?, ?) // 3
parameterNum(?, ?, ?, ?) // 4
▌判斷參數數目的上限
parameterNum()
是依賴 get5th()
來做的
get5th()
確定了 parameterNum()
可判斷參數數目的上限
get5th()
最多可判斷 4 個,get7thVS()
可判斷 6 個 . . . . . .
getnth()
可判斷 n-1
個
我個人會寫好十個左右的 getnth()
,get1th() ~ get10th()
正常夠用的了,如果 9 個可判斷參數數目都不夠用的話 …
該反省一下這個函數的設計,會不會過於復雜
▌結語
爲了以免一直說理論
就來了個簡單的應用:判斷參數數目
如果知道可變參數函數的話
你會發現這個宏配合可變參數函數會很有用