了解DEFER和OBSTRUCT宏

Vit*_*meo 15 c++ macros c-preprocessor c++11

我创建了一个小宏元编程库,实现基本的构建体,例如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)
Run Code Online (Sandbox Code Playgroud)

这很好,但我最近遇到了Paul Fultz的一个非常有趣的宏递归实现.

直到延迟表达部分,我没有理解他的文章.

但是,我在理解使用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
Run Code Online (Sandbox Code Playgroud)

DEFER,OBSTRUCT以及其他实用程序实现如下:

#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
Run Code Online (Sandbox Code Playgroud)
  • 当预处理器扩展宏时,结果将被"绘制"直到下一次扫描 - 除非发生其他扫描,否则它不会递归扩展.它是否正确?

  • 请问EXPAND(...)力的额外的扫描?如果是这样,此扫描是否允许宏递归扩展?和之间的区别是什么?EXPAND(...)DEFER(id)

    • 是否DEFER强制两个额外的扫描?
  • 那个OBSTRUCT(...)宏怎么样?它是否强制进行两次额外扫描?

  • 现在 - 为什么OBSTRUCT在递归实现中需要REPEAT?为什么不DEFER还是EXPAND在这里工作?

Leu*_*nko 20

DEFER一般来说,宏的使用和复杂的C 宏编程取决于理解C预处理器如何实际扩展宏表达式.它不仅像传统编程语言那样尝试减少所有表达式树,而是在线性令牌流上工作,并且在它当前正在检查流以寻找可能替换的点处具有隐式"光标".在扩展过程的任何给定"堆栈帧"内,游标永远不会向后移动,并且一旦在流中传递了令牌,就不会再次检查它.

通过第一个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
     ^
Run Code Online (Sandbox Code Playgroud)

在参数被替换之后,光标A在"重新扫描" DEFER的体内移动过去,这是扩展该组标记的第二次也是最后一次尝试.一旦光标移过,A它就不会在该扩展序列期间返回到它,并且由于"重新扫描"位于顶层,因此没有以下扩展序列.

现在考虑相同的表达式,但包含在调用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
Run Code Online (Sandbox Code Playgroud)

因为参数列表在堆叠上下文中展开,并且光标位置被恢复到重新扫描传递的原始调用前面的位置,所以在任何宏的参数列表中放置一个宏调用 - 即使是实际上什么都不做的,例如EXPAND- 给出它是扩展光标的"自由"额外运行,通过重置光标在流中的位置额外的时间进行额外的重新扫描传递,并因此给出每个新构造的调用表达式(即将宏名称和括号内的一起推送到一起)参数列表)被扩展器识别的额外机会.所以这一切EVAL都是给你363(3 ^ 5 + 3 ^ 4 + 3 ^ 3 + 3 ^ 2 + 3,有人检查我的数学)免费重新扫描通行证.

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

  • "画蓝"不工作类似(在wiki上的解释是有点误导性的措辞,虽然这是没有错的).宏的名称,如果在该宏内生成,将永久涂成蓝色(C11 6.10.3.4"[蓝色]标记不再可用于进一步替换,即使它们稍后(重新)检查过 ").重点DEFER是确保递归调用不会在宏的扩展过程中生成,而是......延迟...直到外部重新扫描步骤,此时它不会被涂成蓝色,因为我们'不再是那个命名的宏.这就是REPEAT_INDIRECT函数式的原因; REPEAT只要我们还在体内,就可以防止它扩展成任何提到名称的东西REPEAT.在起始REPEAT完成后,它需要至少一次进一步的自由通过以扩展间距EMPTY标记.
  • 是的,EXPAND强制额外的扩展通行证.任何宏调用都会向其参数列表中传递的任何表达式授予一个额外的扩展传递.
    • 你的想法DEFER是你没有把整个表达,只是"功能"部分传递给它; 它在函数和它的参数列表之间插入一个spacer,需要一个扩展传递来删除.
    • 因此,之间的区别EXPAND,并DEFERDEFER强加需要一个额外的传球,你通过它得到扩展功能之前; 并EXPAND 提供额外的通行证.所以它们是彼此相反的(并且应用在一起,如在第一个示例中,相当于使用两者的调用).
  • 是的,OBSTRUCT在您传递的函数扩展之前,需要花费两次扩展.它通过一个()DEFER的扩展,EMPTY()通过EMPTY EMPTY() ()烧掉第一个光标重置摆脱嵌套来实现这一点EMPTY.
  • OBSTRUCT需要周围REPEAT_INDIRECT因为WHEN扩展(在真实情况下)到一个调用EXPAND,当我们仍然在调用时,它会烧掉一层间接REPEAT.如果只有一个图层(a DEFER),则REPEAT在我们仍在REPEAT上下文中时会生成嵌套,导致它被涂成蓝色并在那里杀死递归.使用两个层OBSTRUCT意味着嵌套REPEAT将不会生成,直到我们到达外部的 任何宏调用的重新扫描传递REPEAT,此时我们可以安全地再次生成名称而不会被涂成蓝色,因为它已经从没有 -扩展堆栈.

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