Channels ▼

Walter Bright

Dr. Dobb's Bloggers

The X Macro

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  ) \
 X(auto, SCEXP|SCSS|SCRD    ) \
 X(static, SCEXP|SCKEP|SCSCT) \
 X(thread, SCEXP|SCKEP      ) \
 ...

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

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Comments:



Video