C++: Simplifiying a #define

268 Views Asked by At

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:

  • each enumeration needs an initialization value
  • fixed to 7 enumeration values

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

3

There are 3 best solutions below

7
Wander Nauta On BEST ANSWER

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.

0
Bart On

If you do not need to know this compile time you could use a library like Protobuf to define your enums in. Protobuf in C++ supports Enum Descriptors which can be used as a form of reflections. These two posts describe possible solutions with Protobuf (1 and 2).

Edit: I forgot there is another library that might be of use to you if yo need it compile time. It is called Frozen and provides compile time maps. You might be able to generate some code defining the map and use it to convert enum values to strings.

0
Rainer On

I made it working for me. Some special features have been added:

  • manipulator to switch on/off outputting the value of the enums
    (in brackets behind the enum)
  • output for illegal value
    (should not happen: see code for possible occurence)

Here my complete solution:

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

#include <boost/preprocessor.hpp>
#include <boost/preprocessor/tuple/elem.hpp>

class EnumShowValue {
private:
    static bool showValueFlag;
public:
    explicit EnumShowValue(const bool flag) { EnumShowValue::showValueFlag  = flag; }

    static bool showValue() { return EnumShowValue::showValueFlag; }
};
bool    EnumShowValue::showValueFlag    = false;

inline std::ostream &operator <<(std::ostream &os, const EnumShowValue &) { return os; }

#define ENUM_FIELD(I,_,F)\
    BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(F),2),\
                    BOOST_PP_TUPLE_ELEM(0,F)=BOOST_PP_TUPLE_ELEM(1,F),\
                    BOOST_PP_TUPLE_ELEM(0,F)),

#define ENUM_OUTPUT_CASE(I,N,F)\
    case N::BOOST_PP_TUPLE_ELEM(0,F):\
        os << BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(0,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);\
std::ostream &operator <<(std::ostream &os, const N val) {\
    switch (val) {\
    BOOST_PP_SEQ_FOR_EACH(ENUM_OUTPUT_CASE,N,ARGS)\
    default:\
        os << "illegal value: " << BOOST_PP_STRINGIZE(N);\
        if (!EnumShowValue::showValue()) {\
            os << '(';\
            if (sizeof(T) == 1) {\
                os << static_cast<int>(val);\
            } else {\
                os << static_cast<T>(val);\
            }\
            os << ')';\
        }\
    }\
    if (EnumShowValue::showValue()) {\
                    os << '(';\
                    if (sizeof(T) == 1) {\
                        os << static_cast<int>(val);\
                    } else {\
                        os << static_cast<T>(val);\
                    }\
                    os << ')';\
    }\
    return os;\
}

ENUM(Weekdays, unsigned char, ((Monday, 101))((Tuesday))((Wednesday))((Thursday))((Friday))((Saturday, 200))((Sunday)))

int main(const int /*argc*/, const char *const /*argv*/[]) {

    std::cout << Weekdays::Monday << std::endl;
    std::cout << Weekdays::Tuesday << std::endl;
    std::cout << Weekdays::Wednesday << std::endl;
    std::cout << Weekdays::Thursday << std::endl;
    std::cout << Weekdays::Friday << std::endl;
    std::cout << Weekdays::Saturday << std::endl;
    std::cout << Weekdays::Sunday << std::endl;
    std::cout << Weekdays(99) << std::endl;

    std::cout << EnumShowValue(true);
    std::cout << Weekdays::Monday << std::endl;
    std::cout << Weekdays::Tuesday << std::endl;
    std::cout << Weekdays::Wednesday << std::endl;
    std::cout << Weekdays::Thursday << std::endl;
    std::cout << Weekdays::Friday << std::endl;
    std::cout << Weekdays::Saturday << std::endl;
    std::cout << EnumShowValue(false);
    std::cout << Weekdays::Sunday << std::endl;
    std::cout << Weekdays(-1) << std::endl;

    return EXIT_SUCCESS;
}

and the corresponding output:

Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
illegal value: Weekdays(99)
Monday(101)
Tuesday(102)
Wednesday(103)
Thursday(104)
Friday(105)
Saturday(200)
Sunday
illegal value: Weekdays(255)