Search code examples
cc-preprocessorx-macros

Check existence of an entry in C pre-processor list


Is it possible to check for the existence of an entry in a list defined by X-macro? Given the example code below, I'd like the #if defined(GEORGE) condition to be true.

EDIT: without doing an explicit #define GEORGE, of course. I am hoping to have a way to check an entry in the list (in the pre-processor) and I only want to do the declaration in the list. It is likely not possible but thought I ask.

Thanks!

#include <stdio.h>

#define NAMES \
X( JOHN, "John Adams" ) \
X( GEORGE, "George Washington" ) \
X( ABRAHAM, "Abraham Lincoln ")

#define X(_id, _name)    _id,
typedef enum {
    NAMES
} names_e;
#undef X

typedef struct {
    char *name;
} names_t;

#define X(_id, _name)    [_id] = { .name = _name },
static names_t const names[] = {
    NAMES
};
#undef X

int main(void) {
    int i;

    for (i=0; i < sizeof(names)/sizeof(names[0]); i++) {
        printf("%s\n", names[i].name);
    }

    printf("names[ABRAHAM] = %s\n", names[ABRAHAM].name);

#if defined(GEORGE)
    printf("names[GEORGE] = %s\n", names[GEORGE].name);
#endif

    return 0;
}

Output

John Adams
George Washington
Abraham Lincoln 
names[ABRAHAM] = Abraham Lincoln 

Solution

  • You can try to use a preprocessor pattern matcher. The concept simply requires a SECOND macro with indirection; GLUE is nice to shove arbitrary prefixes on:

    #define GLUE(A,B) GLUE_I(A,B)
    #define GLUE_I(A,B) A##B
    
    #define SECOND(...) SECOND_I(__VA_ARGS__,,)
    #define SECOND_I(A,B,...) B
    

    ...and for an X macro "column" that contains only "pseudo-identifiers":

    #define NAMES \
    X( JOHN, "John Adams" ) \
    X( GEORGE, "George Washington" ) \
    X( ABRAHAM, "Abraham Lincoln ")
    

    ...you could do this:

    #define X(ID_, NAME_) SECOND(GLUE(SEARCH_FOR_,ID_),+0)
    #define SEARCH_FOR_GEORGE ,+1
    #if NAMES
    printf("names[GEORGE] = %s\n", names[GEORGE].name);
    #endif
    #undef SEARCH_FOR_GEORGE
    #define SEARCH_FOR_THOMAS ,+1
    #if NAMES
    #error Dewey wins!
    #endif
    #undef SEARCH_FOR_THOMAS
    #undef X
    

    This works because SECOND indirectly expands to the second argument; so SECOND(GLUE(SEARCH_FOR,ID_),+0) by default will expand to +0; and any chain of +0 is a valid false expression. But because the expansion's indirect, and the first argument is that pasted token, then if you define the pasted token SEARCH_FOR_GEORGE to itself expand with a comma in it, the expression after the comma becomes the new second argument. (Ask me if you need this to work on Microsoft VS preprocessors; that requires a tiny tweak).