How does this work? How can a C99/C++11 variadic macro be implemented to expand to different things on the sole basis of how many arguments are given to it?
For portability, you should not have more than 31 parameters for a macro. The parameter list may end with an ellipsis (…).
Macros cannot be overloaded and the (...) mechanism cannot do what you want.
Yes. If you try untouchable(foo,first) you get an error because the macro only sees two parameters but was expecting 4.
The macro definition can include macro arguments, which can be assigned specific values in the macro call. There are two types of arguments: keyword and positional. Keyword arguments are assigned names in the macro definition; in the macro call, they are identified by name.
(Edit: See the end for a ready-made solution.)
To get an overloaded macro, first we need a macro which selects between several implementations. This part doesn't use a variadic macro. Then a variadic macro which generically counts its arguments produces a selector. Plugging the argument count into a dispatcher produces an overloaded macro.
Caveat: This system cannot tell the difference between zero and one arguments because there is no difference between no argument, and a single empty argument. They both look like MACRO()
.
To select between implementations, use the macro catenation operator with a series of function-like macros.
#define select( selector, ... ) impl ## _ ## selector( __VA_ARGS__ ) #define impl_1() meh #define impl_2( abc, xyz ) # abc "wizza" xyz() //etc // usage: select( 1 ) => impl_1() => meh // select( 2, huz, bar ) => impl_2( huzza, bar ) => "huz" "wizza" bar()
Because the ##
operator suppresses macro expansion of its arguments, it's better to wrap it in another macro.
#define CAT( A, B ) A ## B #define SELECT( NAME, NUM ) CAT( NAME ## _, NUM )
To count arguments, use __VA_ARGS__
to shift arguments like so (this is the clever part):
#define GET_COUNT( _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT #define VA_SIZE( ... ) GET_COUNT( __VA_ARGS__, 6, 5, 4, 3, 2, 1 )
#define CAT( A, B ) A ## B #define SELECT( NAME, NUM ) CAT( NAME ## _, NUM ) #define GET_COUNT( _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT #define VA_SIZE( ... ) GET_COUNT( __VA_ARGS__, 6, 5, 4, 3, 2, 1 ) #define VA_SELECT( NAME, ... ) SELECT( NAME, VA_SIZE(__VA_ARGS__) )(__VA_ARGS__)
#define MY_OVERLOADED( ... ) VA_SELECT( MY_OVERLOADED, __VA_ARGS__ ) #define MY_OVERLOADED_1( X ) foo< X > #define MY_OVERLOADED_2( X, Y ) bar< X >( Y ) #define MY_OVERLOADED_3( X, Y, Z ) bang_ ## X< Y >.Z()
Following is an improvement upon Potatoswatter's answer, which can differentiate between zero and one argument.
In a nutshell, when __VA_ARGS__
is empty, EXPAND __VA_ARGS__ ()
inside VA_SIZE
macro becomes EXPAND ()
and is substituted with 6 commas. So, VA_SIZE...
becomes COMPOSE( GET_COUNT, (,,,,,, , 0, 6, 5, 4, 3, 2, 1) )
, and that becomes GET_COUNT (,,,,,, , 0, 6, 5, 4, 3, 2, 1)
and returns 0.
On the other hand, when __VA_ARGS__
is eg, int, 5
, EXPAND __VA_ARGS__ ()
becomes EXPAND int, 5 ()
. So, VA_SIZE...
becomes COMPOSE( GET_COUNT, (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) )
, which becomes GET_COUNT (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1)
and returns 2, as described in Potatoswatter's answer.
I got the EXPAND
idea from Jason Dang's answer.
#define CAT( A, B ) A ## B #define SELECT( NAME, NUM ) CAT( NAME ## _, NUM ) #define COMPOSE( NAME, ARGS ) NAME ARGS #define GET_COUNT( _0, _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT #define EXPAND() ,,,,,, // 6 commas (or 7 empty tokens) #define VA_SIZE( ... ) COMPOSE( GET_COUNT, (EXPAND __VA_ARGS__ (), 0, 6, 5, 4, 3, 2, 1) ) #define VA_SELECT( NAME, ... ) SELECT( NAME, VA_SIZE(__VA_ARGS__) )(__VA_ARGS__)
#define MY_OVERLOADED( ... ) VA_SELECT( MY_OVERLOADED, __VA_ARGS__ ) #define MY_OVERLOADED_0( ) meh() #define MY_OVERLOADED_1( X ) foo< X > #define MY_OVERLOADED_2( X, Y ) bar< X >( Y ) #define MY_OVERLOADED_3( X, Y, Z ) bang_ ## X< Y >.Z() MY_OVERLOADED() // meh() MY_OVERLOADED(bool) // foo< bool > MY_OVERLOADED(int, 5) // bar< int >( 5 ) MY_OVERLOADED(me, double, now) // bang_me< double >.now()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With