c++variadicdefine-syntax

C++: Simplifiying a #define


I have a #define with generates a enum class and a corresponding output operator the the generated enum class.(see below)

#define ENUM(N, T, N1, V1, N2, V2, N3, V3, N4, V4, N5, V5, N6, V6, N7, V7)\
    enum class N : T {\
        N1 = V1,\
        N2 = V2,\
        N3 = V3,\
        N4 = V4,\
        N5 = V5,\
        N6 = V6,\
        N7 = V7,\
    };\
    std::ostream &operator <<(std::ostream &os, const N val);   /* declare function to avoid compiler warning */\
    std::ostream &operator <<(std::ostream &os, const N val) {\
        switch (val) {\
        case N::N1:\
            os << #N1;\
            break;\
        case N::N2:\
            os << #N2;\
            break;\
        case N::N3:\
            os << #N3;\
            break;\
        case N::N4:\
            os << #N4;\
            break;\
        case N::N5:\
            os << #N5;\
            break;\
        case N::N6:\
            os << #N6;\
            break;\
        case N::N7:\
            os << #N7;\
            break;\
        }\
        if (sizeof(T) == 1) {\
            os << '(' << static_cast<int>(val) << ')';\
        } else {\
            os << '(' << static_cast<T>(val) << ')';\
        }\
        return os;\
    }

It can be used like here in this example:

#include <cstdlib>
#include <iostream>
#include <ostream>

ENUM(Weekdays, unsigned char, Monday, 10, Tuesday, 12, Wednesday, 14, Thursday, 16, Friday, 18, Saterday, 100, Sunday, 101)

int main(const int /*argc*/, const char *const /*argv*/[]) {
    Weekdays    test    = Weekdays::Monday;

    std::cout << test << std::endl;
    std::cout << Weekdays::Tuesday << std::endl;
    std::cout << Weekdays::Sunday << std::endl;

    return EXIT_SUCCESS;
}

here the generated output:

Monday(10)
Tuesday(12)
Sunday(101)

My solution has some restrictions:

For a more generalized usage I have two questions. Especially the second one would increase the usability enormously.

Any here my questions:

  1. How can I avoid to define a initialization value for each enumeration value?
    (like in a real enumeration)
  2. Any ideas to generalize the #define to work with any number of values?

I'm waiting for your comments to my code and suggestions for improvement.
Rainer


Solution

  • Sticking relatively close what you have right now, you can take advantage of the BOOST_PP_SEQ_FOR_EACH macro from Boost.Preprocessor, which could look something like this:

    #include <boost/preprocessor.hpp>
    
    #define ENUM_FIELD(I,_,F) F,
    #define ENUM_OUTPUT_CASE(I,N,F) case N::F: os << BOOST_PP_STRINGIZE(F); break;
    
    #define ENUM(N, T, ARGS) \
    enum class N : T {\
    BOOST_PP_SEQ_FOR_EACH(ENUM_FIELD,N,ARGS)\
    };\
    std::ostream &operator <<(std::ostream &os, const N val) {\
        switch (val) {\
        BOOST_PP_SEQ_FOR_EACH(ENUM_OUTPUT_CASE,N,ARGS)\
        }\
        \
        os << '(' << static_cast<int>(val) << ')';\
        return os;\
    }
    
    ENUM(Weekdays, unsigned char, (Monday)(Tuesday)(Wednesday)(Thursday)(Friday)(Saturday)(Sunday))
    

    That removes both the duplication and possibility of supplying the values. The whole thing is shorter, arguably at the cost of making it less readable and potentially harder to debug – I won't weigh in on the pro's and cons of using macros like these.

    Note that I've changed the way arguments are passed to the ENUM macro: this is now a Boost.Preprocessor sequence. You should be able to pass up to 256 elements; see the documentation for more information and more macros that work on sequences.