可变参数宏 __VA_ARGS__ 有个著名的难题:怎么知道参数有几个?预处理器没有循环、没有计数,但利用”占位符平移”技巧可以完美解决。

技巧原理

1
2
3
#define _PrintMacroArgCount(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,XX,...) XX
#define PrintMacroArgCount(...) \
_PrintMacroArgCount("ignored", ##__VA_ARGS__,9,8,7,6,5,4,3,2,1,0)

关键在参数的错位对齐

  • 传 3 个参数时,展开成 _PrintMacroArgCount("ignored", 1, 2, 3, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
  • _0 吃掉 “ignored”(占位符),_1~`_3 吃掉真实参数,XX` 恰好落在 3
  • 传 0 个参数时,XX 落在 0 上,顺便解决了 __VA_ARGS__ 为空时的逗号问题(##__VA_ARGS__ 会吃掉多余的逗号)

应用:按参数个数生成函数名

配合拼接宏,可以让宏根据参数个数自动选择不同的函数

1
2
3
4
5
6
7
#define PP_VA_NAME(prefix,...) \
PP_CAT2(prefix, PrintMacroArgCount(__VA_ARGS__))

#define func(...) PP_VA_NAME(func_, __VA_ARGS__)(__VA_ARGS__)

// func(1,2,3) 展开成 func_3(1,2,3)
// func(1) 展开成 func_1(1)

这就是 C 语言版的”函数重载”——参数个数不同,自动分发到不同的实现。注意最多支持 10 个参数(占位符只有 10 个位置),需要更多就延长表格。

验证

1
2
printf("%d\n", PrintMacroArgCount());                    // 0
printf("%d\n", PrintMacroArgCount(1,2,3,4,5,6,7,8,9,10,11)); // 超过上限按边界处理