digitalmars.com                        
Last update Sat Oct 7 16:49:28 2023

The X Macro

written by Walter Bright

June 24, 2010

I learned this technique over 30 years ago from friends of mine in college. It was for assembler language macros, but survived the transition to C macros easily. I’ve used it regularly since, but interestingly have never seen anyone else use it. That makes it my turn to pass along this little gem.

To use a hackneyed example, let’s have a header file color.h, and in that there’s an enum for the colors:

enum Color { Cred, Cblue, Cgreen };

In the corresponding source file color.c, in order to pretty print colors, there’s a corresponding array of strings:

static char *ColorStrings[] =
    ["red", "blue", "green"];

which we can use like:

enum Color c;
...
printf("the color is %s\n", ColorStrings[c]);

So far, so good. Time goes on, and another color is added:

enum Color { Cred, Cyellow, Cblue, Cgreen };

and yes, we forget to update the ColorStrings[] array, and not only does printing out Cyellow come out as "blue", even worse we have an array overflow printing out the string for Cgreen. (You’re a smart programmer and would never make such a mistake, right?)

The trouble is there’s no semantic connection between the enum and the array. The usual way to deal with this is to add a pack of unit tests. But wouldn’t it be better if we could find a way to connect the dots at compile time? Is this a job for — The X Macro — ?

#define COLORS \
    X(Cred, "red") \
    X(Cblue, "blue") \
    X(Cgreen, "green")

Put this in color.h. Then, in the place where the enum is declared:

#define X(a, b) a,
enum Color { COLORS };
#undef X

You can see where this is going. In the source file color.c:

#define X(a, b) b,
static char *ColorStrings[] = [ COLORS ];
#undef X

In other words, we redefine the X macro as necessary to extract the bit of information needed at the moment and ignore the rest. Proper macro hygiene is maintained because the #define of X will complain if X is already defined, and the #undef will ensure it doesn’t muck up any later X.

Adding a new color becomes trivial:

#define COLORS \
    X(Cred, "red") \
    X(Cyellow, "yellow") \
    X(Cblue, "blue") \
    X(Cgreen, "green")

and the enum and array magically get updated automatically. The more experienced programmers will immediately see this can be made more sophisticated:

#define COLORS \
    X(red) \
    X(blue) \
    X(green)

#define X(a) C##a,
enum Color { COLORS };
#undef X

#define X(a) #a,
static char *ColorStrings[] = [ COLORS ];
#undef X

One real example is in the Digital Mars C++ compiler front end:

#define ENUMSCMAC       \
 X(unde, SCEXP|SCKEP|SCSCT)   /* undefined */ \
 X(auto, SCEXP|SCSS|SCRD  )   /* automatic (stack) */ \
 X(static, SCEXP|SCKEP|SCSCT) /* statically allocated */ \
 X(thread, SCEXP|SCKEP      ) /* thread local */ \
 ...

and 3 separate but parallel constructions are built — the enum, the string table for pretty printing, and the characteristics array (from the second argument). The most complex example I use it for has 6 arguments to the X macro, which builds enums, struct initializers, and runtime initializers.

Of course, you may already be using a macro or variable named X, and X is hardcoded in the macro body. Andrei Alexandrescu suggests the following improvement where the X macro is itself a parameter:

#define FOR_ALL_COLORS(apply) \
    apply(red) \
    apply(blue) \
    apply(green)

And later:

#define SELECT_STRING(a) #a,
static char *ColorStrings[] =
    [ FOR_ALL_COLORS(SELECT_STRING) ];
#undef SELECT_STRING 

The X Macro technique works with any language with a half-decent text macro preprocessor, and C’s is certainly up to the task. Use it and pass it along, as my friends were kind enough to pass it along to me.

(Like I said, I’ve never seen this used by others in the last 30 years. Is it really so obscure? Do you already use it? Post a comment!)

Update

Since I originally wrote this, more information about the X Macro has come to light. See Wikipedia: The X Macro.

Home | Runtime Library | IDDE Reference | STL | Search | Download | Forums