Search code examples
c++macrosvariadic

Variadic macro warning


In C++, variadic macros require at least one argument for the '...'. Consider the function: FOO(a, b, ...); What should I do in case I want both of these calls to be correct and warning-free? FOO(5, 4, "gamma"); FOO(5, 4); I'm using the --pedantic flag, so just suppressing the warning is not an option.

The second one gives a compile-time warning mentioned above. I considered this:

Changing the definition to FOO(a, ...); and splitting the __VA_ARGS__ variable (which stands for ...) into b and, if present, into the rest. So the function calls would look like: FOO(5, "4gamma"); and FOO(5, "4"); This, I think, is not a good option because the splitting is not efficient and the function declaration will not require the b argument, even though its mandatory.

Are there any better ways to get a warning-free compilation?


Solution

  • Although I wholly agree that if at all possible, variadic functions or function templates should be used, the question also shows some misunderstanding about what can and cannot be done with macros, so in this answer I'm going to pretend functions are not an option.

    Changing the definition to FOO(a, ...); and splitting the __VA_ARGS__ variable (which stands for ...) into b and, if present, into the rest.

    Yes.

    So the function calls would look like: FOO(5, "4gamma"); and FOO(5, "4");

    No. Continue the invocation as FOO(5, 4, "gamma"); and FOO(5, 4);. In the first case, __VA_ARGS__ is 4, "gamma". In the second case, __VA_ARGS__ is 4.

    If you need to extract , "gamma" from the former, that can be done by the preprocessor. It requires an upper limit on the amount of parameters, but you can increase this to pretty much any number you like. It's ugly though.

    If __VA_ARGS__ contains no commas, the extraction is trivial:

    #define COMMA_TRAILING_ARGS_0(a)
    

    If you know __VA_ARGS__ contains at least one comma, you can use

    #define COMMA_TRAILING_ARGS_1(a, ...) , __VA_ARGS__
    

    And you can detect which of these to use, up to a certain upper limit of macro arguments:

    #define ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) _16
    #define HAS_COMMA(...) ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, )
    

    Combined:

    #define COMMA_TRAILING_ARGS_0(a)
    #define COMMA_TRAILING_ARGS_1(a, ...) , __VA_ARGS__
    
    #define ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) _16
    #define HAS_COMMA(...) ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,)
    
    #define CONCAT(a, b) a ## b
    #define CONCAT_(a, b) CONCAT(a, b)
    
    #define FOO(a, ...) BAR(a CONCAT_(COMMA_TRAILING_ARGS_, HAS_COMMA(__VA_ARGS__)) (__VA_ARGS__))
    
    FOO(5, 4, x, q);    // expands to BAR(5, x, q);
    FOO(5, 4, "gamma"); // expands to BAR(5, "gamma");
    FOO(5, 4);          // expands to BAR(5);