2013-11-25

How to apply a macro to all arguments of a C preprocessor macro

This blog post explains how to iterate over the variable number of arguments of a C preprocessor macro, and call another macro for each argument.

Let's assume we want automate generating the following strings:

const char import0[] = "";
const char import1[] = "import os\n";
const char import2[] = "import os\nimport os.path\n";
const char import3[] = "import os\nimport os.path\nimport sys\n";
const char import4[] = "import os\nimport os.path\nimport sys\nimport re\n";
const char import5[] = "import os\nimport os.path\nimport sys\nimport re\n"
                       "import select\n";
const char import6[] = "import os\nimport os.path\nimport sys\nimport re\n"
                       "import select\nimport struct\n";

A simple option is to create a macro which uses stringification:

#define IMPORT(n) "import " #n "\n"

const char import0[] = "";
const char import1[] = IMPORT(os);
const char import2[] = IMPORT(os) IMPORT(os.path);
const char import3[] = IMPORT(os) IMPORT(os.path) IMPORT(sys);
const char import4[] = IMPORT(os) IMPORT(os.path) IMPORT(sys) IMPORT(re);
const char import5[] = IMPORT(os) IMPORT(os.path) IMPORT(sys) IMPORT(re)
                       IMPORT(select);
const char import6[] = IMPORT(os) IMPORT(os.path) IMPORT(sys) IMPORT(re)
                       IMPORT(select) IMPORT(struct);

Would it be possible to create a variadic macro which would call IMPORT for all its arguments? We want the following to work:

const char import0[] = IMPORT_ALL();
const char import1[] = IMPORT_ALL(os);
const char import2[] = IMPORT_ALL(os, os.path);
const char import3[] = IMPORT_ALL(os, os.path, sys);
const char import4[] = IMPORT_ALL(os, os.path, sys, re);
const char import5[] = IMPORT_ALL(os, os.path, sys, re, select);
const char import6[] = IMPORT_ALL(os, os.path, sys, re, select, struct);

Indeed, it's possible, define the IMPORT_ALL macro like this:

#define IMPORT_ALL(...) "" APPLY_ALL(IMPORT, __VA_ARGS__)

The tricky part is defining the APPLY_ALL macro. Here is a possible implementation which works with gcc and clang, and it supports up to 6 arguments. (It's easy to increase the number of supported arguments.)

#define APPLY0(t, dummy)
#define APPLY1(t, a) t(a)
#define APPLY2(t, a, b) t(a) t(b)
#define APPLY3(t, a, b, c) t(a) t(b) t(c)
#define APPLY4(t, a, b, c, d) t(a) t(b) t(c) t(d)
#define APPLY5(t, a, b, c, d, e) t(a) t(b) t(c) t(d) t(e)
#define APPLY6(t, a, b, c, d, e, f) t(a) t(b) t(c) t(d) t(e) t(f)

#define NUM_ARGS_H1(dummy, x6, x5, x4, x3, x2, x1, x0, ...) x0
#define NUM_ARGS(...) NUM_ARGS_H1(dummy, ##__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
#define APPLY_ALL_H3(t, n, ...) APPLY##n(t, __VA_ARGS__)
#define APPLY_ALL_H2(t, n, ...) APPLY_ALL_H3(t, n, __VA_ARGS__)
#define APPLY_ALL(t, ...) APPLY_ALL_H2(t, NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

#define IMPORT_ALL(...) "" APPLY_ALL(IMPORT, __VA_ARGS__)
#define IMPORT(n) "import " #n "\n"

const char import0[] = IMPORT_ALL();
const char import1[] = IMPORT_ALL(os);
const char import2[] = IMPORT_ALL(os, os.path);
const char import3[] = IMPORT_ALL(os, os.path, sys);
const char import4[] = IMPORT_ALL(os, os.path, sys, re);
const char import5[] = IMPORT_ALL(os, os.path, sys, re, select);
const char import6[] = IMPORT_ALL(os, os.path, sys, re, select, struct);

See http://stackoverflow.com/questions/2632300/looping-through-macro-varargs-values and http://stackoverflow.com/questions/824639/variadic-recursive-preprocessor-macros-is-it-possible for inspiration and a more detailed explanation on NUM_ARGS and C macros with a variable number of arguments.

No comments: