带有 gcc 和 clang 的可变参数宏扩展的奇怪行为

Weird behavior of variadic macro expansion with gcc and clang

本文关键字:扩展 变参 gcc clang 带有 参数      更新时间:2023-10-16

我正在用C++编写一个可变参数调度程序宏,根据提供给调度程序的参数数量(从无到5(调用不同的宏。我想出了这个解决方案:

#define GETOVERRIDE(_ignored, _1, _2, _3, _4, _5, NAME, ...) NAME
#define NAMEDARGS(...) GETOVERRIDE(ignored, ##__VA_ARGS__, NAMEDARGS5, NAMEDARGS4, NAMEDARGS3, NAMEDARGS2, NAMEDARGS1, NAMEDARGS0)(__VA_ARGS__)

NAMEDARGS 是调度程序宏;使用 1 个参数调用它将导致调用需要 1 个参数的 NAMEDARGS1,依此类推(我不提供各种 NAMEDARGS# 的实现,因为它们在此上下文中无关紧要(。

我测试了代码 gcc 7.1.1,在使用 -std=c++14 标志时,我发现 gcc 扩展的奇怪行为。使用此测试代码:

NAMEDARGS()
NAMEDARGS(int)
NAMEDARGS(int, float)

我得到了这些扩展:

$ gcc -E testMacro.cpp
NAMEDARGS0()
NAMEDARGS1(int)
NAMEDARGS2(int, float)
$ gcc -E -std=c++14 testMacro.cpp
NAMEDARGS1()
NAMEDARGS1(int)
NAMEDARGS2(int, float)

似乎使用 -std=c++14 标志,零参数调用的替换失败,导致单参数宏的调用。我认为这可能是因为##__VA_ARGS__语法是GNU扩展,因此不适用于ISO C++预处理器;但是,当尝试使用 clang 4.0.1 时,我获得了所需的扩展:

$ clang -E -std=c++14 testMacro.cpp
NAMEDARGS0()
NAMEDARGS1(int)
NAMEDARGS2(int, float)

所以我不明白这里发生了什么。clang 是否实现了这个 gnu 扩展,也接受非 ISO 代码,与 gcc 不同,使用 -std==c++14?或者问题出在别处?感谢您的帮助。

GCC默认-stdgnu++14(见这里(,这是GNU扩展的C++14。

将两者与仅定义的NAMEDARGS(...)进行比较,可以显示扩展的不同之处:

法典

#define NAMEDARGS(...) GETOVERRIDE(ignored, ##__VA_ARGS__, NAMEDARGS5, NAMEDARGS4, NAMEDARGS3, NAMEDARGS2, NAMEDARGS1, NAMEDARGS0)(__VA_ARGS__)
NAMEDARGS()

-std=gnu++14 -E

GETOVERRIDE(ignored, NAMEDARGS5, NAMEDARGS4, NAMEDARGS3, NAMEDARGS2, NAMEDARGS1, NAMEDARGS0)()
-------------------^

-std=c++14 -E

GETOVERRIDE(ignored,, NAMEDARGS5, NAMEDARGS4, NAMEDARGS3, NAMEDARGS2, NAMEDARGS1, NAMEDARGS0)()
-------------------^^

我不是一个有经验的标准读者,但我在[cpp.replace]中发现了以下两段话,这表明GCC在这两个调用中都是正确的:

如果宏定义中的

标识符列表不以省略号结尾,则调用类似函数的宏中的参数数(包括那些不包含预处理标记的参数(应等于宏定义中的参数数。否则,调用中的参数应多于宏定义中的参数(不包括...(。应存在终止调用的 ( 预处理令牌。

如果有...紧接在类似函数的宏定义中的 ( 之前,尾随参数(包括任何分隔逗号预处理标记(将合并为单个项目:变量参数。如此组合的参数数使得在合并后,参数数比宏定义中的参数数多 1(不包括 ...(。

将空__VA_ARGS__扩展为单个空参数似乎是正确的。

我找不到叮叮当在这里的行为是否是有意的。