Variadic C functions

You can’t do variadic C functions. Everyone knows that. That’s the realm of C++, or even PHP, but not C.

Except you can.

I needed to do this last week. When I say needed, I didn’t NEED to, but my sense of neatness rebelled when for the hundredth time I had to create functionname(parameter,parameter) and functionname_default(parameter) which just called functionname(parameter,0);

How you do it is with the preprocessor, obviously. There’s no real other way it could be done. Here’s an example of how.

#include <stdio.h>

#define DO_TEST_1(x) do_test(x,1)
#define DO_TEST_2(x,y) do_test(x,y)

#define TEST_MACRO(_1,_2,FUNC,...) FUNC
#define DO_TEST(...) TEST_MACRO(__VA_ARGS__,DO_TEST_2,DO_TEST_1)(__VA_ARGS__)

void do_test(const char *name,double val)
{
    printf("%s %f\n",name,val);
}

int main(int argc,char **argv)
{
    DO_TEST("test");
    DO_TEST("test",2.3);
    return 0;
}

Three levels of macros. Yeah it’s pretty awful, which may explain why I liked it so much. But how does it work? Let’s break it down by following each step for each called function.

The initial called function DO_TEST(“test”); DO_TEST(“test,2.3);
Apply DO_TEST(…) TEST_MACRO(“test”,DO_TEST_2,DO_TEST_1)(“test”); TEST_MACRO(“test”,2.3,DO_TEST_2,DO_TEST_1)(“test”,2.3);
explanatory step TEST_MACRO(“test”=_1,DO_TEST_2=_2,DO_TEST_1=FUNC,…=empty)(“test”); TEST_MACRO(“test”=_1,2.3=_2,DO_TEST_2=FUNC,…=DO_TEST_1)(“test”,2.3);
Apply TEST_MACRO(_1,_2,FUNC,…) DO_TEST_1(“test”); DO_TEST_2(“test”,2.3);
And finally do_test(“test”,1); do_test(“test”,2.3);

Obviously the explanatory step never actually happens, but it allows you to see how the __VA_ARGS__ in DO_TEST will push along the values of the actual macro which is called, allowing you to end up with a different macro for different number of parameters, which then should be all you need.

This example only works for one and two parameters, it can be made to work with as many as you like. For example, this for up to four

#define TEST_MACRO(_1,_2,_3,_4,FUNC,...) FUNC
#define DO_TEST(...) TEST_MACRO(__VA_ARGS__,DO_TEST_4,DO_TEST_3,DO_TEST_2,DO_TEST_1)(__VA_ARGS__)

The only limit to the number of parameters here is zero. That’s possible, but the example becomes a bit more complicated and I don’t think it’s really necessary for this post. If you need that, you’ll need to use ##__VA_ARGS__ as part of the solution.

Leave a Reply