views:

553

answers:

2

A basic definition and example and a few references for "X-Macros" is given in this wikipedia entry on the C pre-processor:

An X-Macro is a header file (commonly using a ".def" extension instead of the traditional ".h") that contains a list of similar macro calls (which can be referred to as "component macros").

What are some good sources of information on how to use this powerful technique? Are there well-known open source libraries using this method?

+2  A: 

Dr. Dobb's has an article on this.

qrdl
+5  A: 

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]\n", 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\n",value);
         break;
      default:
         printf("Invalid error: %d\n",value);
         break;
  }
JayG
This is awesome.
SetJmp
Very, very clever -- kept out of the clutches of evil, this will improve both maintainability and readability. Thanks, JayG!
Adam Liss