Anyhow, this problem has been asked many times on the usenet and internet, and I stumbled on a very creative solution by Laurent Deniau posted on comp.std.c back in 2006.
#define PP_NARG(...) \
PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
PP_ARG_N(__VA_ARGS__)
#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
/* Some test cases */
PP_NARG(A) -> 1
PP_NARG(A,B) -> 2
PP_NARG(A,B,C) -> 3
PP_NARG(A,B,C,D) -> 4
PP_NARG(A,B,C,D,E) -> 5
However, passing no arguments to this macro yields 1, which is not as we expect. So last night I tweaked the macro to fix this problem by checking the length of the stringified macro arguments and adjusting the return value for a empty __VA_ARGS__ - as follows:
#define PP_NARG(...) (PP_NARG_(__VA_ARGS__,PP_RSEQ_N()) - \
(sizeof(#__VA_ARGS__) == 1))
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
#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
The purists may point out that PP_NARG() only handles 64 arguments. For just integer arguments, a better solution for any number of arguments has been proposed by user qrdl on stackoverflow:
#define NUMARGS(...) (int)(sizeof((int[]){0, ##__VA_ARGS__})/sizeof(int)-1)
..which is appealing as it is more immediately understandable than the PP_NARG() macro, however it is less generic since it only works for ints.
Anyhow, it's great to find such novel solutions even if they may be at first a little bit non-intuitive.
Hey Colin,
ReplyDeleteThis macro trick for counting arguments definitely predates Laurent's post in 2006; Jamie Lokier posted this example using it to the gcc development list in September of 2000 and its usage quite likely predated that.
The group I was with around that time ended up using that trick as part of the FormatGuard implementation and paper, an early attempt to combat the same format string issue that gcc's -Wformat-security issue now tackles.
Thanks for the trip down memory lane, though, I remember having to reverse engineer how exactly that macro worked so we could use it.
Hi Steve, thanks for finding the reference to the earlier example! I wonder how many more curious macros exists out there in the wild. Be nice if there was some kind of GCC macro hacks website somewhere...
ReplyDelete