【问题标题】:C - Variadic macro which expands into set of macro calls on each argumentC - 可变参数宏,扩展为每个参数的宏调用集
【发布时间】:2015-07-27 13:49:02
【问题描述】:

我想要一个宏调用,它接受多个函数指针,每个函数指针由第二个宏调用,它是一个函数声明。

我想要表单上有两个宏

#define FUNCTION_DEF(func) extern int func(void);
#define FUNCTION_DEFS(...) (???)

这样称呼

FUNCTION_DEFS(
    myFunc1,
    myFunc2,

    otherFunc1,
    otherFunc2,

    defaultFunc
)

展开成

FUNCTION_DEF(myFunc1)
FUNCTION_DEF(myFunc2)

FUNCTION_DEF(otherFunc1)
FUNCTION_DEF(otherFunc2)

FUNCTION_DEF(defaultFunc)

换句话说,对FUNCTION_DEFS 的单个调用扩展为所有可变参数的函数声明。

目前我只是跳过第一步并在每个函数指针上调用FUNCTION_DEF,但是解决这个问题会很棒。

这可能吗?

编辑:

感谢 @Vality 向我介绍 X-Macro。我发现这个帖子“Real-world use of X-Macros”正是我需要的。

【问题讨论】:

  • 是的。与例如Boost.PP.
  • @Columbo:C 语言的提升?
  • @Olaf 来自文档:“Boost Preprocessing 库是一个宏库,支持预处理器元编程。该库支持 C++ 和 C 编译。”跨度>
  • 好吧,对不起,不知道boost是两个部分:预处理和编译库。 (我不知道如何解释您第一条评论中的“PP”)
  • @Olaf 为了完整性,Boost 不是一个库,而是一个独立库的集合。有些是仅头文件,有些需要目标文件,有些也是 C。

标签: c macros function-pointers variadic-macros


【解决方案1】:

我不相信使用标准 C 预处理器可以精确地实现您想要的。但是,使用 X 宏也可以实现类似的解决方案。

要使用它们执行与您的代码等效的操作,您首先将函数列表定义为 X 宏:

#define FUNCTION_LIST_A \
    X(myFunc1) \
    X(myFunc2) \
    X(otherFunc1) \
    X(otherFunc2) \
    X(defaultFunc)

然后要使用特定宏实例化这些函数,您需要定义宏以在每个函数上执行:

#define X(name) FUNCTION_DEF(name)
FUNCTION_LIST_A
#undef X

然后将扩展为:

FUNCTION_DEF(myFunc1)
FUNCTION_DEF(myFunc2)
FUNCTION_DEF(otherFunc1)
FUNCTION_DEF(otherFunc2)
FUNCTION_DEF(defaultFunc)

希望这是有用的并且接近你想要的。诚然,语法有很大不同,但如果您希望完成的是将所选函数或宏应用于整个数据列表(在本例中为函数指针),这是我所知道的使用 c 预处理器的最惯用的方法.

【讨论】:

  • 很好的答案,但是在标准 PP 中可能的。不过,您可能想让 Boost.PP 的家伙弄脏管道:p
  • @Quentin 您对 boost 能够做到这一点是绝对正确的,而且可能更优雅一些,但是我正在尝试解决操作要求,不要使用他们在中提到的外部预处理器一条评论“忘了提到这是在我不能使用第三方库/头文件(如 Boost)的嵌入式解决方案上”。不过,是的,一个不同的前任是最好的解决方案。
  • Boost 不是外部预处理器。 BoostPP 是纯 .h 解决方案;其中的任何内容都可以在纯用户代码中再次完成。
  • @Leushenko 如果您知道如何,请随时为 BoostPP 添加另一个答案,我没有足够的知识来完成此操作,但如果您可以使用它来制定更好的解决方案,那就是值得一说。
  • @Vality,这正是我想要的!这也给了我很好的可重用性,因为我还需要从同一个列表中创建一个函数指针表,我可以通过这个设置来完成。谢谢!
【解决方案2】:

有很多方法可以做到这一点。最简单的是为每个可能的长度预定义一个循环版本(基于较小的版本),然后根据要迭代的参数数量选择正确的循环:

#define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N

#define M_CONC(A, B) M_CONC_(A, B)
#define M_CONC_(A, B) A##B
#define M_ID(...) __VA_ARGS__

#define M_FOR_EACH(ACTN, ...) M_CONC(M_FOR_EACH_, M_NARGS(__VA_ARGS__)) (ACTN, __VA_ARGS__)

#define M_FOR_EACH_0(ACTN, E) E
#define M_FOR_EACH_1(ACTN, E) ACTN(E)
#define M_FOR_EACH_2(ACTN, E, ...) ACTN(E) M_FOR_EACH_1(ACTN, __VA_ARGS__)
#define M_FOR_EACH_3(ACTN, E, ...) ACTN(E) M_FOR_EACH_2(ACTN, __VA_ARGS__)
#define M_FOR_EACH_4(ACTN, E, ...) ACTN(E) M_FOR_EACH_3(ACTN, __VA_ARGS__)
#define M_FOR_EACH_5(ACTN, E, ...) ACTN(E) M_FOR_EACH_4(ACTN, __VA_ARGS__)
//...etc

#define FUNCTION_DEF(func) extern int func(void);
#define FUNCTION_DEFS(...) M_FOR_EACH(FUNCTION_DEF, __VA_ARGS__)

您也可以使用传统的递归技术来设计一个更通用的解决方案,但这通常需要一个支持库(例如[1][2])来提供这种机制,因为直接定义的宏不支持递归。

这是因为预处理器中的所有循环都必须有一个预先确定的上限,这是没有直接宏递归的结果:您可以像上面的代码一样在简单的情况下将限制硬编码到循环实现中,或者您可以让循环构造运算符底层的递归驱动程序包含限制,并为请求它们的构造提供 N 次迭代(后者的优点是它可以让您集中化,然后忘记,只要它足够高的限制,例如 Order-PP 在数十亿次迭代中有一个限制,你在实践中永远不会达到)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-29
    • 2019-09-23
    • 2020-03-31
    • 2012-01-25
    • 1970-01-01
    相关资源
    最近更新 更多