【问题标题】:C Macros: How to map another macro to variadic arguments?C 宏:如何将另一个宏映射到可变参数?
【发布时间】:2017-08-09 08:53:05
【问题描述】:

我想知道如何将一元函数(或另一个宏)应用于宏的可变参数,例如

int f(int a);

#define apply(args...) <the magic>
apply(a, b, c)

展开

f(a)
f(b)
f(c)

请注意,参数的数量是未知的。

【问题讨论】:

    标签: c macros


    【解决方案1】:

    下面的代码可以使用多达 1024 个参数来满足您的要求,并且没有使用诸如 boost 之类的其他东西。它定义了一个EVAL(...) 和一个MAP(m, first, ...) 宏来进行递归,并为每次迭代使用宏m 和下一个参数first

    使用它,您的apply(...) 看起来像:#define apply(...) EVAL(MAP(apply_, __VA_ARGS__))

    大部分抄自C Pre-Processor Magic。那里也有很好的解释。你也可以在这个git repository下载这些像EVAL(...)这样的辅助宏,实际代码中也有很多解释。它是可变参数,所以它需要你想要的参数数量。

    但我更改了FIRSTSECOND 宏,因为它使用了一个Gnu 扩展,就像我复制它的源代码一样。这是@HWalters在下面的cmets中说的:

    具体来说,6.10.3p4:“否则 [标识符列表以 ... 结尾] 调用中的参数应多于宏定义中的参数(不包括 ...)”。

    主要功能部分:

    int main()
    {
       int a, b, c;
       apply(a, b, c) /* Expands to: f(a); f(b); f(c); */
    
       return 0;
    }
    

    宏定义:

    #define FIRST_(a, ...) a
    #define SECOND_(a, b, ...) b
    
    #define FIRST(...) FIRST_(__VA_ARGS__,)
    #define SECOND(...) SECOND_(__VA_ARGS__,)
    
    #define EMPTY()
    
    #define EVAL(...) EVAL1024(__VA_ARGS__)
    #define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
    #define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
    #define EVAL256(...) EVAL128(EVAL128(__VA_ARGS__))
    #define EVAL128(...) EVAL64(EVAL64(__VA_ARGS__))
    #define EVAL64(...) EVAL32(EVAL32(__VA_ARGS__))
    #define EVAL32(...) EVAL16(EVAL16(__VA_ARGS__))
    #define EVAL16(...) EVAL8(EVAL8(__VA_ARGS__))
    #define EVAL8(...) EVAL4(EVAL4(__VA_ARGS__))
    #define EVAL4(...) EVAL2(EVAL2(__VA_ARGS__))
    #define EVAL2(...) EVAL1(EVAL1(__VA_ARGS__))
    #define EVAL1(...) __VA_ARGS__
    
    #define DEFER1(m) m EMPTY()
    #define DEFER2(m) m EMPTY EMPTY()()
    
    #define IS_PROBE(...) SECOND(__VA_ARGS__, 0)
    #define PROBE() ~, 1
    
    #define CAT(a,b) a ## b
    
    #define NOT(x) IS_PROBE(CAT(_NOT_, x))
    #define _NOT_0 PROBE()
    
    #define BOOL(x) NOT(NOT(x))
    
    #define IF_ELSE(condition) _IF_ELSE(BOOL(condition))
    #define _IF_ELSE(condition) CAT(_IF_, condition)
    
    #define _IF_1(...) __VA_ARGS__ _IF_1_ELSE
    #define _IF_0(...)             _IF_0_ELSE
    
    #define _IF_1_ELSE(...)
    #define _IF_0_ELSE(...) __VA_ARGS__
    
    #define HAS_ARGS(...) BOOL(FIRST(_END_OF_ARGUMENTS_ __VA_ARGS__)())
    #define _END_OF_ARGUMENTS_() 0
    
    #define MAP(m, first, ...)           \
      m(first)                           \
      IF_ELSE(HAS_ARGS(__VA_ARGS__))(    \
        DEFER2(_MAP)()(m, __VA_ARGS__)   \
      )(                                 \
        /* Do nothing, just terminate */ \
      )
    #define _MAP() MAP
    
    #define apply_(x) f(x);
    #define apply(...) EVAL(MAP(apply_, __VA_ARGS__))
    

    要测试宏扩展,使用带有命令行参数-E 的 gcc 很有用:

    $ gcc -E srcFile.c
    

    因为您会收到具体的错误消息并了解发生了什么。

    【讨论】:

    • FIRST 如此处所示依赖于非标准的 gnu 扩展。根据标准,此实现需要两个参数;您使用它需要FIRST 接受一个论点。 #define FIRST(...) FIRST_(__VA_ARGS__,) #define FIRST_(x, ...) x 是一个修复。与SECOND类似。
    • 另外,HAS_ARGS 充其量只是名称不佳;如果它没有传递任何参数,它并不是真正的测试。相反,它正在测试它的第一个参数是否为空。
    • @HWalters:谢谢!正如我所说,它主要是从我在答案中提到的来源复制而来的。我不明白为什么FIRST 不应该适用于所有编译器/预处理器,为什么你的版本可以解决这个问题? “我的版本”只是抛弃了__VA_ARGS__ 为什么这是一个 gnu 扩展?
    • 具体来说,6.10.3p4:“否则 [标识符列表以 ... 结尾] 调用中的参数应该比宏定义中的参数多(不包括 ... )”。 (我知道您要求提供链接,但文本适合评论)。 FIRST(a, ...) 因此至少需要两个参数。接受 1 是一个与其逗号省略功能相关的 gnu 扩展。
    • @HWalters:非常感谢你得到它并改变它! @ZhaoxiongCui:还有一个小错误,它扩展为:f(a); , f(b); , f(c);,之前中间有逗号。现在扩展工作并扩展为:f(a); f(b); f(c);.
    【解决方案2】:

    如果你在 C 中抛出足够多的丑陋宏,一切皆有可能。例如,你可以有一个丑陋的函数式宏:

    #include <stdio.h>
    
    int f (int a)
    {
      printf("%d\n", a);
    }
    
    #define SIZEOF(arr) (sizeof(arr) / sizeof(*arr))
    
    #define apply(...)                    \
    {                                     \
      int arr[] = {__VA_ARGS__};          \
      for(size_t i=0; i<SIZEOF(arr); i++) \
      {                                   \
        f(arr[i]);                        \
      }                                   \
    }
    
    int main (void)
    {
      apply(1, 2, 3);
    }
    

    请注意 1) 将其作为可变参数 函数 会更好,并且 2) 如果您完全摆脱可变参数的废话并简单地创建一个诸如 之类的函数会更好/p>

    int f (size_t n, int array[n])
    

    【讨论】:

    • 在定义 apply 时可以使用do{...}while(0)
    • @fzyzcjy 取决于上下文。如果目标是生成高质量的 C 代码,那么不 - 高质量的 C 代码不会使用没有大括号 {}if() x(); else,并且允许这样的代码是 do-while(0) 的唯一目的。如果目标是创建一些可供具有不同代码质量标准的各种用户使用的可移植库,那么应该使用 do-while(0)。
    • 谢谢!我很少听说过这种解释
    • @fzyzcjy 这就是 MISRA-C 放弃使用 do-while(0) 要求的原因。在旧版本的 MISRA 中,类似函数的宏需要它,但他们在最新版本中删除了该规则,因为 MISRA-C 已经要求我们必须始终在所有控制和循环语句之后使用大括号。
    【解决方案3】:

    IMO 不可能,除非你使用像这样可怕的技巧和黑客

    https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s

    正如我所见,它调用了相同的函数,所以参数的类型是已知的 - 为什么你不使用 stdarg 来代替。

    【讨论】:

      猜你喜欢
      • 2020-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-23
      • 1970-01-01
      • 1970-01-01
      • 2014-12-22
      相关资源
      最近更新 更多