【问题标题】:Recursive macros via __VA_OPT__通过 __VA_OPT__ 递归宏
【发布时间】:2018-07-04 05:14:05
【问题描述】:

__VA_OPT__写递归宏合法吗?

GCC 和 Clang 似乎不会递归替换,但我不确定它是否是故意的(因为 __VA_OPT__ 支持是最近才出现的)。

C++ 规范(§19.3.1/3:__VA_OPT__):

否则,替换包括扩展的结果 content 作为当前类函数宏的替换列表 在重新扫描和进一步替换之前

上面突出显示的部分是否意味着不可能递归?


例如,添加可变参数宏参数列表:

#define RECURSE(mFIRST, ...) + mFIRST __VA_OPT__(RECURSE(__VA_ARGS__))

int main(int argc, char const* const argv[])
    {
        return 1 RECURSE(2, 3, 4);
        // Expected result: "return 1 + 2 + 3 + 4;"
    }

GCC 和 Clang 都在它们的后处理中生成 RECURSE

// NOTE: Below is the output from g++ -E
int main(int argc, char const* const argv[])
 {
  return 1 + 2 RECURSE(3, 4);
 }

注意:如果可能的话,可以相当容易地编写更复杂的可变参数宏,例如连接,因为您可以从 __VA_OPT__ 创建自定义 __VA_NO_OPT__,它允许您为 1 和 2+ 提供完全独立的代码参数。

【问题讨论】:

  • 仅仅是因为它们总是会无限递归吗?因为__VA_OPT__ 不再适用。
  • 我猜这是因为:19.3.4/2:重新扫描并进一步更换。 “如果在替换列表的扫描过程中发现被替换的宏的名称(不包括源文件的其余预处理标记),则不会被替换”。
  • 不太可能被修改。 C++ 非常努力地摆脱每次修订都使用宏的需要。由于各种原因,它不能,但不太可能添加递归。 __VA_OPT__ 是一个很小的便利功能。递归宏是一项巨大的变化。
  • @NicolBolas:AFAICT __VA_OPT__ 是 c++2a 功能,而不是 c++17 功能,因此标签回滚。如果我被误导了,请道歉。

标签: c++ preprocessor c++20


【解决方案1】:

答案是肯定的!在您已经可以have recursive macros in C 的范围内,您可以在 C++20 中做到这一点,__VA_OPT__ 使某些事情变得更好,更好。

这是您可以做的an example (with explanation):定义一个FOR_EACH 宏,该宏将宏应用于一堆参数,并且比您在__VA_OPT__ 之前必须做的可怕事情要好得多:

#define PARENS () // Note space before (), so object-like macro

#define EXPAND(arg) EXPAND1(EXPAND1(EXPAND1(EXPAND1(arg))))
#define EXPAND1(arg) EXPAND2(EXPAND2(EXPAND2(EXPAND2(arg))))
#define EXPAND2(arg) EXPAND3(EXPAND3(EXPAND3(EXPAND3(arg))))
#define EXPAND3(arg) EXPAND4(EXPAND4(EXPAND4(EXPAND4(arg))))
#define EXPAND4(arg) arg

#define FOR_EACH(macro, ...)                                    \
  __VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, __VA_ARGS__)))
#define FOR_EACH_HELPER(macro, a1, ...)                         \
  macro(a1)                                                     \
  __VA_OPT__(FOR_EACH_AGAIN PARENS (macro, __VA_ARGS__))
#define FOR_EACH_AGAIN() FOR_EACH_HELPER

FOR_EACH(F, a, b, c, 1, 2, 3)   // => F(a) F(b) F(c) F(1) F(2) F(3)

【讨论】:

  • 我实际上使用了这样的东西,F 通常是用户定义的字符串文字,所以 MACRO("dir1", "dir2", "file") 变成 "dir1"_X, "dir2" _X,“文件”_X。在创建将每个目录名称保存在路径中的 constexpr 数组时很有用(更清晰),尤其是当 UDL 名称实际上比 _X 长时。虽然目前最多 64 个项目,所以仍然感觉有点 hacky。
  • @xaxazak 上面的代码最多可以接受 342 个参数,这比 64 个要好,对于任何合理的事情来说可能已经足够了。如果没有,再多写一行代码EXPAND5,你肯定有足够的。
  • 你的指数技术比我的好(我会适应它,谢谢),但不幸的是,任何任意最大值仍然会让人觉得很笨拙。还有编译时间的权衡。如果您指定了 100 条路径,每个路径都需要 1000 条宏替换,并且编译器正在跟踪这些以进行调试,那么它可能会变得很明显。
  • 另外,这为__VA_SEP__(<separator>) 之类的东西提供了一个很好的例子,它的行为类似于__VA_ARGS__,但使用<separator> 而不是逗号。这可能会消除递归的大部分需求。
  • @xaxazak __VA_SEP__ 不如 FOR_EACH 通用。如果他们要促进这种宏,最好的办法就是将FOR_EACH(或__FOR_EACH__或其他东西)直接构建到预处理器中。
猜你喜欢
  • 2022-01-11
  • 1970-01-01
  • 2019-03-31
  • 2020-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-08
  • 1970-01-01
相关资源
最近更新 更多