了解 DEFER 和 OBSTRUCT 宏

Understanding DEFER and OBSTRUCT macros

我创建了一个 small macro metaprogramming library,它实现了 REPEAT(times, x)IF(value, true, false)、元组等基本有用的结构。

我的大部分实现都是通过 重载 宏基于可变参数计数或通过计数器来工作的:

// Example:
#define REPEAT_0(x) 
#define REPEAT_1(x) x REPEAT_0(x) 
#define REPEAT_2(x) x REPEAT_1(x)
#define REPEAT_3(x) x REPEAT_2(x)
// ...
// (these defines are generated using an external script)
// ...

#define REPEAT(count, x) CAT(REPEAT_, count)(x)

这很好用,但我最近遇到了 an extremely interesting implementation of macro recursion by Paul Fultz

直到 deferred expression 部分,我都可以轻松理解他的文章。

但是,我在理解 DEFEROBSTRUCT 的正确用法时遇到了很多麻烦。

Paul 实现了一个非常优雅的 REPEAT 版本,不需要脚本生成的定义,如下所示:

#define EAT(...)
#define EXPAND(...) __VA_ARGS__
#define WHEN(c) IF(c)(EXPAND, EAT)

#define REPEAT(count, macro, ...) \
    WHEN(count) \
    ( \
        OBSTRUCT(REPEAT_INDIRECT) () \
        ( \
            DEC(count), macro, __VA_ARGS__ \
        ) \
        OBSTRUCT(macro) \
        ( \
            DEC(count), __VA_ARGS__ \
        ) \
    )
#define REPEAT_INDIRECT() REPEAT

//An example of using this macro
#define M(i, _) i
EVAL(REPEAT(8, M, ~)) // 0 1 2 3 4 5 6 7

DEFEROBSTRUCT 和其他实用程序是这样实现的:

#define EMPTY()
#define DEFER(id) id EMPTY()
#define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
#define EXPAND(...) __VA_ARGS__

#define A() 123
A() // Expands to 123
DEFER(A)() // Expands to A () because it requires one more scan to fully expand
EXPAND(DEFER(A)()) // Expands to 123, because the EXPAND macro forces another scan

DEFER 这样的宏的使用,以及一般复杂的 C 宏,取决于对 C 预处理器实际如何扩展宏表达式的理解。它不只是试图像传统编程语言那样减少所有表达式树,而是在 linear 标记流上工作,并且在 "cursor" 处有一个隐式的 "cursor"它当前正在检查流以寻找可能的替换点。在任何给定的 "stack frame" 扩展过程中,光标永远不会向后移动,并且一旦在流中传递了一个标记,就不会再次检查它。

遍历 DEFER 操作的第一个示例:

 DEFER(A)()  // cursor starts at the head of the sequence
^            // identifies call to DEFER - push current position

 DEFER( A )()  // attempt to expand the argument (nothing to do)
       ^
 // replace occurrences of id in DEFER with A,
 // then replace the call to it with the substituted body

 A EMPTY() ()  // pop cursor position (start of pasted subsequence)
^              // doesn't find an expansion for A, move on
 A EMPTY() ()  // move to next token
  ^            // EMPTY() is a valid expansion
 A  ()         // replace EMPTY() with its body in the same way
   ^           // continuing...
 A ()          // `(` is not a macro, move on
  ^
 A ( )         // `)` is not a macro, move on
    ^
 A ()          // end of sequence, no more expansions
     ^

DEFER 正文的 "rescan" 期间,光标移过 A,在参数被替换之后,这是第二次也是最后一次尝试扩展该组令牌。一旦光标移过 A,在该扩展序列期间它不会 return 到它,并且由于 "rescan" 位于顶层,因此没有后续扩展序列。

现在考虑相同的表达式,但包含在对 EXPAND:

的调用中
 EXPAND(DEFER(A)())    // cursor starts at the head etc.
^                      // identifies call to EXPAND

 EXPAND( DEFER(A)() )  // attempt to expand the argument
        ^              // this does the same as the first
                       // example, in a NESTED CONTEXT

 // replace occurrences of __VA_ARGS__ in EXPAND with A ()
 // then replace the call with the substituted body

 A ()          // pop cursor position (start of pasted subsequence)
^              // identifies A, and can expand it this time

因为参数列表在堆栈上下文中展开,并且光标位置恢复到 rescan pass 的原始调用之前的位置,将宏调用放在任何宏的参数列表——即使是一个实际上什么都不做的,比如 EXPAND - 通过扩展游标给它一个额外的 "free" 运行,通过额外时间重置游标在流中的位置进行额外的重新扫描,因此为每个新构造的调用表达式(即宏名称和带括号的参数列表的组合)提供了被扩展器识别的额外机会。所以 EVAL 所做的就是给你 363(3^5+3^4+3^3+3^2+3,有人检查我的数学)免费重新扫描通行证。

所以,根据这个问题解决问题:

  • "painting blue" 并不像那样工作 相当(wiki 中的解释措辞有点误导性,尽管它并没有错)。宏的名称,如果在该宏中生成,将被涂成蓝色永久(C11 6.10.3.4“[蓝色]标记不再可用于进一步替换甚至如果它们稍后被(重新)检查")。 DEFER 的要点是确保递归调用 不会在宏的扩展过程中生成 ,而是...延迟...直到外部重新扫描步骤,此时它不会被涂成蓝色,因为我们不再在那个命名的宏中。这就是为什么 REPEAT_INDIRECT 是函数式的;这样就可以防止它扩展为任何提及 REPEAT 名称的内容,只要我们仍在 REPEAT 的主体内。在原始 REPEAT 完成扩展间隔 EMPTY 个令牌后,它至少需要一个额外的免费通行证。
  • 是的,EXPAND 强制进行额外的扩展。任何宏调用都会向在其参数列表中传递的任何表达式授予一次额外的扩展传递。
    • DEFER 的想法是你不传递整个表达式,只传递 "function" 部分;它在函数和它的参数列表之间插入一个间隔符,需要一个扩展通道才能删除。
    • 因此 EXPANDDEFER 之间的区别在于 DEFER 在您传递的函数之前强加了 需要 额外的传递它得到扩展;并且 EXPAND 提供 额外的通行证。所以它们是彼此的逆(并且一起应用,如在第一个示例中,等同于两者都不使用的调用)。
  • 是的,OBSTRUCT 在你传递给它的函数被扩展之前需要两次扩展。它通过 DEFEREMPTY() 间隔扩展一个 (EMPTY EMPTY() ()),在摆脱嵌套的 EMPTY.[=93 时燃烧第一个光标重置来做到这一点=] 在 REPEAT_INDIRECT 周围需要
  • OBSTRUCT 因为 WHEN 扩展(在 true 上)到对 EXPAND 的调用,这将在我们“仍然在对 REPEAT 的调用中。如果只有一层(a DEFER),嵌套的 REPEAT 会在我们仍在 REPEAT 的上下文中时生成,导致它被涂成蓝色并终止递归在那里。使用带有 OBSTRUCT 的两层意味着在我们到达 outside REPEAT 的任何宏调用的重新扫描通道之前,不会生成嵌套的 REPEAT , 此时我们可以安全地再次生成名称而无需将其涂成蓝色,因为它已从未扩展堆栈中弹出。

因此,这种递归方法的工作原理是使用大量重新扫描过程 (EVAL) 的外部源,并通过至少一次重新扫描过程推迟宏名称在自身内部的扩展,因此它发生在调用堆栈的更下方。 REPEAT 的主体的第一次扩展发生在 EVAL[363] 的重新扫描期间,递归调用被推迟到 EVAL[362] 的重新扫描,嵌套扩展被推迟......等等。这不是真正的递归,因为它不能形成无限循环,而是依赖于 外部 堆栈帧源来燃烧。