问题描述
有很多问题讨论如何计算 __VA_ARGS__
和零参数的问题(例如 [1] 和 [2])。然而,这些问题的答案通常不是可移植的,因为它们使用 GCC 特定的 ##__VA_ARGS__
来解释“0 个参数”的情况,或者它们是可移植的,但不能解释 0 个参数(两个 {{1} } 和 COUNT_ARGS()
的计算结果为 COUNT_ARGS(something)
)。
是否有一种解决方案可以计算 1
中的参数数量,包括 0,并且可以在任何符合标准的 C 编译器中运行?
解决方法
经过一番搜索,我找到了 Jens Gustedt 的一篇博文,名为“Detect empty macro arguments”(我在 this answer 中找到了他的评论)。结合 H Walters 的 counting solution found in another answer(类似于 this one),我们可以构建一个可以在任何 C99 编译器中工作的解决方案。下面的代码是这两种方法的统一。
我所做的一项值得注意的更改是添加了额外的 __VA_ARGS__
宏。如 this question 中所述,MSVC 不像大多数其他编译器那样扩展 EXPAND
,因此需要额外的扩展步骤。
__VA_ARGS__
这些是一些示例输出:
#define EXPAND(x) x
#define _GLUE(X,Y) X##Y
#define GLUE(X,Y) _GLUE(X,Y)
#define _ARG_100(_,\
_100,_99,_98,_97,_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81,\
_80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,_64,_63,_62,_61,\
_60,_59,_58,_57,_56,_55,_54,_53,_52,_51,_50,_49,_48,_47,_46,_45,_44,_43,_42,_41,\
_40,_39,_38,_37,_36,_35,_34,_33,_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21,\
_20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,X_,...) X_
#define HAS_COMMA(...) EXPAND(_ARG_100(__VA_ARGS__,\
1,1,0))
#define _TRIGGER_PARENTHESIS_(...),#define _PASTE5(_0,_1,_4) _0 ## _1 ## _2 ## _3 ## _4
#define _IS_EMPTY_CASE_0001,#define _IS_EMPTY(_0,_3) HAS_COMMA(_PASTE5(_IS_EMPTY_CASE_,_0,_3))
#define IS_EMPTY(...) \
_IS_EMPTY( \
/* test if there is just one argument,eventually an empty \
one */ \
HAS_COMMA(__VA_ARGS__),\
/* test if _TRIGGER_PARENTHESIS_ together with the argument \
adds a comma */ \
HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__),\
/* test if the argument together with a parenthesis \
adds a comma */ \
HAS_COMMA(__VA_ARGS__ (/*empty*/)),\
/* test if placing it between _TRIGGER_PARENTHESIS_ and the \
parenthesis adds a comma */ \
HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__ (/*empty*/)) \
)
#define _VAR_COUNT_EMPTY_1(...) 0
#define _VAR_COUNT_EMPTY_0(...) EXPAND(_ARG_100(__VA_ARGS__,\
100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,\
80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,\
60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,\
40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,\
20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1))
#define VAR_COUNT(...) GLUE(_VAR_COUNT_EMPTY_,IS_EMPTY(__VA_ARGS__))(__VA_ARGS__)
正如 Jens Gustedt 在他的博客文章中所指出的,这个解决方案有一个缺陷。引用:
事实上,当使用宏作为期望 #define EATER0(...)
#define EATER1(...),#define EATER2(...) (/*empty*/)
#define EATER3(...) (/*empty*/),#define EATER4(...) EATER1
#define EATER5(...) EATER2
#define MAC0() ()
#define MAC1(x) ()
#define MACV(...) ()
#define MAC2(x,y) whatever
VAR_COUNT() // 0
VAR_COUNT(/*comment*/) // 0
VAR_COUNT(a) // 1
VAR_COUNT(a,b) // 2
VAR_COUNT(a,b,c) // 3
VAR_COUNT(a,c,d) // 4
VAR_COUNT(a,d,e) // 5
VAR_COUNT((a,e)) // 1
VAR_COUNT((void)) // 1
VAR_COUNT((void),d) // 3
VAR_COUNT((a,b),d) // 3
VAR_COUNT(_TRIGGER_PARENTHESIS_) // 1
VAR_COUNT(EATER0) // 1
VAR_COUNT(EATER1) // 1
VAR_COUNT(EATER2) // 1
VAR_COUNT(EATER3) // 1
VAR_COUNT(EATER4) // 1
VAR_COUNT(MAC0) // 1
VAR_COUNT(MAC1) // 1
VAR_COUNT(MACV) // 1
/* This one will fail because MAC2 is not called correctly.*/
VAR_COUNT(MAC2) // error
、0
或可变参数列表的参数调用 ISEMPTY 时,它应该可以工作。如果使用宏 1
作为参数调用,而该参数本身需要多个参数(例如 X
),则扩展会导致该宏 MAC2
的无效使用。
因此,如果传递的列表包含具有两个或多个参数的类似函数的宏,则 X
将失败。
除此之外,我已经在 GCC 9.3.0 和 Visual Studio 2019 上测试了宏,它也应该适用于任何 C99(或更新版本)编译器。
对修复上述缺陷的任何修改表示赞赏。