gpt4 book ai didi

c++ - 了解DEFER和OBSTRUCT宏

转载 作者:IT老高 更新时间:2023-10-28 22:26:58 27 4
gpt4 key购买 nike

我创建了一个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

  • 当预处理器扩展宏时,结果将“绘制”直到下一次扫描-除非进行其他扫描,否则它不会递归扩展。 这是正确的吗?
  • EXPAND(...)是否强制进行附加扫描? 如果是,此扫描是否允许宏递归扩展? EXPAND(...)DEFER(id)有什么区别?
  • DEFER是否强制执行两次附加扫描?
  • OBSTRUCT(...)宏呢?是否会强制进行两次其他扫描?
  • 现在-为什么OBSTRUCT的递归实现中需要REPEAT?为什么DEFEREXPAND在这里不起作用?
  • 最佳答案

    通常使用诸如DEFER之类的宏以及复杂的C宏,取决于对C预处理器实际上是如何扩展宏表达式的理解。它不仅试图像传统编程语言一样减少所有的表达式树,而且还可以在线性 token 流上工作,并且在当前正在检查流中是否有可能替换的地方具有隐式“cursor”。在扩展过程的任何给定“堆栈帧”内,光标永远不会向后移动,并且一旦 token 已在流中传递,就不会再次检查它。

    逐步了解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
    ^

    在替换参数之后,在 A主体的“重新扫描”期间,光标移过 DEFER,这是扩展该组 token 的第二次也是最后一次尝试。一旦光标移过 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

    由于参数列表是在堆叠的上下文中扩展的,并且光标位置恢复到了重新扫描遍历的原始调用之前的位置,因此在任何宏的参数列表中都放置了宏调用-即使是实际上不执行任何操作的变量,例如 EXPAND-通过扩展光标在流中的位置,为扩展游标提供了一个“免费”的额外游程,以进行额外的重新扫描遍历,从而获得了额外的时间,因此为每个新构造的调用表达式(即,将宏名和带括号的参数列表)被扩展器识别的额外机会。因此, EVAL所做的只是为您提供363次免费重新扫描通行证(3 ^ 5 + 3 ^ 4 + 3 ^ 3 + 3 ^ 2 + 3,有人检查了我的数学运算)。

    因此,鉴于此解决问题:
  • “涂成蓝色”的工作方式并非如此(Wiki的解释措词有点误导,尽管这没有错)。如果宏名称在该宏中生成,则将其永久性地涂成蓝色 (C11 6.10.3.4“[blue]标记即使以后(重新)检查也不再可用于进一步替换”)。 DEFER的要点是确保不会在宏的扩展过程中生成递归调用,而是推迟执行...直到外部重新扫描步骤为止,此时该步骤不会被涂成蓝色,因为我们不再位于该命名宏内。这就是REPEAT_INDIRECT类似于函数的原因;只要我们仍然在REPEAT的主体内,就可以防止它扩展到提及REPEAT名称的任何内容。原始REPEAT完成后,至少需要再进行一次免费通行证才能扩展EMPTY token 间隔。
  • 是的,EXPAND强制执行附加扩展。任何宏调用都会向其参数列表中传递的任何表达式授予一次额外的扩展传递。
  • DEFER的思想是,您不将其传递为整个表达式,而只是传递“功能”部分。它将在函数与其参数列表之间插入一个间隔,该间隔需要花费一次扩展才能删除。
  • 因此,EXPANDDEFER之间的区别在于DEFER要求在传递的函数扩展之前需要额外的传递;而EXPAND提供了额外的通行证。因此它们彼此相反(如第一个示例所示,它们一起应用,等效于不使用二者的调用)。
  • 是的,OBSTRUCT在通过传递的函数之前需要花费两次扩展传递。它通过DEFEREMPTY()分隔符扩展一个(EMPTY EMPTY() ()),并在摆脱嵌套的EMPTY时刻录第一个光标重置来实现此目的。
  • OBSTRUCT周围需要
  • REPEAT_INDIRECT,因为WHEN扩展(按真)可以调用EXPAND,这将在我们仍在REPEAT调用中的同时消除一层间接。如果只有一层(DEFER),则当我们仍在REPEAT的上下文中时,将生成嵌套的REPEAT,从而将其涂成蓝色,并终止此处的递归。在OBSTRUCT中使用两层意味着在到达REPEAT之外的任何宏调用的重新扫描过程之前,不会生成嵌套的REPEAT,这时我们可以安全地再次生成名称,而无需将其涂成蓝色,因为它是从无扩展堆栈。

  • 因此,这种递归方法通过使用大量重新扫描遍历的外部源( EVAL)起作用,并通过至少一个重新扫描遍历延迟其内部宏名称的扩展,以便在调用堆栈的更远处进行。 REPEAT主体的第一个扩展发生在 EVAL[363]的重新扫描期间,递归调用被推迟到 EVAL[362]的重新扫描,嵌套扩展被推迟...等等。这不是真正的递归,因为它不能形成无限循环,而是依靠外部堆栈帧来进行烧录。

    关于c++ - 了解DEFER和OBSTRUCT宏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29962560/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com