C++是否有跨编译器可移植的可变参CHECK/PROBE宏参数计数实现?
问题根因
这是MSVC传统预处理器的已知遗留行为,和标准要求的展开逻辑不一致:
- 符合标准的预处理器(GCC、Clang,以及加了
/Zc:preprocessor开关的新版MSVC)在传递宏参数前,会先对参数内容做完整宏展开,展开后产生的逗号会被正常识别为参数分隔符。 - MSVC默认使用的旧预处理器对
__VA_ARGS__有特殊处理:如果可变参数直接出现在宏替换列表的另一个宏调用参数中,预处理器不会提前展开__VA_ARGS__内的宏,也不会识别其中的逗号做参数拆分,而是直接把整个__VA_ARGS__块作为单个参数传递给下层宏。
原代码里CHECK直接把__VA_ARGS__传给CHECK_N,MSVC环境下PROBE(~)根本没有被展开拆分,直接作为第一个参数传给CHECK_N,自然永远取到第二个位置的0。
跨编译器兼容方案
不需要用#ifdef写多套分支实现,只需要加一层极薄的间接展开宏,强制触发预处理器多做一轮参数扫描即可,该写法在GCC、Clang、新旧MSVC环境下行为完全一致:
#define CHECK_N(x, n, ...) n #define CHECK(...) EXPAND(CHECK_N(__VA_ARGS__, 0,)) #define PROBE(x) x, 1, #define EXPAND(...) __VA_ARGS__
展开逻辑说明
这套写法的核心是避免CHECK_N的调用直接出现在CHECK的替换列表中,而是通过EXPAND宏间接输出:
- 处理
CHECK(PROBE(~))时,第一轮替换先得到EXPAND(CHECK_N(PROBE(~), 0,))。展开EXPAND时,所有编译器都会先对EXPAND的参数做完整扫描展开,PROBE(~)被替换为~, 1,,最终传入CHECK_N的参数序列是~, 1, , 0,,匹配后n位的值为1,符合预期。 - 处理
CHECK(xxx)时,第一轮替换得到EXPAND(CHECK_N(xxx, 0,)),xxx没有可展开的宏,传入CHECK_N的参数序列是xxx, 0,,匹配后n位的值为0,符合预期。
如果项目允许调整编译选项,给MSVC加上
/Zc:preprocessor开关即可直接启用标准合规的预处理器,原代码不需要修改就能运行,但上面给出的宏写法不需要依赖编译选项,兼容性更强。
内容的提问来源于stack exchange,提问作者Athena
相关产品推荐
相关产品推荐

