Enum to String (and other preprocessor tricks)

Author: thothonegan
Tags: c++11 enum preprocessor

Sometimes in code you want to expand out a list of identifiers multiple ways in different contexts, without having to type it out again. For example, you have an enum which you also want an 'enumToString' function, without having to retype every single item in the list. In C, theirs a really neat method to do this using the preprocessor. For this example, i'll assume we're making an enum of colors.

First, you define a macro for your list. In this case, i'll call it COLORS_LIST.

#define COLORS_LIST(XX) \
    XX(Red) \
    XX(Green) \
    XX(Blue)

Then you define a macro for the operations you want to do, and use that with the original macro. So for example, to make an enum:

enum Color
{
    #define ENUM_VALUE(name) \
        name,

    COLORS_LIST(ENUM_VALUE)
    // Expands out to "Red, Green, Blue,"
    // (and c++11 ignores the extra comma).
};

And for the 'convert enum to names', you can do something like:

const char* colorToString (Color param)
{
    #define RETURN_IF(name) \
        if (param == name) { return #name; }

    COLORS_LIST(RETURN_IF)

    // Expands to
    // if (param == Red) return "Red";
    // if (param == Blue) return "Blue;
    // if (param == Green) return "Green;

    return nullptr; // failure case
}

Basically whats happening, is you're passing a macro name (the operation macro) to your list macro. The list macro then calls the operation macro with each value in the list, allowing it to expand in any way it wants.

For advanced usage, you can even use this to fake enum attributes, somewhat similar to other languages (like java). So say we wanted every color to also have an html color associated with each. We would change our macros like:

// the macro XX now has to take two arguments
#define COLOR_LIST(XX) \
    XX(Red, "#ff0000") \
    XX(Green, "#00ff00") \
    XX(Blue, "#0000ff")

enum Color
{
    #define ENUM_VALUE(name, html) name,

    COLORS_LIST(ENUM_VALUE) // same as before
}

const char* colorToString (Color color)
{
    #define RETURN_IF(name, html) if (param == name) { return #name; }

    COLORS_LIST(RETURN_IF)
    return nullptr; // failed
}

const char* colorToHTMLColor (Color color)
{
    #define RETURN_IF(name, html) if (param == name) { return html; } // return the html color

    COLORS_LIST(RETURN_IF)
    return nullptr; // failed
}

In this case, all of the macros take two arguments, and decide which pieces of information they want to use. Very useful for long lists of stuff that you want to manipulate at compile time, instead of having to type all the attributes out, and hope you don't forget one.