Skeen
Skeen

Reputation: 4722

Combine enums c++

In my project, I have several enum declarations alike this one;

enum Comparison
{
    LT,     // "<"
    GT,     // ">"
    EQ,     // "=="
    LTEQ,   // "<="
    GTEQ,   // ">="
    NEQ     // "!="
};
enum Arithmetic
{
    ADD,    // "+"
    SUB,    // "-"
    MUL,    // "*"
    DIV,    // "/"
    MOD,    // "%"
};

And I'd like to combine several of these, into a single combined enum, such that;

Like this:

enum Comparison
{
    LT,     // "<"
    GT,     // ">"
    EQ,     // "=="
    LTEQ,   // "<="
    GTEQ,   // ">="
    NEQ     // "!="

    ADD,    // "+"
    SUB,    // "-"
    MUL,    // "*"
    DIV,    // "/"
    MOD,    // "%"
};

Also what I'd like to be able to do, is to 'cast' the combined enum, to one of the original ones, given the value in the combined enum only (should be trivial assuming the values are consistent).

An alternative to enum, is a class based solution, where the classes implement the operator int() operator.

Note; I do believe the operator int() is somehow the way to go.

Upvotes: 15

Views: 18189

Answers (8)

Mooing Duck
Mooing Duck

Reputation: 66952

What I've commonly seen is this:

enum OperationType {
    Comparison = 0x100,
    Arithmetic = 0x200
};        

enum ComparisonType
{
    LT = Comparison,     // "<"
    GT,     // ">"
    EQ,     // "=="
    LTEQ,   // "<="
    GTEQ,   // ">="
    NEQ     // "!="
};
enum ArithmeticType
{
    ADD = Arithmetic,    // "+"
    SUB,    // "-"
    MUL,    // "*"
    DIV,    // "/"
    MOD,    // "%"
};

Which gives you a little more flexibility than simple chaining, because now you can add comparisons without disrupting your Arithmetics, and the Arithmetics and Comparisons don't need to know about eachother. It also becomes trivial to get the type of an enum:

constexpr OperationType getOperationType(unsigned value)
{return static_cast<OperationType>(value&0xFF00);}

Upvotes: 26

JustinC
JustinC

Reputation: 61

Since an enum is really an int, you can wrap an int with a struct and use methods that cast to/from either enum type. Adding guards to the enums also helps with validating and converting back from an int.

enum OperationType {
    COMPARISON_OP = 0x100,
    ARITHMETIC_OP = 0x200
};

enum ComparisonType {
    UNKNOWN_COMPARISON = 0,
    LT = COMPARISON_OP,     // "<"
    GT,     // ">"
    EQ,     // "=="
    LTEQ,   // "<="
    GTEQ,   // ">="
    NEQ,    // "!="
    END_COMPARISON
};

enum ArithmeticType {
    UNKNOWN_ARITHMETIC = 0,
    ADD = ARITHMETIC_OP,    // "+"
    SUB,    // "-"
    MUL,    // "*"
    DIV,    // "/"
    MOD,    // "%"
    END_ARITHMETIC
};

struct Comparison {
    int value;

    Comparison(ComparisonType val) : value((int)val) { }
    Comparison(ArithmeticType val) : value((int)val) { }
    Comparison& operator=(ComparisonType val) {
        value = (int)val;
        return *this;
    }
    Comparison& operator=(ArithmeticType val) {
        value = (int)val;
        return *this;
    }

    ComparisonType get_comparison() const {
        if (value >= COMPARISON_OP && value < END_COMPARISON)
            return (ComparisonType)value;
        return UNKNOWN_COMPARISON;
    }
    ArithmeticType get_arithmetic() const {
        if (value >= ARITHMETIC_OP && value < END_ARITHMETIC)
            return (ArithmeticType)value;
        return UNKNOWN_ARITHMETIC;
    }
};

You can then compare the struct value with enum constants:

Comparison cmp = ADD;
switch (cmp.value) {
    case LT:
        ...
    case ADD:
        ...
}

Upvotes: 1

5Volt
5Volt

Reputation: 36

So I've recently done something somewhat similar with the preprocessor, I know this answer is coming about 2 years late, but still.

Basically I have various non-contiguously defined enumerated types which I want to be able to extend from eachother so i have written the following preprocessor directives:

#define START_ENUM(name,extends)\
namespace name##_ns {\
enum name\
{ BASE = extends::LAST  + 1
#define DEF_ENUM(name) , name
#define END_ENUM(name) \
,LAST\
};};\
using namespace name##_ns;

The enums are created in their own namespaces to avoid multiple definitions of LAST and BASE. You could switch them out for enum classes if you don't like the namespace pollution, but it makes casting back and forth to unsigned ints way more of a hassle later on.

You need to define a base enumerator for successive enums to extend, but it can just be empty, depending on your preference for style

enum class base_action {BASE = 0, LAST = 0}

Subsequent enums can be declared with the directives

START_ENUM(comparison, base_enum)
DEF_ENUM(LT)
DEF_ENUM(GT)
...
END_ENUM(comparison)

START_ENUM(arithmetic, comparison)
DEF_ENUM(ADD)
...
END_ENUM(arithmetic)

It's just some syntactic sugar to create chained enums.

To combine all of these enums, you will probably still need to do some casting I use a simple struct to unify the enums

struct EnumValue
{
    EnumValue(unsigned int _val):myVal(_val){}

    //template method allows casting back to original enums and such
    template<typename T>
    T asBaseEnum()
    {
        //optional range checking
        return static_cast<T>(myVal);
    }

    //you could template these too if you want, or add 
    //a templated conversion operator instead 
    //(template<typename T> operator T()) 
    //but I personally don't bother
    operator=(unsigned int _val){myVal = _val}
    operator==(unsigned int _val){myVal == _val}

}

Upvotes: 0

Ben Jackson
Ben Jackson

Reputation: 93860

Fancy Template Version

Since there's no way to know the cardinality of an enum in C++ it is stuck with a fixed offset (here hardcoded as 100, but you could get template-fancy with that as well):

template <typename T0, typename REST>
struct enum_list : REST
{
    int base() { return 100 + REST::base(); }
    int unified(T0 value) { return int(value) + base(); }
    int separated(int value, T0 dummy) { return value - base(); }  // plus assertions?
    using REST::unified;
    using REST::separated;
};

template <typename T0>
struct enum_list<T0, void>
{
    int base() { return 0; }
    int unified(T0 value) { return int(value); }
    int separated(int value, T0 dummy) { return value; }
};

template <typename T0,        typename T1 = void, typename T2 = void, typename T3 = void,
          typename T4 = void, typename T5 = void, typename T6 = void, typename T7 = void>
struct make_enum_list {
    typedef enum_list<T0, typename make_enum_list<T1, T2, T3, T4, T5, T6, T7>::type> type;
};
template <>
struct make_enum_list<void,void,void,void> {
    typedef void type;
};

Example

enum Foo { A, B, C };
enum Bar { D, E, F };

typedef make_enum_list<Foo, Bar>::type unifier;

template <typename E>
int unify(E value)
{
    unifier u;
    return u.unified(value);
}

template <typename E>
E separate(int value)
{
    unifier u;
    return static_cast<E>(u.separated(value, E()));
}

#include <iostream>
int
main()
{
    std::cout << unify(B) << std::endl;
    std::cout << unify(F) << std::endl;
    std::cout << separate<Foo>(101) << std::endl;
    std::cout << separate<Bar>(1) << std::endl;
}

Whenever you add a new enum you just add it to the list in typedef make_enum_list<Foo, Bar>::type unifier.

Upvotes: 1

sehe
sehe

Reputation: 393457

About 'casting' the enums, I was thinking about a discriminated union of enums (sort of like Boost Variant but with (implicit) conversions and other conveniences specifically geared to enums. Without ado:

Assume we have two enums:

enum A { A_1, A_2, A_3, A_4 };
enum B { B_1, B_2, B_3, B_4 };

Note I donot concern myself about uniqueness of enum members, as I propose a discriminated union. Now, we'd like to be able to have a type AorB that behaves like so:

A a = A_3;
B b = B_1;

AorB any;

// any is isNil now
any = b; // makes it isB
any = a; // makes it isA

if (any == A_2) // comparison is fine, because any is in `isA` now
{
    std::cout << "Whoops, should be A_3, really\n"; // doesn't happen
}

if (any == B_2) // comparison
{
    std::cout << "Whoops, should not match"; // doesn't happen
}

a = static_cast<A>(any); // valid cast
b = static_cast<B>(any); // fails assertion

Here's my take on it:

#include <cassert> // for assert
#include <utility> // for std::swap

struct AorB
{
    enum Discriminant { isNil, isA, isB } discriminant;

    union
    {
        A valA;
        B valB;
    };

    AorB() : discriminant(isNil) {}

    A asA() const { assert(discriminant==isA); return valA; }
    B asB() const { assert(discriminant==isB); return valB; }

    explicit operator A() const { return asA(); }
    explicit operator B() const { return asB(); }

    /*explicit*/ AorB(A valA) : discriminant(isA), valA(valA) {}
    /*explicit*/ AorB(B valB) : discriminant(isB), valB(valB) {}

    friend void swap(AorB& a, AorB& b) {
        auto tmp = a; 
        a.discriminant = b.discriminant;
        a.safe_set(b.safe_get());

        b.discriminant = tmp.discriminant;
        b.safe_set(tmp.safe_get());
    }

    AorB& operator=(AorB implicit_conversion) {
        swap(implicit_conversion, *this);
        return *this;
    }

    bool operator==(AorB other) const {
        return 
            discriminant == other.discriminant && 
            safe_get()   == other.safe_get();
    }

  private:
    void safe_set(int val) {
        switch(discriminant) {
            case isA: valA = static_cast<A>(val); break;
            case isB: valB = static_cast<B>(val); break;
            case isNil: break;
        }
    }
    int safe_get() const {
        switch(discriminant) {
            case isA: return valA;
            case isB: return valB;
            case isNil: 
            default:  return 0;
        }
    }
};

See it live on Coliru, printing:

main.cpp:20: B AorB::asB() const: Assertion `discriminant==isB' failed.

Upvotes: 0

Emilio Garavaglia
Emilio Garavaglia

Reputation: 20739

Unfortunately enums are not designed to be combined, so -unless implementing some factory-based ID generators, but this goes out from enums an compile-time solutions- you cannot do much more of what suggested by Ben Jackson or Mooing Duck.

Consider also that -by a language standpoint- enums are not required to be sequential, so there is no way to know how many of them are into an enum (and also makes few sense to know it, since their values can be anything), hence the compiler cannot provide any automatic mechanism to chain (Jackson) or fork (Duck), hence it's only up to you to organize them. The above cired solutions are both valid, unless you are in the position you cannot define yourself the enumeral values (for example because you've got them from somebody else API).

In this last case, the only possibility is redefine yourself the combination (with other values) and map to the original through a conversion function.

Upvotes: 2

Zac Howland
Zac Howland

Reputation: 15870

I'm not quite sure what you mean by wanting to "cast the combined enum", but to allow for enum combinations, you use a bit-field:

enum Comparison
{
    LT = 0x0001,     // "<"
    GT = 0x0002,     // ">"
    EQ = 0x0004,     // "=="
    LTEQ = 0x0005,   // "<=" - combines LT and EQ
    GTEQ = 0x0006,   // ">=" - combines GT and EQ
    NEQ = 0x0008     // "!="
};

Since a few of them cannot be merged together (something cannot be both LT and GT, for example), you can adjust the bitfield to prevent that.

EDIT:

Since it appears you are looking for something slightly different:

If you want to merge enums, you can use the method Ben Jackson already described. An alternative would be to do something like this:

enum Comparison
{
    LT,
    ...
    NEG
};

enum Logical
{
    AND,
    OR,
    ...
};

enum MyNewCombination
{
    LessThan = LT,
    ...
    NotEqual = NEG,
    And = AND,
    Or = OR,
    ...
};

This would effectively move all of your existing enums into the new MyNewCombination enum. For the valid ranges, you could cast the MyNewCombination enum to a Comparison or Logical enum.

Upvotes: -2

Ben Jackson
Ben Jackson

Reputation: 93860

A common (but not exceptionally elegant) way to chain enum together (for example if child classes need to extend a unique set) is to have each enum provide a "last" value and use it to start the next:

enum Comparison
{
    LT,     // "<"
    ...
    NEQ,    // "!="
    LastComparison
};

enum Logical
{
    AND = LastComparison,
    OR,
    ...
    LastLogical
};

Upvotes: 7

Related Questions