Search code examples
cc-preprocessorpython-c-apivariadic-macros

Variadic macro wrapper that expands to format string with characters corresponding to number of arguments


Question

I am looking for a variadic C preprocessor macro that passes its argument and a corresponding format string to a function, repeating a character depending on the number of arguments. For example, I would like a macro FOO which expands as follows (or to equivalent C code):

  • FOO(1)bar("d",1)
  • FOO(1,2)bar("dd",1,2),
  • FOO(1,2,3)bar("ddd",1,2,3)
  • bonus: FOO()bar("")

While I can combine the solutions to C preprocessor macro for returning a string repeated a certain number of times and C++ preprocessor __VA_ARGS__ number of arguments (or similar questions) or use variadic macros, these have several drawbacks such as:

  • requiring special libraries, such as Boost (which would be an issue for me),
  • being compiler-dependent,
  • only working at runtime,
  • being extremely complicated.

My hope is that some better solutions emerge when these problems are not regarded separately.

Background

I want to callback Python functions in a C extension of Python in automatically generated code. So, for example, I need foo(1,2,3) to expand to:

PyObject_CallObject( callback_foo, Py_Build_Value("(Oddd)",Y,1,2,3) )

I know that all arguments of foo are doubles, but I do not know their number. (The above example is somewhat simplified. I am aware that it is missing a few Py_DECREFs.)


Solution

  • The best I could come up with so far is to take this answer and simplify it:

    # define EXPAND(x) x
    
    # define FORMATSTRING(...) EXPAND(ELEVENTHARG1(__VA_ARGS__ __VA_OPT__(,) RSEQ()))
    # define ELEVENTHARG1(...) EXPAND(ELEVENTHARG2(__VA_ARGS__))
    # define ELEVENTHARG2(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,N,...) N
    # define RSEQ() "dddddddddd","ddddddddd","dddddddd", \
        "ddddddd","dddddd","ddddd","dddd","ddd","dd","d",""
    
    # define FOO(...) bar( FORMATSTRING(__VA_ARGS__) __VA_OPT__(,) __VA_ARGS__ )
    
    FOO()      // expands to: bar( "" )
    FOO(1)     // expands to: bar( "d" , 1 )
    FOO(1,2,3) // expands to: bar( "ddd" , 1,2,3 )
    

    This works with GCC and Clang (with -std=c++2a) and up to ten arguments (but can be expanded).

    The biggest compatibility issue are the two instances of __VA_OPT__(,), which are only required for handling the zero-argument case. Otherwise, they can be replaced by a simple ,.