C++ 预处理器宏能否仅扩展一些粘贴的参数?

C++ 预处理器宏能否仅扩展一些粘贴的参数?,c++,c-preprocessor,C++,C Preprocessor,我知道,在扩展像预处理器宏这样的函数时,顶级替换列表中的#和#标记基本上在对参数进行任何宏扩展之前起作用。例如,给定 #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\u NO\u EXPAND(A,B,C)是pp令牌ABC,而EXPAND\u CONCA

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

#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\u NO\u EXPAND(A,B,C)
是pp令牌
ABC
,而
EXPAND\u 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,)
请注意,调用“需要”另一个参数(请参见下文了解原因);但是:

  • 宏(A、B、C)
    这里的宏与C++20兼容
  • 宏(A,B,C)
    将在许多C++11/C++17预处理器(例如gnu/clang)中“工作”,但这是一个扩展,而不是符合C++11/C++17的行为
我知道,在展开预处理器宏之类的函数时,顶级替换列表中的#和##标记基本上在对参数进行任何宏展开之前起作用。 更准确地说,宏扩展有四个步骤:

  • 论元识别
  • 变元替换
  • 串接和粘贴(按未指定的顺序)
  • 重新扫描和进一步更换
参数标识将宏定义中的参数与调用中的参数相关联。在这种情况下,
x
A
相关联,
y
B
z
C
相关联,
..
与“placemarker”相关联(抽象的空值与参数的参数没有任何关联)。对于C++预处理器,直到C++ 20,使用A<代码>……/CUL>至少需要一个参数;因为C++ 20添加了<代码>VAYOPTTY 特性,调用中使用<代码>…<代码>是可选的。 参数替换是展开参数的步骤。具体来说,这里发生的是,对于宏的替换列表中的每个参数(在这里,
PASTE3(x#######(x###)VA(u#u)ARGS(,y,#u#VA#ARGS##z#,如果所述参数不参与粘贴或字符串化,则关联的参数将被完全展开,就像它出现在调用之外一样;然后,替换列表中所有提及的不参与字符串化和粘贴的参数都将被替换为展开的结果。例如,在此步骤中
MAGIC(A,B,C)
invocation,
y
是唯一提到的限定参数,因此
B
被展开产生
y
;此时我们得到
PASTE3(x####VA_uargs uuuuuuuuuuuu,y,u(VA#uu ARGS#uz)

下一步不按特定顺序应用粘贴和字符串化操作符。此处特别需要Placemarker,因为您希望展开中间而不是末端,并且不需要额外的内容;即,使
A
不展开到
X
,并保持
A
(与更改为
“A”
)相反,您需要特别避免参数替换。a.s.只有两种方法可以避免:粘贴或字符串化,因此如果字符串化不起作用,我们必须粘贴。由于您希望该标记与您的标记保持不变,因此需要粘贴到placemarker(这意味着您需要粘贴到一个,这就是为什么有另一个参数)

一旦该宏将粘贴应用于“placemarkers”,您将得到
PASTE3(A、Y、C)
;然后是重新扫描和进一步替换步骤,在此过程中,
PASTE3
被标识为宏调用。快进,由于
PASTE3
粘贴其参数,a.s.不适用于任何参数,我们按“某种顺序”进行粘贴,最后得到
AYC


最后,在这个解决方案中,我使用一个不同的参数来生成placemarker令牌,因为它允许至少在C++20中调用形式
宏(a,B,C)
。我将其粘贴到
z
,因为这使得添加至少可能对其他东西有用(
MAGIC(a,B,C,))
会使用
作为“分隔符”来产生
a\u Y\u C
)。

“那么
MAGIC(a,B,C)
就是
AYC
”好吧,让我们同意请求魔法至少是离题的。@πάνταῥεῖ 不,我不同意,这种使用“魔法”(单词)的方式表示解决方案是一种很好的方法。这样我在工作中经常使用魔术。谢谢。在我发布问题后,我开始考虑使用空参数粘贴占位符标记,但当然我们必须从某个地方获得空参数。你的最后一句话让我意识到我在动机中做了一个X-Y:关于这个问题,实际上我确实希望在所有情况下都使用下划线分隔符。所以这是可行的,只需将用法从
BUILD\u NAME(前缀、基、后缀)
更改为
BUILD\u NAME(PR