【问题标题】:Can a preprocessor macro expand just some pasted parameters?预处理器宏可以扩展一些粘贴的参数吗?
【发布时间】:2019-07-21 20:19:54
【问题描述】:

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

#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-token ABCEXPAND_AND_CONCAT(A,B,C)是pp-token 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 中定义的预处理器步骤。

【问题讨论】:

  • "然后 MAGIC(A,B,C)AYC。" 好吧,让我们同意要求 magic 至少是 题外话.
  • @πάνταῥεῖ 不,我不同意,这种使用“魔法”(这个词)来表示解决方案的方式是一个好方法。这样我就经常在工作中使用魔法。

标签: c++ c-preprocessor


【解决方案1】:

这里有一个解决方案:

#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 相关联,... 与“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)

下一步不按特定顺序应用粘贴和字符串化运算符。这里特别需要 Placemarker,因为您想扩展中间而不是结束,并且您不想要额外的东西;即,要让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)。

【讨论】:

  • 谢谢。在我发布问题后,我开始思考使用空参数粘贴占位符标记的思路,但当然我们必须从某个地方获取该空参数。你的最后一句话让我意识到我做了一个 X-Y:在问题的动机中,我实际上确实希望在所有情况下都使用下划线分隔符。所以这是可行的,只需将用法从 BUILD_NAME(PREFIX_, base, _SUFFIX) 更改为 BUILD_NAME(PREFIX, base, SUFFIX)
  • 如果您真的想在所有情况下进行分隔,您可以只使用_ 而不是地标 (#define MAGIC(x,y,z) PASTE3(x##_,y,_##z))。
猜你喜欢
  • 1970-01-01
  • 2021-02-04
  • 1970-01-01
  • 2018-12-27
  • 1970-01-01
  • 2018-01-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多