可变参数宏包装器,扩展为使用与参数数量相对应的字符格式化字符串
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_DECREF
s。)
【问题讨论】:
我认为没有任何可能避免“极其复杂”,特别是因为您无法依赖第三方包提供的宏堆栈(这只会 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。以上是关于可变参数宏包装器,扩展为使用与参数数量相对应的字符格式化字符串的主要内容,如果未能解决你的问题,请参考以下文章