2
votes

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

3
I don't see any likelihood of avoiding "extremely complicated", especially since you're unable to rely on a macro stack provided by a third-party package (which would just move the complexity, anyway, not eliminate it). C's macro processor is not designed to be a programming language in its own right, and your proposed behavior touches on at least two distinct areas that are difficult to handle with it. Why can't your code generator handle generating the full function calls itself?John Bollinger
That's easy, just overload the macro on number of arguments... Have the overload return "d" "dd" "ddd" etc. depending.KamilCuk
Does this answer your question? Overloading Macro on Number of ArgumentsKamilCuk
@KamilCuk: It’s not bad, but there are still some synergy effect. I posted an answer based on them.Wrzlprmft
@JohnBollinger: Why can't your code generator handle generating the full function calls itself? – Theoretically it can, but it would be a major hassle due to reasons that go far beyond the scope of this question.Wrzlprmft

3 Answers

3
votes

With 100% standard C, you could do this:

#define COUNT_ARGS(...) (sizeof((int[]){__VA_ARGS__}) / sizeof(int))
#define STRTABLE (const char*[]){ "", "d", "dd", "ddd", "ddddd" } // and so on
#define FOO(...) bar(STRTABLE[COUNT_ARGS(__VA_ARGS__)], __VA_ARGS__)

In this example, STRTABLE is a compound literal look-up table with a bunch of string literals as an initializer list. Only the initializer corresponding to the number of arguments passed to the macro is used, by counting the number of macro arguments and using that array index specifically.

Full example:

#include <stdio.h>

#define COUNT_ARGS(...) (sizeof((int[]){__VA_ARGS__}) / sizeof(int))
#define STRTABLE (const char*[]){ "", "d", "dd", "ddd", "ddddd" } // and so on
#define FOO(...) bar(STRTABLE[COUNT_ARGS(__VA_ARGS__)], __VA_ARGS__)

void bar(const char* fmt, ...)
{
  puts(fmt);
}

int main (void)
{
  FOO(1);
  FOO(1,2);
  FOO(1,2,3);
}
0
votes

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 ,.

-1
votes

Just 2 macros:

#define GET_MACRO(_0,_1,_2,_3,_4,NAME,...) NAME
#define FOO(...) bar(GET_MACRO(0,##__VA_ARGS__,"dddd","ddd","dd","d",""), ##__VA_ARGS__)