I use X Macros() in code a lot. The value comes from only adding new data only to the "X list" and not modifying any other code.
The most common use of X Macros() is for associating error text with error codes. When new error codes are added, programmers must remember to add the code and the text, typically in separate places. The X Macro allows the new error data to be added in a single place and get automatically populated anywhere it is needed.
Unfortunately, the mechanisms use a lot of pre-compiler magic that can make the code somewhat hard to read (e.g. string joining with token1##token2
, string creation with #token
). Because of this I typically explain what the X Macro is doing in the comments.
Here is an example using the error/return values. All new data gets added to the "X_ERROR
" list. None of the other code hast to be modified.
/*
* X Macro() data list
* Format: Enum, Value, Text
*/
#define X_ERROR
X(ERROR_NONE, 1, "Success")
X(ERROR_SYNTAX, 5, "Invalid syntax")
X(ERROR_RANGE, 8, "Out of range")
/*
* Build an array of error return values
* e.g. {0,5,8}
*/
static int ErrorVal[] =
{
#define X(Enum,Val,Text) Val,
X_ERROR
#undef X
};
/*
* Build an array of error enum names
* e.g. {"ERROR_NONE","ERROR_SYNTAX","ERROR_RANGE"}
*/
static char * ErrorEnum[] = {
#define X(Enum,Val,Text) #Enum,
X_ERROR
#undef X
};
/*
* Build an array of error strings
* e.g. {"Success","Invalid syntax","Out of range"}
*/
static char * ErrorText[] = {
#define X(Enum,Val,Text) Text,
X_ERROR
#undef X
};
/*
* Create an enumerated list of error indexes
* e.g. 0,1,2
*/
enum {
#define X(Enum,Val,Text) IDX_##Enum,
X_ERROR
#undef X
IDX_MAX /* Array size */
};
void showErrorInfo(void)
{
int i;
/*
* Access the values
*/
for (i=0; i<IDX_MAX; i++)
printf(" %s == %d [%s]
", ErrorEnum[i], ErrorVal[i], ErrorText[i]);
}
You can also use X Macros() to generate code. For example to test if an error value is "known", the X Macro can generate cases in a switch statement:
/*
* Test validity of an error value
* case ERROR_SUCCESS:
* case ERROR_SYNTAX:
* case ERROR_RANGE:
*/
switch(value)
{
#define X(Enum,Val,Text) case Val:
X_ERROR
#undef X
printf("Error %d is ok
",value);
break;
default:
printf("Invalid error: %d
",value);
break;
}