【问题标题】:Generating initializer-lists using the C preprocessor使用 C 预处理器生成初始化列表
【发布时间】:2020-10-09 10:17:21
【问题描述】:

我想知道是否可以构造一个可以使用这种语法的 C99 宏

MAGIC(a,b,(c,d),(e,(f,g))) // Expands to {{a}, {b}, {{c, d}}, {{e, {f,g}}}}

或者这个更实用的语法

MAGIC( (a)(b)(c,d)(e,(f,g)) ) // Again should expands to {{a}, {b}, {{c, d}}, {{e, {f,g}}}}

如果需要,我可以假设最大嵌套深度 w.r.t。括号,比如说四个级别。

我已经尝试过here 提出的解决方案。但到目前为止,我还没有走远。我试图避免必须像宏一样创建这些生成来模拟递归/迭代。但这可能无法解决。

【问题讨论】:

    标签: c-preprocessor c99


    【解决方案1】:

    对于 C 预处理器来说,这很困难的部分原因是它需要递归到括号中的分组中。这实际上意味着我们需要在外部级别应用延迟/评估,或者我们需要在内部级别应用不同的宏。递归级别的规范还存在不对称性,虽然这本身并不难,但必须加以考虑。

    如果需要,我可以假设最大嵌套深度 w.r.t。括号,比如说四个级别。

    我将致力于此...并支持四个级别。

    定义

    定义(使用与 boost 预处理器兼容的术语):元组是一种预处理数据结构,由单个括号分组组成,其元素以逗号分隔。所以(a,(b,c),d) 是一个包含三个元素的元组...a(b,c)dsequence 是一种预处理数据结构,由一系列连续元素组成,每个元素都由括号括起来。所以上面列出的相同元素用序列(a)((b,c))(d)表示。

    设计

    要使用可变参数处理元组,我们需要一个计数器、一个粘合宏以及一个用于我们想要支持的每个元组大小的宏。如果说我们支持从 1 到 10 个元素的元组,并且想要将其更改为支持从 1 到 20 的元组,我们可能必须更改计数器并添加 10 个更多的宏。但是如果我们仅使用元组支持 4 级递归,我们需要 4 倍于这个数量的宏;这使得扩展更加繁重。

    您展示了两种形式的宏;两者都使用元组,后者只使用顶层的序列。为了保持一致性,我将选择第一种形式。但是为了可扩展性,我将添加元组处理只是为了将元组转换为序列,显示 9 元素元组支持......然后,要支持 n 元素元组,您只需要更改计数并添加 n-9 个宏。

    方法

    我们从一个基本的粘合宏、一个计数和一个单一级别的序列转换器开始 (PARN):

    #define GLUE(A,B) GLUE_I(A,B)
    #define GLUE_I(A,B) A##B
    #define PARN(...) GLUE(PARN_, COUNT (__VA_ARGS__)) (__VA_ARGS__)
    #define PARN_9(A,B,C,D,E,F,G,H,I) (A)(B)(C)(D)(E)(F)(G)(H)(I)
    ...
    #define PARN_2(A,B) (A)(B)
    #define PARN_1(A) (A)
    

    我们想要迭代另一个级别或只处理一个元素,具体取决于它是否是括号。为此,我将应用基于“间接第二宏”的模式匹配器。这个想法是你在参数 1 中放置一个模式并设置宏,这样,如果一个模式匹配,它将标记转移到参数 2;否则,您的模式只会在参数 1 中生成一堆被忽略的标记。这是带有括号检测器的模式匹配器构造(请注意,如果您传递 1 个参数,我的第二个扩展为空):

    #define SECOND(...) SECOND_I(__VA_ARGS__,,)
    #define SECOND_I(A,B,...) B
    #define CALLDETECT(...) ,
    

    鉴于您提供的链接,您应该已经知道如何应用序列。我有一个用于序列应用的特定宏:

    #define PASTE_E(...) PASTE_E_I(__VA_ARGS__)
    #define PASTE_E_I(...) __VA_ARGS__ ## E
    

    ...典型的“简单”序列处理在两个宏之间切换,称它们为AB,直到它们到达终端...所以它们转到 A,B,A,B,..., E.但是我们想留下一个逗号分隔的结果,没有额外的逗号。因此,我们将在每个宏中添加逗号,但我们将从另一个不添加的 A 开始;即,我们要去 A,B,C,B,C,...,E。我们需要 这些 组中的四个来 (a) 避免蓝色油漆,以及 (b) 在顶层进行不同的处理。这是初始设置:

    #define BRACP0(X) {SECOND(CALLDETECT X MAGIC1)X}
    #define BRAC0_A(X) BRACP0(X)BRAC0_B
    #define BRAC0_B(X) ,BRACP0(X)BRAC0_C
    #define BRAC0_C(X) ,BRACP0(X)BRAC0_B
    #define BRAC0_BE
    #define BRAC0_CE
    

    这会将外部级别MAGIC链接到内部级别MAGIC1;这个集合专门为每个规范添加了一个额外的 {} 对 (BRACP0)。除了{} 之外,其余的集合看起来都差不多。但是对于最后一个级别,我们希望调用一个简单的 bracify 而不是“下一个魔术”(这也是可扩展的;您需要每个级别的递归深度支持这些集合之一)。 bracify 和 4 级的“BRACP”看起来像这样:

    #define BRACIFY(...) {__VA_ARGS__}
    #define BRACP4(X) SECOND(CALLDETECT X BRACIFY)X
    

    每个魔术宏级别都有一个“展开”:

    #define MAGIC(...) {PASTE_E(MAGIC_U(BRAC0_A PARN(__VA_ARGS__)))}
    #define MAGIC_U(...) __VA_ARGS__
    #define MAGIC1(...) {PASTE_E(MAGIC1_U(BRAC1_A PARN(__VA_ARGS__)))}
    #define MAGIC1_U(...) __VA_ARGS__
    ...
    #define MAGIC4(...) {PASTE_E(MAGIC4_U(BRAC3_A PARN(__VA_ARGS__)))}
    #define MAGIC4_U(...) __VA_ARGS__
    

    完整的解决方案和演示

    链接到coliru.stacked-crooked 演示。

    最后的比赛

    我将 C99 解释为此处的标准含义,而这个问题只是“是否可能”;因此,我没有费心将其转换为与 MSVS 的预处理器一起使用。它可能不会按原样工作。让我知道这是否让您感到困扰。

    【讨论】:

    • @H Walters 感谢您提供这个精细而详细的解决方案。一旦我澄清了我对 C 预处理器行为的一些困惑,我可能会有更多的话要说。之后我会考虑接受你的回答。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-18
    • 1970-01-01
    相关资源
    最近更新 更多