2

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

Rainer
  • 51
  • 5
  • You’re writing C++, so why aren’t you using templates? – Dai Aug 09 '21 at 09:57
  • @Dai Converting enum to string with templates is fairly obscure. – HolyBlackCat Aug 09 '21 at 09:59
  • can be done with X macros – Alex Guteniev Aug 09 '21 at 10:06
  • @Dai I not very familiar with templates. I've no idea how to realize this with templates, nor solve my problems with templates. If you can, show us how! – Rainer Aug 09 '21 at 10:07
  • https://www.boost.org/doc/libs/1_76_0/libs/preprocessor/doc/index.html will probably be helpful – Alan Birtles Aug 09 '21 at 10:07
  • For complex stuff like this, I would personally choose to use "proper" meta programming rather than macro meta programming. So, I recommend considering that option at least. – eerorika Aug 09 '21 at 10:10
  • @Alan Birtles Can you convert the example to BOOST, including my additional requested features? – Rainer Aug 09 '21 at 10:15
  • @Rainer I think you want a variadic template: for example: https://stackoverflow.com/questions/18996403/build-an-enum-using-variadic-template-parameters – Dai Aug 09 '21 at 10:18
  • @eeroika What do you mean with "proper" meta programming? Manually coding the features a each enumeration? I like the macro because I can use it to define very simply new numerations and corresponding output operators.(if a copy/paste/adapt the macro for enumerations with another number of values. – Rainer Aug 09 '21 at 10:18
  • @eerorika What constitutes “proper” metaprogramming in your opinion? – Dai Aug 09 '21 at 10:18
  • @Dai Write a program that writes a program. – eerorika Aug 09 '21 at 10:20
  • @Dai There is no tautology. You write a program. That program writes another program for you. – eerorika Aug 09 '21 at 10:22
  • @eerorika C++ Templates a Turing-complete programs in themselves. What about T4 Templates? LinqPad? And C-preprocessor macros are still “programs” that generate code, even if they have limits to their expressiveness. I argue that *all* meta-programming is “proper” metaprogramming. No program is “improper” if it works (unless you’re a stickler for nonfunctional testing…), therefore no metaprogram is “improper” either. – Dai Aug 09 '21 at 10:29
  • @Dai Why choose a "language" that has limited expressiveness when there is a world of more powerful languages available? – eerorika Aug 09 '21 at 10:34
  • @Dai I would argue that c++ macros and templates are part of the language itself, and thus don't constitute as "proper" meta programming. – eerorika Aug 09 '21 at 10:35

3 Answers3

2

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.

Wander Nauta
  • 18,832
  • 1
  • 45
  • 62
  • Nice solution. When I understood your comment it is not possible to define initial value for the enumerations. But this is essential for me because some of my enumerations have gaps. – Rainer Aug 09 '21 at 10:42
  • Ah, right - I thought that you didn't want to provide values at all. You could adapt the above to pass a sequence of day name/value tuples rather than a sequence of day names, and then use `BOOST_PP_TUPLE_ELEM` to pull either part out again... though note that this stuff tends to turn into spaghetti fairly quickly. – Wander Nauta Aug 09 '21 at 10:51
  • I make it. Replace two lines: #define ENUM_FIELD(I,_,F) BOOST_PP_TUPLE_ELEM(0,F)=BOOST_PP_TUPLE_ELEM(1,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; – Rainer Aug 09 '21 at 12:01
  • Is it possible to combile the two solutions? Like if number of Arguments in tuple is 1 than do older solution, if number of Arguments in tuple is 2 than to new solution. For ENUM_FIELD and for ENUM_OUTPUT_CASE – Rainer Aug 09 '21 at 12:03
  • Yep, that should be possible. You can ask for BOOST_PP_TUPLE_SIZE and then choose between macros based on that. – Wander Nauta Aug 09 '21 at 12:55
  • Can you show me at one of them how to use BOOST_PP_TUPLE_SIZE inside of a macro. (e.g. ENUM_FIELD) – Rainer Aug 09 '21 at 14:11
  • Here the code for ENUM_FIELD to enable optional definition of values. #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)), – Rainer Aug 10 '21 at 06:40
1

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)
Rainer
  • 51
  • 5
0

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.

Bart
  • 1,405
  • 6
  • 32