0

In some other langauges you can specify enums along with states, eg.:

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);
   ...

This isn't hard to emulate in - but only it easily becomes somewhat verbose.

I know this has been asked before and similar questions exists, e.g. in Build an enum using variadic template parameters, but I wanted to ask this again and have slightly different perspective.

Consider the following 'hand-written' approach:

#pragma warning(error : 4062)    

enum tvtype {
    black_and_white,
    color
};    

struct TvType {
    tvtype tvtype_;
    constexpr TvType(tvtype type) : tvtype_{type} {

    }
    constexpr operator tvtype() const {
        return tvtype_;
    }
};

constexpr TvType BlackAndWhite(black_and_white);
constexpr TvType Color(color);

bool hasColor(TvType tv) {
    switch (tv) {
        case BlackAndWhite:
        return false;
        //case Color:
        //return true;
    };

    return false;
}

Try it yourself (comment in the two lines in the switch to make it compile)

This method is working well - only it is somewhat verbose.

Using this method it should now be possible to store some extra information along with the enum value, like a string representation, perhaps adding functions as well, etc.

Also because it's actually an enum underneath, it's possible for the compiler to check that all cases have been checked in the switch (the example above should generates a compile error due that the 'color' case is outcommented).

I'm interested in a generic / templated way of getting the enum generated or something similar that fulfills the requirement of compiler checked switch and extensibility.

But in order to templatize the above, it seems the enum should be generated in a (compile-time) programatical way, which leads to the question:

Is it possible to create an enum from a fold expression for instance or failing that from some kind of recursive template ?

darune
  • 10,480
  • 2
  • 24
  • 62
  • 1
    I don't understand what you want. What do you want to do that the other answer does not provide? – n314159 Jan 03 '20 at 11:20
  • You don't really have any direct use of the `enum` if you're going to wrap it in a class. You could just use plain `int` values. Your example shows no indication on how you are planning to solve the "check that all cases have been checked" scenario. This question needs more focus and clarity. – super Jan 03 '20 at 11:41
  • @n314159 basicly java-like enums with the extra security for switch. The only solutions ive seen so far is either using int (which isn't an enum and then switch can't be checked) or type traits like design where you need to pre-write a specialization for each 'size' of the enum. – darune Jan 03 '20 at 11:43
  • @super try following link - you will see a compiler error. If you comment in the two lines in the switch the code will compile since now the switch checks all cases of the enum - this is not possible with an int – darune Jan 03 '20 at 11:45
  • @darune well, I don't know how enums in Java work. Think about what you want to achieve exactly, for what you need help and write it down. – n314159 Jan 03 '20 at 11:49
  • @darune That's not really a hard-error. You are allowed to make a switch statement that does not consider all enum values. Different compilers can be made to report errors/warnings but I'm not sure if that is consistent between all the 3 big compilers even. Anyway, it should probably be specified in your question for clarity. – super Jan 03 '20 at 11:50
  • @super it was already there, I highlighted it now though – darune Jan 03 '20 at 11:56
  • @darune That is very far from being clear. You are talking about compiler-specific behaviour. If you really want an answer that is based on implementation details of the compiler you need to be very clear about that fact and you also need to be clear about which compiler you are talking and under which circumstances you plan to use it. – super Jan 03 '20 at 11:57
  • @super perhaps you are right, but I believe other mainstream compilers can check this too (gcc and clang), don't you think ? - I just happen to know how to turn this warning into an error in MSVC. Anyway feel free to help me clarify the post how you see fit :) – darune Jan 03 '20 at 12:03
  • Ok, I changed the question a lot now, please help me reopen. – darune Jan 03 '20 at 12:10
  • Let me know what you find unclear – darune Jan 03 '20 at 13:20

1 Answers1

1

Is it possible to create an enum from a fold expression for instance or failing that from some kind of recursive template ?

Not with variadic templates. Template arguments can be types, or constant values. You can't substitute a symbol to stick it inside an enum block.

However, you can do that with macros. Only trouble is, while C++11 has something called "variadic macros", it's very limited.

#define MY_MACRO(...) enum { __VA_ARGS__ }

You can't do any kind of transformation on variadic arguments like you can with templates, it literally just replaces __VA_ARGS___ with all the tokens the ... represents.

But, using the technique from this great answer: https://stackoverflow.com/a/11763277/1863938 , you can support "up to N" variadic solutions. Using that, you can create a macro that templates your boilerplate class for up to N values. Below is for up to 4 symbols:

#define _JENUM_GET_MACRO(_1,_2,_3,_4,NAME,...) NAME

#define _JENUM_VALUE_DEF1(_1) _##_1##_v_
#define _JENUM_VALUE_DEF2(_1,_2) _JENUM_VALUE_DEF1(_1), _JENUM_VALUE_DEF1(_2)
#define _JENUM_VALUE_DEF3(_1,_2,_3) _JENUM_VALUE_DEF1(_1), _JENUM_VALUE_DEF1(_2), _JENUM_VALUE_DEF1(3)
#define _JENUM_VALUE_DEF4(_1,_2,_3,_4) _JENUM_VALUE_DEF1(_1), _JENUM_VALUE_DEF1(_2), _JENUM_VALUE_DEF1(3), _JENUM_VALUE_DEF1(_4)
#define _JENUM_VALUE_DEF(...) _JENUM_GET_MACRO(__VA_ARGS__, _JENUM_VALUE_DEF4, _JENUM_VALUE_DEF3, _JENUM_VALUE_DEF2, _JENUM_VALUE_DEF1)(__VA_ARGS__)
#define _JENUM_ENUM_DEF(name, ...) enum _##name##_e_ { _JENUM_VALUE_DEF(__VA_ARGS__) };

#define _JENUM_CLASS_DEF(name) struct name {\
    _##name##_e_ _value;\
    constexpr name(const name& that) : _value(that._value) { }\
    constexpr name(_##name##_e_ _value) : _value(_value) { }\
    constexpr operator _##name##_e_() const { return _value; }\
};\

#define _JENUM_CONST_DEF1(name, _1) constexpr name _1(_JENUM_VALUE_DEF1(_1));
#define _JENUM_CONST_DEF2(name, _1,_2) _JENUM_CONST_DEF1(name, _1) _JENUM_CONST_DEF1(name, _2)
#define _JENUM_CONST_DEF3(name, _1,_2,_3) _JENUM_CONST_DEF1(name, _1) _JENUM_CONST_DEF1(name, _2) _JENUM_CONST_DEF1(name, _3)
#define _JENUM_CONST_DEF4(name, _1,_2,_3,_4) _JENUM_CONST_DEF1(name, _1) _JENUM_CONST_DEF1(name, _2) _JENUM_CONST_DEF1(name, _3) _JENUM_CONST_DEF1(name, _4)
#define _JENUM_CONST_DEF(name, ...) _JENUM_GET_MACRO(__VA_ARGS__, _JENUM_CONST_DEF4, _JENUM_CONST_DEF3, _JENUM_CONST_DEF2, _JENUM_CONST_DEF1)(name, __VA_ARGS__)

#define JENUM(name, ...)\
    _JENUM_ENUM_DEF(name, __VA_ARGS__)\
    _JENUM_CLASS_DEF(name)\
    _JENUM_CONST_DEF(name, __VA_ARGS__)

... just follow the pattern to add more overloads

Then, to use:

JENUM(TvType, BlackAndWhite, Color);

Now, if you want to add methods and whatnot, it's easier to just inherit than to try and work anything into the macro:

struct TvTypeEx : public TvType {
    using TvType::TvType;
    TvTypeEx(TvType that) : TvType(that) { }
    bool hasColor() const { return *this == Color; }
};

Now you can do stuff like:

TvTypeEx tv = Color;
return tv.hasColor() ? GetColorContent() : GetBlackAndWhiteContent();

Demo: https://godbolt.org/z/rdqS65

parktomatomi
  • 3,851
  • 1
  • 14
  • 18