Search code examples
cc-preprocessorx-macros

X-Macro with concatenation parameter


I am using X-Macro to create enumerations using the following code

#define WIDGET_OFFSETS                                                               \
     X_WIDGET_OFFSET(OFFSET_HEIGHT_WA, 1, OFFSET_HEIGHT_WB, 2, OFFSET_HEIGHT_WC, 3)  \
     X_WIDGET_OFFSET(OFFSET_WIDTH_WA,  1, OFFSET_WIDTH_WB,  2, OFFSET_WIDTH_WC,  3)  \
     X_WIDGET_OFFSET(OFFSET_LENGTH_WA, 1, OFFSET_LENGTH_WB, 2, OFFSET_LENGTH_WC, 3)


#define X_WIDGET_OFFSET(a, b, c, d, e, f) a = b,
typedef enum { WIDGET_OFFSETS } Offsets_WA_e;
#undef X_WIDGET_OFFSET

I would like to generalize this a bit more such that the X-Macro table only have four parameters. The first parameter is the general/base name then have it concatenation with additional parameter. I have defined the macros as the following which is trying to accomplish the code just shown.

#define WIDGET_OFFSETS                         \
     X_WIDGET_OFFSET(OFFSET_HIEGHT_, 1, 2, 3)  \
     X_WIDGET_OFFSET(OFFSET_WIDTH_,  1, 2, 3)  \
     X_WIDGET_OFFSET(OFFSET_LENGTH_, 1, 2, 3)

#define EXPAND_NAME(x,y)   x##y
#define EXPAND_ENUM_TYPE(z) typedef enum { WIDGET_OFFSETS } Offsets_##z_e;

#define X_WIDGET_OFFSET(a, b, c, d) EXPAND_NAME(a,WA) = b,
EXPAND_ENUM_TYPE(WA)
#undef X_WIDGET_OFFSET

The compiler provides me with the following error message:

error: expected identifier before ‘(’ token

So, it seems my problem is at the "EXPAND_NAME(a, WA) = b," expression. I have tried a couple of different approaches, but none work so far. Lastly and ideally, I would like to only specify "WA", "WB" and "WC" once and not twice as the code here does.

I did find a similar question, Concatenate multiple tokens for X macro, but not quite what I would like to do, if possible. Any insight to what I am doing wrong is greatly appreciated. Thanks.

Edit: Corrected the first X-Macro definition as it contained copy/paste errors


Solution

  • Fix first code block

    Your first example code should be using WIDTH and LENGTH consistently in the third and fourth lines of the macro:

    #define WIDGET_OFFSETS                                                               \
         X_WIDGET_OFFSET(OFFSET_HEIGHT_WA, 1, OFFSET_HEIGHT_WB, 2, OFFSET_HEIGHT_WC, 3)  \
         X_WIDGET_OFFSET(OFFSET_WIDTH_WA,  1, OFFSET_WIDTH_WB,  2, OFFSET_WIDTH_WC,  3)  \
         X_WIDGET_OFFSET(OFFSET_LENGTH_WA, 1, OFFSET_LENGTH_WB, 2, OFFSET_LENGTH_WC, 3)
    
    #define X_WIDGET_OFFSET(a, b, c, d, e, f) a = b,
    typedef enum { WIDGET_OFFSETS } Offsets_WA_e;
    #undef X_WIDGET_OFFSET
    #undef WIDGET_OFFSETS
    

    With noise lines and repeated blank lines omitted, that yields:

    typedef enum { OFFSET_HEIGHT_WA = 1, OFFSET_WIDTH_WA = 1, OFFSET_LENGTH_WA = 1, } Offsets_WA_e;
    

    Fix second code block

    Your second example had problems, notably it used Offsets_ ## z_e in place of the Offsets_ ## z ## _e shown below. It is also usually best to leave semicolons off the end of a macro definition and to leave them to appear after the macro invocation. That's a guideline; there are exceptions aplenty.

    #define WIDGET_OFFSETS                        \
         X_WIDGET_OFFSET(OFFSET_HEIGHT, 1, 2, 3)  \
         X_WIDGET_OFFSET(OFFSET_WIDTH,  1, 2, 3)  \
         X_WIDGET_OFFSET(OFFSET_LENGTH, 1, 2, 3)
    
    #define EXPAND_NAME(x,y)   x ## _ ## y
    #define EXPAND_ENUM_TYPE(z) typedef enum { WIDGET_OFFSETS } Offsets_ ## z ## _e
    
    #define X_WIDGET_OFFSET(a, b, c, d) EXPAND_NAME(a,WA) = b,
    EXPAND_ENUM_TYPE(WA);
    #undef X_WIDGET_OFFSET
    
    #define X_WIDGET_OFFSET(a, b, c, d) EXPAND_NAME(a,WB) = c,
    EXPAND_ENUM_TYPE(WB);
    #undef X_WIDGET_OFFSET
    
    #define X_WIDGET_OFFSET(a, b, c, d) EXPAND_NAME(a,WC) = d,
    EXPAND_ENUM_TYPE(WC);
    #undef X_WIDGET_OFFSET
    
    #undef WIDGET_OFFSETS
    #undef EXPAND_ENUM
    #undef EXPAND_NAME
    

    That produced:

    typedef enum { OFFSET_HEIGHT_WA = 1, OFFSET_WIDTH_WA = 1, OFFSET_LENGTH_WA = 1, } Offsets_WA_e;
    
    typedef enum { OFFSET_HEIGHT_WB = 2, OFFSET_WIDTH_WB = 2, OFFSET_LENGTH_WB = 2, } Offsets_WB_e;
    
    typedef enum { OFFSET_HEIGHT_WC = 3, OFFSET_WIDTH_WC = 3, OFFSET_LENGTH_WC = 3, } Offsets_WC_e;
    

    Avoid repeating the suffixes

    Your concern is that the suffixes WA, WB and WC are repeated in the code, and you'd rather not repeat yourself like that. It is possible to work around it. One way of doing it is to pass an argument to the WIDGET_OFFSETS macro, like this:

    #define WIDGET_OFFSETS(sx)                    \
         X_WIDGET_OFFSET(EXPAND_NAME(OFFSET_HEIGHT, sx), 1, 2, 3)  \
         X_WIDGET_OFFSET(EXPAND_NAME(OFFSET_WIDTH,  sx), 1, 2, 3)  \
         X_WIDGET_OFFSET(EXPAND_NAME(OFFSET_LENGTH, sx), 1, 2, 3)
    
    #define EXPAND_NAME(x,y)   x ## _ ## y
    #define EXPAND_ENUM_TYPE(z) typedef enum { WIDGET_OFFSETS(z) } Offsets_ ## z ## _e
    
    #define X_WIDGET_OFFSET(a, b, c, d) a = b,
    EXPAND_ENUM_TYPE(WA);
    #undef X_WIDGET_OFFSET
    
    #define X_WIDGET_OFFSET(a, b, c, d) a = c,
    EXPAND_ENUM_TYPE(WB);
    #undef X_WIDGET_OFFSET
    
    #define X_WIDGET_OFFSET(a, b, c, d) a = d,
    EXPAND_ENUM_TYPE(WC);
    #undef X_WIDGET_OFFSET
    
    #undef WIDGET_OFFSETS
    #undef EXPAND_ENUM
    #undef EXPAND_NAME
    

    This yields the following, which is the same as before, but the WA, WB, and WC only appears once each in the source.

    typedef enum { OFFSET_HEIGHT_WA = 1, OFFSET_WIDTH_WA = 1, OFFSET_LENGTH_WA = 1, } Offsets_WA_e;
    
    typedef enum { OFFSET_HEIGHT_WB = 2, OFFSET_WIDTH_WB = 2, OFFSET_LENGTH_WB = 2, } Offsets_WB_e;
    
    typedef enum { OFFSET_HEIGHT_WC = 3, OFFSET_WIDTH_WC = 3, OFFSET_LENGTH_WC = 3, } Offsets_WC_e;