The X-Y macro
Here's a fun piece of C preprocessor trickery I thought up earlier this year.
Background
There's a fairly standard existing trick, rediscovered several times and going by several names (‘list macro’, ‘higher-
#define LIST_OF_MUSTELIDAE(X) \
X(stoat) X(weasel) X(badger) X(otter)
in which the macro takes a parameter X, which is expected to be another macro. Now you can iterate over this list doing lots of different things, by invoking the macro with different macros as parameters. For example:
#define PREFIXED_ENUM_DECL(val) M_ ## val,
enum Mustelid { LIST_OF_MUSTELIDAE(PREFIXED_ENUM_DECL) };
// expands to M_stoat, M_weasel, M_badger, M_otter,#define STRINGIFIED_NAME(val) #val ,
const char *const names[] = { LIST_OF_MUSTELIDAE(STRINGIFIED_NAME) };
// expands to "stoat", "weasel", "badger", "otter",
and now you've declared an enumerated type, and an array of textual names, in such a way that there's no risk of them getting out of sync with each other, because both are generated from the same source, namely the original definition of the X-
There are assorted variants on this. If you need a list that has two different kinds of thing interleaved, you can make your list macro take two (or more) macro parameters, and choose which one to invoke for each list element. Or you can make your list macro expect its parameter macro to accept many parameters, so that you can declare a list of things with several properties…
#define ERROR_MESSAGES(X) \
X(ERR_UNRECOGNISED, "unrecognised mustelid name") \
X(ERR_TAIL_COLOUR, "this mustelid's tail isn't that colour") \
// …
… and then each time you invoke it, the parameter macro can use some or all of its arguments, as it chooses.
The X-
A twist on this that I thought up earlier this year is a thing I'm currently calling an ‘X-X, it takes two which I'm calling X and Y.
The key point is that Y is passed as one of the parameters to X. So your list macro itself might look like this:
#define XYLIST_OF_MUSTELIDAE(X,Y) \
X(Y,stoat) X(Y,weasel) X(Y,badger) X(Y,otter)
Of course, this version can do all the same things the more usual X-
Modified list inclusion
Suppose you want to define two lists of things, one of which is a sublist of the other. For example, my program might need a list of mammals, which needs to include everything in the list of Mustelidae I've declared above, plus other things too.
Of course, you can do that with an ordinary X-X:
#define LIST_OF_MUSTELIDAE(X) \
X(stoat) X(weasel) X(badger) X(otter)
#define LIST_OF_MAMMALIA(X) \
X(cow) X(pig) LIST_OF_MUSTELIDAE(X) X(sheep)
But now suppose you wanted to modify each element of the sublist. For example, suppose I wanted a list of strings, some of which are the stringified versions of the mustelid names, and others are custom. I can't do this:
#define LIST_OF_STRINGS(X) \
X("custom string") X("hello") LIST_OF_MUSTELIDAE(X) X("blither")
because the sub-
But if the subsidiary list is defined by an X-
#define STRINGIFY_AND_CALL(X, value) X(#value)
#define LIST_OF_STRINGS(X) \
X("custom string") X("hello") \
XYLIST_OF_MUSTELIDAE(STRINGIFY_AND_CALL, X) \
X("blither")
because the X-STRINGIFY_AND_CALL(X,weasel), which in turn expands to X("weasel"), which matches the form of the list entries that were expressed directly.
Parametric expression macros
Another thing you can do with an X-
For example, suppose I had a list macro defining a set of identifiers each with a specific value, and I wanted to test some input value against all of them in some way.
#define MAGIC_CONSTANTS(X) X(fizz, 3) X(buzz, 5)
If I only need to do the test at run time, then of course I can just use a standard X-
const char *optionally_replace(int input, const char *default_string)
{
#define TEST_AND_RETURN(name, val) \
if (input % val == 0) return #name;
MAGIC_CONSTANTS(TEST_AND_RETURN)
// expands to: if (input % 3 == 0) return "fizz";
// if (input % 5 == 0) return "buzz";
return default_string;
}
But if I want to generate an array at compile time –(input % value == 0), but where does it get input from?
From the extra parameter Y, of course:
#define XY_MAGIC_CONSTANTS(X,Y) X(Y, fizz, 3) X(Y, buzz, 5)
#define TEST_CLAUSE(input, name, val) input % val == 0 ? #name :
#define REPLACE(n) ( XY_MAGIC_CONSTANTS(TEST_CLAUSE, n) #n )
// expands to: (TEST_CLAUSE(n,"fizz",3) TEST_CLAUSE(n,"buzz",5) #n)
// -> (n % 3 == 0 ? "fizz" : n % 5 == 0 ? "buzz" : #n)const char *const names[] = {
REPLACE(1), REPLACE(2), REPLACE(3),
REPLACE(4), REPLACE(5), REPLACE(6)
// -> "1", "2", "fizz", "4", "buzz", "fizz"
};
Afterthoughts
Of course, this is very similar to a standard pattern used for callback functions in ordinary runtime C: it's common to define your callback function as taking a void * context parameter, and then whatever is going to call the callback, you pass it an argument pair (f, ctx) consisting of the function pointer and the context you want it to use, and then it calls f(ctx, stuff) repeatedly. But until this year I'd never seen a need to do the same thing at preprocessor time!
I don't doubt that in some situation with yet another level of indirection you'd need a list macro taking three parameters and evaluating X(Y,Z,foo), X(Y,Z,bar), and so on. Probably that would come up if you needed a second layer of modified list inclusion, or some such. But I haven't encountered it yet.