可变参数宏包装器,扩展为使用与参数数量相对应的字符格式化字符串

Posted

技术标签:

【中文标题】可变参数宏包装器,扩展为使用与参数数量相对应的字符格式化字符串【英文标题】:Variadic macro wrapper that expands to format string with characters corresponding to number of arguments 【发布时间】:2020-07-12 14:16:01 【问题描述】:

问题

我正在寻找一个可变参数 C 预处理器宏,它将其参数和相应的格式字符串传递给一个函数,根据参数的数量重复一个字符。 例如,我想要一个宏 FOO,它的扩展如下(或等效的 C 代码):

FOO(1)bar("d",1) FOO(1,2)bar("dd",1,2), FOO(1,2,3)bar("ddd",1,2,3) 奖励:FOO()bar("")

虽然我可以将C preprocessor macro for returning a string repeated a certain number of times 和C++ preprocessor __VA_ARGS__ number of arguments(或类似问题)的解决方案结合起来,或者使用variadic macros,但它们有几个缺点,例如:

需要特殊库,例如 Boost(这对我来说是个问题), 依赖于编译器, 仅在运行时工作, 极其复杂。

我希望,当这些问题不被分开考虑时,会出现一些更好的解决方案。

背景

我想在自动生成的代码中回调 Python 的 C 扩展中的 Python 函数。 因此,例如,我需要将foo(1,2,3) 扩展为:

PyObject_CallObject( callback_foo, Py_Build_Value("(Oddd)",Y,1,2,3) )

我知道foo 的所有参数都是双精度数,但我不知道它们的数量。 (上面的例子有些简化。我知道它缺少一些Py_DECREFs。)

【问题讨论】:

我认为没有任何可能避免“极其复杂”,特别是因为您无法依赖第三方包提供的宏堆栈(这只会 move 复杂性,无论如何,不​​会消除它)。 C 的宏处理器本身并非设计为一种编程语言,您提出的行为至少涉及两个难以处理的不同领域。为什么您的代码生成器不能自己处理生成完整的函数调用? 这很简单,只需在参数数量上重载宏...让重载返回 "d" "dd" "ddd" 等。 这能回答你的问题吗? Overloading Macro on Number of Arguments @KamilCuk:还不错,但还是有一些协同效应。我根据他们发布了一个答案。 @JohnBollinger:为什么您的代码生成器不能自己处理生成完整的函数调用? – 理论上可以,但由于原因,这将是一个很大的麻烦远远超出了这个问题的范围。 【参考方案1】:

使用 100% 标准 C,您可以这样做:

#define COUNT_ARGS(...) (sizeof((int[])__VA_ARGS__) / sizeof(int))
#define STRTABLE (const char*[]) "", "d", "dd", "ddd", "ddddd"  // and so on
#define FOO(...) bar(STRTABLE[COUNT_ARGS(__VA_ARGS__)], __VA_ARGS__)

在此示例中,STRTABLE 是一个复合文字查找表,其中包含一堆字符串文字作为初始值设定项列表。通过计算宏参数的数量并专门使用该数组索引,只使用与传递给宏的参数数量相对应的初始值设定项。

完整示例:

#include <stdio.h>

#define COUNT_ARGS(...) (sizeof((int[])__VA_ARGS__) / sizeof(int))
#define STRTABLE (const char*[]) "", "d", "dd", "ddd", "ddddd"  // and so on
#define FOO(...) bar(STRTABLE[COUNT_ARGS(__VA_ARGS__)], __VA_ARGS__)

void bar(const char* fmt, ...)

  puts(fmt);


int main (void)

  FOO(1);
  FOO(1,2);
  FOO(1,2,3);

【讨论】:

你知道这个优化编译器会在编译时评估哪些部分吗? @Wrzlprmft 几乎所有内容,这就是重点:godbolt.org/z/LGhFSd。该代码仅产生使用的 3 个字符串,每个字符串的地址都传递给每个函数调用的函数。【参考方案2】:

到目前为止,我能想到的最好的方法是采用 this answer 并简化它:

# define EXPAND(x) x

# define FORMATSTRING(...) EXPAND(ELEVENTHARG1(__VA_ARGS__ __VA_OPT__(,) RSEQ()))
# define ELEVENTHARG1(...) EXPAND(ELEVENTHARG2(__VA_ARGS__))
# define ELEVENTHARG2(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,N,...) N
# define RSEQ() "dddddddddd","ddddddddd","dddddddd", \
    "ddddddd","dddddd","ddddd","dddd","ddd","dd","d",""

# define FOO(...) bar( FORMATSTRING(__VA_ARGS__) __VA_OPT__(,) __VA_ARGS__ )

FOO()      // expands to: bar( "" )
FOO(1)     // expands to: bar( "d" , 1 )
FOO(1,2,3) // expands to: bar( "ddd" , 1,2,3 )

这适用于 GCC 和 Clang(使用 -std=c++2a)以及最多十个参数(但可以扩展)。

最大的兼容性问题是__VA_OPT__(,) 的两个实例,它们仅用于处理零参数的情况。 否则,可以将它们替换为简单的,

【讨论】:

【参考方案3】:

只有 2 个宏:

#define GET_MACRO(_0,_1,_2,_3,_4,NAME,...) NAME
#define FOO(...) bar(GET_MACRO(0,##__VA_ARGS__,"dddd","ddd","dd","d",""), ##__VA_ARGS__)

【讨论】:

但是,##__VA_ARGS__ 不是有效的标准 C。

以上是关于可变参数宏包装器,扩展为使用与参数数量相对应的字符格式化字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何将元组扩展为可变参数模板函数的参数?

为具有可变数量参数的不同函数编写通用包装器

C++ 预处理器 __VA_ARGS__ 参数数量

如何制作可变参数宏(可变数量的参数)

Rust的宏可以像C预处理器宏一样扩展为十六进制数吗?

可变参数宏与枚举