预处理器宏可以只扩展一些粘贴的参数吗?

Can a preprocessor macro expand just some pasted parameters?

本文关键字:参数 处理器 扩展 预处理      更新时间:2023-10-16

我知道在扩展类似函数的预处理器宏时,顶级替换列表中的###标记基本上"在"参数的任何宏扩展之前"起作用。 例如,给定

#define CONCAT_NO_EXPAND(x,y,z) x ## y ## z
#define EXPAND_AND_CONCAT(x,y,z) CONCAT_NO_EXPAND(x,y,z)
#define A X
#define B Y
#define C Z

那么CONCAT_NO_EXPAND(A,B,C)是PP令牌ABCEXPAND_AND_CONCAT(A,B,C)是PP令牌XYZ

但是,如果我想定义一个宏,在粘贴之前只扩展其部分参数怎么办? 例如,我想要一个宏,它只允许三个参数的中间扩展,然后将其与一个完全未展开的前缀和一个确切的未展开后缀粘贴在一起,即使前缀或后缀是类似对象的宏的标识符。 也就是说,如果我们再次拥有

#define MAGIC(x,y,z) /* What here? */
#define A X
#define B Y
#define C Z

那么MAGIC(A,B,C)AYC.

一个简单的尝试,比如

#define EXPAND(x) x
#define MAGIC(x,y,z) x ## EXPAND(y) ## z

导致错误"粘贴")"和"C"没有给出有效的预处理令牌"。 这是有道理的(我认为它也会产生不需要的令牌AEXPAND)。

有没有办法只使用标准的、可移植的预处理器规则来获得这种结果?(没有额外的代码生成或修改工具。

如果没有,也许一种适用于最常见实现的方法?在这里,Boost.PP将是公平的游戏,即使它涉及一些特定于编译器的技巧或解决方法。

如果它有任何区别,我最感兴趣的是 C++11 和 C++17 中定义的预处理器步骤。

这是一个解决方案:

#define A X
#define B Y
#define C Z
#define PASTE3(q,r,s) q##r##s
#define MAGIC(x,y,z,...) PASTE3(x##__VA_ARGS__,y,__VA_ARGS__##z)
MACRO(A,B,C,)

请注意,调用"需要"另一个参数(原因见下文);但是:

  • 此处MACRO(A,B,C)符合 C++20 标准
  • MACRO(A,B,C)将在许多 C++11/C++17 预处理器(例如 GNU/clang )中"工作",但这是一个扩展,而不是符合 C++11/C++17 的行为
我知道在扩展类似函数的预处理器宏时,顶级替换列表中的 # 和 ## 标记基本上"在"参数的任何宏扩展之前"起作用。

更准确地说,宏观扩展有四个步骤:

  • 参数标识
  • 参数替换
  • 字符串化和粘贴(按未指定的顺序)
  • 重新扫描和进一步更换

参数标识将宏定义中的参数与调用中的参数相关联。 在这种情况下,xA相关联,yB相关联,zC相关联,...与"地标"(与参数没有标记的参数相关联的抽象空值)相关联。 对于高达 C++20 的C++预处理器,使用...至少需要一个参数;由于 C++20 添加了__VA_OPT__功能,因此在调用中使用...是可选的。

参数替换是扩展参数的步骤。 具体来说,这里发生的事情是,对于宏替换列表中的每个参数(此处,PASTE3(x##__VA_ARGS__,y,__VA_ARGS__##z)),其中所述参数不参与粘贴或字符串化,则关联的参数将完全展开,就好像它出现在调用之外一样;然后,替换列表中不参与字符串化和粘贴的所有对该参数的提及都将替换为展开的结果。 例如,在MAGIC(A,B,C,)调用的这一步中,y是唯一提到的限定参数,因此B扩展生成Y;在这一点上,我们得到PASTE3(x##__VA_ARGS__,Y,__VA_ARGS__##z).

下一步应用粘贴和字符串化运算符,顺序不分特定。 这里特别需要地标,因为您想扩展中间而不是结尾,并且您不想要额外的东西;即,要使A扩展到X,并保持A(而不是更改为"A"),您需要特别避免参数替换。 A.S.只通过两种方式避免;粘贴或字符串化,所以如果字符串化不起作用,我们必须粘贴。 由于您希望该令牌与现有令牌保持一致,因此您需要粘贴到一个地标(这意味着您需要粘贴到一个位置标记,这就是为什么有另一个参数的原因)。

一旦此宏将粘贴应用于"地标",您就会得到PASTE3(A,Y,C);然后是重新扫描和进一步的替换步骤,在此期间PASTE3被标识为宏调用。 快进,由于PASTE3粘贴了它的参数,a.s. 不适用于其中任何一个,我们按"某种顺序"进行粘贴,最终得到AYC

最后一点,在此解决方案中,我使用不同的参数来生成地标标记,正是因为它允许调用至少 C++20 中的表单MACRO(A,B,C)。 我将其左粘贴到z,因为这使得添加至少可能对其他东西有用(MAGIC(A,B,C,_)会使用_作为"分隔符"来产生A_Y_C)。