c++coverloadingc-preprocessorvariadic-macros

Can macros be overloaded by number of arguments?


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?


Solution

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