I have a variadic function from a third-party C library:
int func(int argc, ...);
argc indicates the number of passed optional arguments.
I'm wrapping it with a macro that counts the number of arguments, as suggested here. For reading convenience, here's the macro:
#define PP_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \
_11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \
_21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \
_31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \
_41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \
_51, _52, _53, _54, _55, _56, _57, _58, _59, _60, \
_61, _62, _63, N, ...) N
#define PP_RSEQ_N() \
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, 0
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
#define PP_NARG(...) PP_NARG_(__VA_ARGS__, PP_RSEQ_N())
and I'm wrapping it like so:
#define my_func(...) func(PP_NARG(__VA_ARGS__), __VA_ARGS__)
The PP_NARG macro works great for functions accepting one or more arguments. For instance, PP_NARG("Hello", "World") evaluates to 2.
The problem is that when no arguments are passed, PP_NARG() evaluates to 1 instead of 0.
I understand how this macro works, but I can't come up with an idea to modify it so that it behaves correctly for this case as well.
Any ideas?
EDIT:
I have found a workaround for PP_NARG, and posted it as an answer.
I still have problems with wrapping the variadic function though. When __VA_ARGS__ is empty, my_func expands to func(0, ) which triggers a compilation error.
Another possibility, which does not use sizeof nor a GCC extension is to add the following to your code
#define PP_COMMASEQ_N() \
1, 1, 1, 1, \
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
1, 1, 1, 1, 1, 1, 1, 1, 0, 0
#define PP_COMMA(...) ,
#define PP_HASCOMMA(...) \
PP_NARG_(__VA_ARGS__, PP_COMMASEQ_N())
#define PP_NARG(...) \
PP_NARG_HELPER1( \
PP_HASCOMMA(__VA_ARGS__), \
PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()), \
PP_NARG_(__VA_ARGS__, PP_RSEQ_N()))
#define PP_NARG_HELPER1(a, b, N) PP_NARG_HELPER2(a, b, N)
#define PP_NARG_HELPER2(a, b, N) PP_NARG_HELPER3_ ## a ## b(N)
#define PP_NARG_HELPER3_01(N) 0
#define PP_NARG_HELPER3_00(N) 1
#define PP_NARG_HELPER3_11(N) N
The result is
PP_NARG() // expands to 0
PP_NARG(x) // expands to 1
PP_NARG(x, 2) // expands to 2
Explanation:
The trick in these macros is that PP_HASCOMMA(...) expands to 0 when called with zero or one argument and to 1 when called with at least two arguments. To distinguish between these two cases, I used PP_COMMA __VA_ARGS__ (), which returns a comma when __VA_ARGS__ is empty and returns nothing when __VA_ARGS__ is non-empty.
Now there are three possible cases:
__VA_ARGS__is empty:PP_HASCOMMA(__VA_ARGS__)returns 0 andPP_HASCOMMA(PP_COMMA __VA_ARGS__ ())returns 1.__VA_ARGS__contains one argument:PP_HASCOMMA(__VA_ARGS__)returns 0 andPP_HASCOMMA(PP_COMMA __VA_ARGS__ ())returns 0.__VA_ARGS__contains two or more arguments:PP_HASCOMMA(__VA_ARGS__)returns 1 andPP_HASCOMMA(PP_COMMA __VA_ARGS__ ())returns 1.
The PP_NARG_HELPERx macros are just needed to resolve these cases.
Edit:
In order to fix the func(0, ) problem, we need to test whether we have supplied zero
or more arguments. The PP_ISZERO macro comes into play here.
#define PP_ISZERO(x) PP_HASCOMMA(PP_ISZERO_HELPER_ ## x)
#define PP_ISZERO_HELPER_0 ,
Now let's define another macro which prepends the number of arguments to an argument list:
#define PP_PREPEND_NARG(...) \
PP_PREPEND_NARG_HELPER1(PP_NARG(__VA_ARGS__), __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER1(N, ...) \
PP_PREPEND_NARG_HELPER2(PP_ISZERO(N), N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER2(z, N, ...) \
PP_PREPEND_NARG_HELPER3(z, N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER3(z, N, ...) \
PP_PREPEND_NARG_HELPER4_ ## z (N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER4_1(N, ...) 0
#define PP_PREPEND_NARG_HELPER4_0(N, ...) N, __VA_ARGS__
The many helpers are again needed to expand the macros to numeric values. Finally test it:
#define my_func(...) func(PP_PREPEND_NARG(__VA_ARGS__))
my_func() // expands to func(0)
my_func(x) // expands to func(1, x)
my_func(x, y) // expands to func(2, x, y)
my_func(x, y, z) // expands to func(3, x, y, z)
Online example:
http://coliru.stacked-crooked.com/a/73b4b6d75d45a1c8
See also:
Please have also a look at the P99 project, which has much more advanced preprocessor solutions, like these.
I came up with the following workaround for PP_NARG:
#define PP_NARG(...) (sizeof(#__VA_ARGS__) - 1 ? \
PP_NARG_(__VA_ARGS__, PP_RSEQ_N()) : 0)
It stringifies __VA_ARGS__, so if it's empty its length equals 1 (because #__VA_ARGS__ == '\0').
It works with -std=c99 -pedantic.
I still have problems with wrapping the variadic function though. When __VA_ARGS__ is empty, my_func expands to func(0, ) which triggers a compilation error.
It is possible to do in GCC using the ##VA_ARGS extension:
#define PP_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \
_11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \
_21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \
_31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \
_41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \
_51, _52, _53, _54, _55, _56, _57, _58, _59, _60, \
_61, _62, _63, N, ...) N
/* Note 63 is removed */
#define PP_RSEQ_N() \
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, 0
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
/* Note dummy first argument _ and ##__VA_ARGS__ instead of __VA_ARGS__ */
#define PP_NARG(...) PP_NARG_(_, ##__VA_ARGS__, PP_RSEQ_N())
#define my_func(...) func(PP_NARG(__VA_ARGS__), __VA_ARGS__)
Now PP_NARG(a, b, c) gives 3 and PP_NARG() gives 0.
Unfortunately I don't see a way to make it work in general.
The full example on Mehrwolf's answer unfortunately doesn't compile on VS2010 nor it does on VS2015 (i tried it also on this online VS compiler). A different approach on this problem that allows to produce cross-platform code that compiles on a wide range of compilers is using non-standard extensions that are also available in MS compilers as in my answer to a similar question. Also, differently form the cited answer, the result of PP_NARG((a, b, c)) using the extensions (in both MS and gcc/clang compilers) is 1 and not 0.
来源:https://stackoverflow.com/questions/11317474/macro-to-count-number-of-arguments