45

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?

Community
  • 1
  • 1
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 2
    Doesn't answer how it works, but if someone just needs an implementation: [boost preprocessor](http://www.boost.org/doc/libs/1_53_0/libs/preprocessor/doc/ref/overload.html) – dyp May 22 '13 at 06:19

5 Answers5

65

(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 )

Library code:

#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__)

Usage:

#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()
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Neat, I didn't know you could reliably do that. – chris May 22 '13 at 03:50
  • Thank you, that is a great explanation. Maybe a bit more details on the "clever" part would be nice. – Oli May 22 '13 at 04:02
  • 3
    @Oli, Say you pass one argument to `VA_SIZE`. It would expand to `GET_COUNT(x, 6, 5, 4, 3, 2, 1)`, which cancels out the `x` with `_1`, the `6` with `_2`, and so on, and you're left with `1` being `COUNT`. With two arguments, one more gets pushed on and `2` becomes `COUNT`. – chris May 22 '13 at 04:06
  • @chris, indeed, that is clever – Oli May 22 '13 at 04:08
  • @Oli, It is, but it's too bad there's a limit to the number of arguments it will work with, hence the "ad nauseam". You can always add as many as you'd like to support. – chris May 22 '13 at 04:10
  • Do you know if there is support (in Boost for example) for the reliably detection of the number of elements of `__VA_ARGS__` ? I think some compilers offer intrinsics for it, and Boost can usually be counted on to use the appropriate intrinsic depending on the compiler. – Matthieu M. May 22 '13 at 06:57
  • @MatthieuM. I'm mostly unaware of the existing utilities. Hence opening this question :vP . I'm maintaining a C++ preprocessor, but I mostly avoid preprocessor metaprogramming, so practical libraries are mostly outside my interest. And the Boost.PP docs don't make it easy to grasp what's impossible or why. So… maybe open another Q. – Potatoswatter May 22 '13 at 08:04
  • 1
    Note that in MSVC there seem to be a bug. Here's a workaround: http://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly – Asaf Jul 03 '16 at 15:46
5

I would post this as a comment to Potatoswatter's post, but it's too long and requires a code listing.

Here is a bit of perl code for generating a set of macros which are meant to be overloaded macros.

$ perl -le 'map{
        $arity = $_; map {
                $ar = 2 + $arity + $_; $arm = $ar - 1; $arlist = join("", map{"A$_, "} 1..$arity); $warlist = "WHAT, $arlist";
                @li = map {"_$_"} 0..$_; $lis = join(", ", @li); $lim = pop @li; $lims = join(", ", @li);
                print "#define FEI_${arity}A_$ar($warlist$lis) FEI_${arity}A_$arm($warlist$lims) WHAT($_, $arlist$lim)"
        } 1..3; print ""
} 0..4'

Here is the output of the script:

#define FEI_0A_3(WHAT, _0, _1) FEI_0A_2(WHAT, _0) WHAT(1, _1)
#define FEI_0A_4(WHAT, _0, _1, _2) FEI_0A_3(WHAT, _0, _1) WHAT(2, _2)
#define FEI_0A_5(WHAT, _0, _1, _2, _3) FEI_0A_4(WHAT, _0, _1, _2) WHAT(3, _3)

#define FEI_1A_4(WHAT, A1, _0, _1) FEI_1A_3(WHAT, A1, _0) WHAT(1, A1, _1)
#define FEI_1A_5(WHAT, A1, _0, _1, _2) FEI_1A_4(WHAT, A1, _0, _1) WHAT(2, A1, _2)
#define FEI_1A_6(WHAT, A1, _0, _1, _2, _3) FEI_1A_5(WHAT, A1, _0, _1, _2) WHAT(3, A1, _3)

#define FEI_2A_5(WHAT, A1, A2, _0, _1) FEI_2A_4(WHAT, A1, A2, _0) WHAT(1, A1, A2, _1)
#define FEI_2A_6(WHAT, A1, A2, _0, _1, _2) FEI_2A_5(WHAT, A1, A2, _0, _1) WHAT(2, A1, A2, _2)
#define FEI_2A_7(WHAT, A1, A2, _0, _1, _2, _3) FEI_2A_6(WHAT, A1, A2, _0, _1, _2) WHAT(3, A1, A2, _3)

#define FEI_3A_6(WHAT, A1, A2, A3, _0, _1) FEI_3A_5(WHAT, A1, A2, A3, _0) WHAT(1, A1, A2, A3, _1)
#define FEI_3A_7(WHAT, A1, A2, A3, _0, _1, _2) FEI_3A_6(WHAT, A1, A2, A3, _0, _1) WHAT(2, A1, A2, A3, _2)
#define FEI_3A_8(WHAT, A1, A2, A3, _0, _1, _2, _3) FEI_3A_7(WHAT, A1, A2, A3, _0, _1, _2) WHAT(3, A1, A2, A3, _3)

#define FEI_4A_7(WHAT, A1, A2, A3, A4, _0, _1) FEI_4A_6(WHAT, A1, A2, A3, A4, _0) WHAT(1, A1, A2, A3, A4, _1)
#define FEI_4A_8(WHAT, A1, A2, A3, A4, _0, _1, _2) FEI_4A_7(WHAT, A1, A2, A3, A4, _0, _1) WHAT(2, A1, A2, A3, A4, _2)
#define FEI_4A_9(WHAT, A1, A2, A3, A4, _0, _1, _2, _3) FEI_4A_8(WHAT, A1, A2, A3, A4, _0, _1, _2) WHAT(3, A1, A2, A3, A4, _3)

These are the (regularly structured sections of) groups of macro overloads that are used for generating FOR_EACH (a.k.a. FE) macros which can dispatch a WHAT macro optionally with an arbitrary number of constant arguments (A1, A2...) in addition to an arbitrary number of arguments in a list, along with an index in the proper ordering (a naive implementation without using something like SELECT for overloading would yield reversed indices).

As an example, the remaining section (the nonregular "base case" portion of the second block) looks like the following:

#define FE_INDEXED_1ARG(...) VA_SELECT(FEI_1A, __VA_ARGS__)
#define FEI_1A_3(WHAT, A1, _0) WHAT(0, A1, _0)

The utility of this can perhaps be brought into question (I built it because I saw a use for it...), and neither does this answer the OP's question directly (in fact, it sort of does the opposite -- a foreach construct does the same thing to all variadic arguments...), but I just thought that the technique is pretty interesting (as well as utterly horrifying in some ways) and allows for quite some expressive power using the preprocessor and it will be possible to generate very efficient machine code in this manner. I think it also serves as a poignant example of why I personally think the C preprocessor still has room for improvement.

By which I mean that the C preprocessor is an absolute abomination and we should probably scrap it and start from scratch :)

Steven Lu
  • 41,389
  • 58
  • 210
  • 364
5

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.

Library code:

#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__)

Usage:

#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()
4

Though it's already answered, I have prepared a very short version of it. Hope it may help.

Implementation

// Variable Argument Macro (VA_MACRO) upto 6 arguments
#define NUM_ARGS_(_1, _2, _3, _4, _5, _6, TOTAL, ...) TOTAL
#define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 6, 5, 4, 3, 2, 1)

#define CONCATE_(X, Y) X##Y  // Fixed the double '_' from previous code
#define CONCATE(MACRO, NUMBER) CONCATE_(MACRO, NUMBER)
#define VA_MACRO(MACRO, ...) CONCATE(MACRO, NUM_ARGS(__VA_ARGS__))(__VA_ARGS__)

Customization

// This is how user may define own set of variadic macros
#define MY_MACRO(...) VA_MACRO(MY_MACRO, __VA_ARGS__)
#define MY_MACRO1(_1) "One"
#define MY_MACRO2(_1, _2) "Two"
#define MY_MACRO3(_1, _2, _3) "Three"

Usage

// While using those, user needs to use only the main macro
int main ()
{
  auto one = MY_MACRO(1);
  auto two = MY_MACRO(1, 2); 
  auto three = MY_MACRO(1, 2, 3); 
}
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • How is this different from my answer? The only thing I see is the double underscore, which is a real error that some compilers will flag. – Potatoswatter Dec 26 '16 at 03:50
  • 1
    @Potatoswatter, it's not much different, except that it's easy to understand, generalised, simiplified and the naming of the macros & its argument are more lucid for a newbee. When I read your answer, I got completely lost and was able to figure out the "actual" solution myself with some help from your answer. Regarding `__`, I have purposefully kept (as you can see in the comment), because typically people will have a `CONCATE` & `CONCATE_` combination of their own for concatenating 2 strings, hence that part will anyways go away. Think of that '__' as a placeholder for sake of understanding. – iammilind Dec 26 '16 at 05:20
  • It's not generalized. It's practically the same code but there is no explanation attached. Perhaps I should put the code first and the description second. The `__` is an error; it is reserved for the compiler implementer and not allowed for the user. – Potatoswatter Dec 26 '16 at 07:13
  • 2
    Found this answer helpfull as the code is more easy to read. Reading this, made it easier to understand @Potatoswatter 's answer. so an upvote for this answer too. – Otzen Jul 21 '17 at 11:51
0

I extended the solution from Potatowatter to avoid the iso c99 requires rest arguments to be used issue when gcc's compiler switch -pedantic is in use.
Zero parameters are also possible

Library

#define NUM_ARGS_(_1, _2, _3, _4, _5, _6, _7, _8, TOTAL, ...) TOTAL
#define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
#define CONCATE_(X, Y) X##Y
#define CONCATE(MACRO, NUMBER) CONCATE_(MACRO, NUMBER)
#define VA_MACRO(MACRO, ...) CONCATE(MACRO, NUM_ARGS (__VA_ARGS__))(__VA_ARGS__)

Customisation

Note: In this example MacroTest is your overloaded C++ function. But you can place an functions here.

#define MY_OVERLOADED(...) VA_MACRO(MY_OVERLOADED, void, void, __VA_ARGS__)
#define MY_OVERLOADED0(s, t) MacroTest()
#define MY_OVERLOADED1(s, t, a) MacroTest( a)
#define MY_OVERLOADED2(s, t, a, b) MacroTest(a, b)
#define MY_OVERLOADED3(s, t, a, b, c) MacroTest(a, b, c)

Usage

MY_OVERLOADED();
MY_OVERLOADED(1);
MY_OVERLOADED(11, 22);
MY_OVERLOADED(111, 222, 333);

Complete Example

Tested with https://www.onlinegdb.com/online_c++_compiler and https://www.jdoodle.com/online-compiler-c++/

#include <stdio.h>

#define NUM_ARGS_(_1, _2, _3, _4, _5, _6, _7, _8, TOTAL, ...) TOTAL
#define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
#define CONCATE_(X, Y) X##Y
#define CONCATE(MACRO, NUMBER) CONCATE_(MACRO, NUMBER)
#define VA_MACRO(MACRO, ...) CONCATE(MACRO, NUM_ARGS (__VA_ARGS__))(__VA_ARGS__)

void MacroTest() { printf("no param\n"); }
void MacroTest(int p1) { printf("p1=%i\n", p1); }
void MacroTest(int p1, int p2) { printf("p1=%i p2=%i\n", p1, p2); }
void MacroTest(int p1, int p2, int p3) { printf("p1=%i p2=%i p3=%i\n", p1, p2, p3); }

#define MY_OVERLOADED(...) VA_MACRO(MY_OVERLOADED, void, void, __VA_ARGS__)
#define MY_OVERLOADED0(s, t) MacroTest()
#define MY_OVERLOADED1(s, t, a) MacroTest( a)
#define MY_OVERLOADED2(s, t, a, b) MacroTest(a, b)
#define MY_OVERLOADED3(s, t, a, b, c) MacroTest(a, b, c)

int main()
{
    printf("gcc %i.%i.%i\n", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
    printf("MY_OVERLOADED()             : ");
    MY_OVERLOADED();
    printf("MY_OVERLOADED(1)            : ");
    MY_OVERLOADED(1);
    printf("MY_OVERLOADED(11, 22)       : ");
    MY_OVERLOADED(11, 22);
    printf("MY_OVERLOADED(111, 222, 333): ");
    MY_OVERLOADED(111, 222, 333);

    return 0;
}
Th. Thielemann
  • 2,592
  • 1
  • 23
  • 38
  • This should be the expansion (__VAR_ARGS__ are empty/not present): `MY_OVERLOADED()` -> `VA_MACRO(MY_OVERLOADED, void, void)` and therefore `VA_MACRO(MY_OVERLOADED, void, void)` -> `MacroTest()` – Th. Thielemann Jan 06 '23 at 10:57
  • Tried it with an online g++ and it worked as expected. I did not change any settings. Default version was g++ 9.4.0. – Th. Thielemann Jan 07 '23 at 10:18