Search code examples
c++enumsmacrosc++17

How to iterate over an enumeration with a ranged-for loop


#define DEFINE_ENUM_CLASS(Name, ...) \
enum class Name { __VA_ARGS__ }; \
constexpr const char* Name##Strings[] = { #__VA_ARGS__ };

int main()
{
    DEFINE_ENUM_CLASS(Color, Red, Green, Blue);
    DEFINE_ENUM_CLASS(Bolor, Blue, Cyan, Green);

    for (const char* colorName : ColorStrings)
        std::cout << colorName << std::endl;

    for (const char* bolorName : BolorStrings)
        std::cout << bolorName << std::endl;
}

right now printing is not in a function, I'm struggling to figure out how to make the behavior to print one of those for loops happen in a function.


Solution

  • We need a functional "map" macro, for converting enum tokens into strings, or prepending the enum name prefix to them. This macro is adapted from this SO post, and takes up most of the length of the program.

    #include <iostream>
    
    // 2-argument functional map with a fixed first argument
    
    #define EVAL0(...) __VA_ARGS__
    #define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__)))
    #define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
    #define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
    #define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
    #define EVAL(...)  EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
    
    #define MAP_END(...)
    #define MAP_OUT
    #define MAP_COMMA ,
    
    #define MAP_GET_END2() 0, MAP_END
    #define MAP_GET_END1(...) MAP_GET_END2
    #define MAP_GET_END(...) MAP_GET_END1
    #define MAP_NEXT0(test, next, ...) next MAP_OUT
    #define MAP_NEXT1(test, next) MAP_NEXT0(test, next, 0)
    #define MAP_NEXT(test, next)  MAP_NEXT1(MAP_GET_END test, next)
    
    #define MAP0(f, a, x, peek, ...) f(a,x) MAP_NEXT(peek, MAP1)(f, a, peek, __VA_ARGS__)
    #define MAP1(f, a, x, peek, ...) f(a,x) MAP_NEXT(peek, MAP0)(f, a, peek, __VA_ARGS__)
    
    #define MAP_LIST_NEXT1(test, next) MAP_NEXT0(test, MAP_COMMA next, 0)
    #define MAP_LIST_NEXT(test, next)  MAP_LIST_NEXT1(MAP_GET_END test, next)
    
    #define MAP_LIST0(f, a, x, peek, ...) f(a, x) MAP_LIST_NEXT(peek, MAP_LIST1)(f, a, peek, __VA_ARGS__)
    #define MAP_LIST1(f, a, x, peek, ...) f(a, x) MAP_LIST_NEXT(peek, MAP_LIST0)(f, a, peek, __VA_ARGS__)
    
    /**
     * Applies the function macro `f` to first argument `a` and each of the remaining parameters.
     */
    #define MAP_TWOARG(f, firstarg, ...) EVAL(MAP1(f, firstarg, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
    
    /**
     * Applies the function macro `f` to first argument `a` and each of the remaining parameters,
     * and inserts commas between the results.
     */
    #define MAP_TWOARG_LIST(f, firstarg, ...) EVAL(MAP_LIST1(f, firstarg, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
    
    #define PREFIX_AS_ENUM_CLASS(EnumName, EnumValue) EnumName::EnumValue
    #define STR_WITH_DUMMY(dummy,token) #token
    
    template <typename Enum>
    constexpr std::underlying_type_t<Enum> to_underlying(Enum x)
    {
        return static_cast<std::underlying_type_t<Enum>>(x);
    }
    
    #define DEFINE_ENUM_CLASS(Name, ...) \
    enum class Name { __VA_ARGS__ }; \
    constexpr const Name Name##Values[] = {  \
         MAP_TWOARG_LIST(PREFIX_AS_ENUM_CLASS, Name, __VA_ARGS__)  \
    }; \
    constexpr char const* Name##Strings[] = { MAP_TWOARG_LIST(STR_WITH_DUMMY, dummy, __VA_ARGS__) }; \
    constexpr char const* as_string(Name x, ...) \
    { \
        return Name##Strings[to_underlying(x)]; \
    }
    
    DEFINE_ENUM_CLASS(Color, Red, Green, Blue);
    DEFINE_ENUM_CLASS(Bolor, Blue, Cyan, Green);
    
    
    int main()
    {
        std::cout << "Colors:\n";
        for (auto color : ColorValues)
            std::cout << as_string(color) << std::endl;
        std::cout << "-----\nBolors:\n";
        for (auto bolor : BolorValues)
            std::cout << as_string(bolor) << std::endl;
    }
    

    And it works (GodBolt.org).


    Notes:

    • Instead of an array of all of the values, it's better to just define a maximum value and iterate from the minimum to the maximum. After all, this doesn't support skipping or repeating any values.
    • Something like this - and much better in fact - is already available in various souped-up enum libraries, e.g. "better enums" or "magic enum" mentioned by @heapunderrun. Probably better not to reinvent the wheel.
    • The code is valid C++14.