X macros are an idiomatic usage of programming language macros for generating list-like structures of data or code. They are most useful when at least some of the lists cannot be composed by indexing, such as compile time. They provide reliable maintenance of parallel lists whose corresponding items must be declared or executed in the same order.
Examples of such lists particularly include initialization of arrays, in concert with declarations of enumeration constants and function prototypes; generation of statement sequences and switch arms; etc.
Usage of X macros dates back to the 1960s. It remains useful in modern-day C and C++ programming languages, but remains relatively unknown.
Implementation
An X macro application consists of two parts:
- The definition of the list's elements.
- Expansion(s) of the list to generate fragments of declarations or statements.
The list is defined by a macro or header file (named, LIST
) which generates no code by itself, but merely consists of a sequence of invocations of a macro (classically named "X
") with the elements' data. Each expansion of LIST
is preceded by a definition of X
with the syntax for a list element. The invocation of LIST
expands X
for each element in the list.
Example 1
This example defines a list of variables, and automatically generates their declarations and a function to print them out.
First the list definition. The list entries could contain multiple arguments, but here only the name of the variable is used.
#define LIST_OF_VARIABLES \ X(value1) \ X(value2) \ X(value3)
Then we expand this list to generate the variable declarations:
#define X(name) int name; LIST_OF_VARIABLES #undef X
In a similar way, we can generate a function that prints the variables and their values:
void print_variables(void) { #define X(name) printf("%s = %d\n", #name, name); LIST_OF_VARIABLES #undef X }
When run through the C preprocessor, the following code is generated. Line breaks and indentation have been added for ease of reading, even though they are not actually generated by the preprocessor:
int value1; int value2; int value3; void print_variables(void) { printf("%s = %d\n", "value1", value1); printf("%s = %d\n", "value2", value2); printf("%s = %d\n", "value3", value3); }
Example 2 with X macro as argument
This example aims to improve the readability of the X macro usage by:
- Prefix the name of the macro that defines the list with "FOR_".
- Pass name of the worker macro into the list macro. This both avoids defining an obscurely named macro (X), and alleviates the need to undefine it.
- Use the syntax for variadic macro arguments "..." in the worker macros to be able to accept more arguments than needed. This enables the maintainer of the code to add columns to the list without having to update all the macro definitions.
- Use the name "DO" as the macro name, the argument to the list macro.
#define FOR_LIST_OF_VARIABLES(DO) \ DO(id1, name1) \ DO(id2, name2) \ DO(id3, name3) \
As above, execute this list to generate the variable declarations:
#define DEFINE_NAME_VAR(id, name, ...) int name; FOR_LIST_OF_VARIABLES( DEFINE_NAME_VAR )
or declare an enumeration:
#define DEFINE_ENUMERATION(id, name, ...) name = id, enum my_id_list_type { FOR_LIST_OF_VARIABLES( DEFINE_ENUMERATION ) }
In a similar way, we can generate a function that prints the variables and their names:
void print_variables(void) { #define PRINT_NAME_AND_VALUE(id, name, ...) printf("%s = %d\n", #name, name); FOR_LIST_OF_VARIABLES( PRINT_NAME_AND_VALUE ) }
Further reading
References
- Meyers, Randy. The New C: X Macros. Dr.Dobb's 2001.
- Bright, Walter. The X Macro. Digital Mars 2010
- Lucas, Andrew. Reduce C-language coding errors with X macros. Embedded.com 2013