预处理程序宏可以仅扩展某些粘贴的参数吗?

时间:2019-02-28 01:06:36

标签: 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_NO_EXPAND(A,B,C)是pp令牌ABC,而EXPAND_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”没有给出有效的预处理令牌”。这是有道理的(我假设它还会产生不需要的令牌{{1} }。

有什么方法可以仅使用标准的可移植预处理程序规则来获得这种结果吗? (没有额外的代码生成或修改工具。)

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

如果有什么不同,我对C ++ 11和C ++ 17中定义的预处理器步骤最感兴趣。

1 个答案:

答案 0 :(得分:2)

这是一个解决方案:

#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的行为
我知道,在扩展类似函数的预处理器宏时,顶级替换列表中的#和##标记实际上在参数的任何宏扩展之前“起作用”。

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

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

参数标识将宏定义中的参数与调用中的参数相关联。在这种情况下,xAyBzC以及...和“ placemarker”(与参数没有标记的参数相关联的抽象空值)。对于不超过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"),您需要特别避免参数替换。如。仅通过两种方式避免;粘贴或字符串化,因此如果字符串化不起作用,则必须粘贴。并且由于您希望该令牌与原来的令牌保持相同,因此需要粘贴到一个地标(这意味着您需要粘贴一个,这就是为什么还有另一个参数的原因)。

一旦此宏将粘贴内容应用于“地标”,您将以PASTE3(A,Y,C)结束; 然后,存在重新扫描和进一步的替换步骤,在此期间,PASTE3被标识为宏调用。由于PASTE3粘贴其参数a.s,因此可实现快速转发。不适用于其中任何一个,我们以“某种顺序”进行粘贴,然后以AYC结尾。

最后一点,在此解决方案中,我使用一个可变的参数来生成地标标记,正是因为它允许至少在C ++ 20中调用MACRO(A,B,C)形式。我将其左粘贴到z,因为这使得添加的内容至少可能对其他内容有用(MAGIC(A,B,C,_)_用作“定界符”以产生A_Y_C )。