2013-08-20 15 views
9

In meinem Projekt habe ich mehrere Enum-Deklarationen wie diese;Kombiniere enums C++

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

Und ich würde gerne mehrere davon kombinieren, in einer einzigen kombinierten enum, so dass;

  • Alle Elemente (aus den Sub-Enums) sind in der kombinierten enum vorhanden.
  • Alle Elemente haben einen eindeutigen Wert (offensichtlich).
  • Alle Elemente haben konsistenten Wert in der kombinierten enum und dem Original.

So:

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

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

Auch das, was ich möchte in der Lage sein, zu tun ist, zu ‚werfen‘ die kombinierte Enum, einem der ursprünglichen, den Wert in der kombinierten gegeben Nur Enumeration (sollte trivial sein unter der Annahme, dass die Werte konsistent sind).

Eine Alternative zu enum ist eine klassenbasierte Lösung, bei der die Klassen den Operator operator int() implementieren.

Hinweis; Ich glaube, die operator int() ist irgendwie der Weg zu gehen.

+0

Was, wenn zwei Elemente passiert, den gleichen Wert in verschiedenen 'enum's haben ? – Jacob

+0

Ich lege keinem Element explizit Werte zu, ich wollte, dass der Compiler es wenn möglich automatisch behandelt. Mein derzeitiger 'Fix' besteht darin, einfach das erste Element in jeder Enumeration dem Wert des letzten Elements in der vorherigen Enumeration (+1) zuzuweisen. - Jedoch finde ich das überhaupt nicht angenehm. – Skeen

+0

Warum möchten Sie mehrere Enums sowie eine 'Master' Enum haben? – Kindread

Antwort

13

Was habe ich häufig gesehen, ist dies:

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

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

Welche Sie ein wenig mehr Flexibilität als einfache Verkettung gibt, denn jetzt können Sie Vergleiche ohne Ihr Arithmetics zu stören und die Arithmetics und Vergleiche nicht müssen voneinander wissen.Es wird auch trivial den Typ eines ENUM zu erhalten:

constexpr OperationType getOperationType(unsigned value) {return value&0xFF00;} 
+0

Gibt es eine Möglichkeit, die Anzahl der Elemente einer Enumeration zu erhalten (zur Kompilierzeit)? – Skeen

+0

@Skeen Nein, das geht nicht. –

+0

@Skeen: Mit dieser Methode ja, aber nicht leicht. Sie müssten zu jedem von ihnen eine "Last ### Element" -Auszählung hinzufügen und sie dann zusammenfassen. Du könntest eine Funktion schreiben, um das zu tun, damit niemand, der sich den Code ansieht, ihren Kopf kratzt und dich verflucht. Wenn dies jedoch die Art von Funktionalität ist, nach der Sie suchen, ist eine große einzelne Aufzählung wirklich das, was Sie wollen. –

5

Eine gemeinsame (aber nicht außergewöhnlich elegant) Art und Weise an die Kette enum zusammen (zum Beispiel, wenn das Kind Klassen einen einzigartigen Satz verlängern müssen) ist jeder enum eine „letzte“ Wert schaffen zu haben, und es verwenden, um die nächste zu starten:

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

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

Dies ist, was ich gerade benutze, und ich bin auf der Suche nach etwas eleganter;) – Skeen

-2

ich bin mir nicht ganz sicher, was meinen Sie wollen „werfen die kombinierte enum“, aber für eNUM-Kombinationen zu ermöglichen, verwenden Sie ein Bitfeld:

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

Seit einigen Sie können nicht miteinander verschmolzen werden (etwas kann nicht bo sein) th LT und GT, zum Beispiel), können Sie das Bitfeld anpassen, um das zu verhindern.

EDIT:

Da es etwas, was Sie suchen erscheint etwas anders:

Wenn Sie Aufzählungen zusammenführen möchten, können Sie die Methode Ben Jackson bereits beschrieben verwenden. Eine Alternative wäre, so etwas zu tun:

enum Comparison 
{ 
    LT, 
    ... 
    NEG 
}; 

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

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

Diese effektiv alle vorhandenen Aufzählungen in die neuen MyNewCombination Enum bewegen würde. Für die gültigen Bereiche können Sie die Enumeration MyNewCombination in eine Comparison oder Logical Enumeration umwandeln.

+1

Ich versuche nicht, diese zusammen als Flaggen zu verschmelzen. Ich habe mehrere separate Enums, die ich in einem einzigen zusammenführen möchte. – Skeen

+0

In diesem Fall verschmelzen Sie sie entweder zu einer einzelnen enum oder Sie verketten sie (Ben Jackson zeigt das unten). –

+0

Ich habe auf eine dritte Alternative gehofft, eventuell mit einem Cleaver-Templating und dem Operator int(). – Skeen

1

Leider Aufzählungen sind nicht kombiniert werden entworfen, so -Außer einige Fabrik-basierten ID-Generatoren Umsetzung, aber dies von Aufzählungen aus geht eine Kompilierung-Lösungen - Sie können nicht viel mehr von dem tun, was von Ben Jackson oder Mooing Duck vorgeschlagen wird.

Bedenke auch, dass Enums - vom Standpunkt der Sprache aus gesehen - nicht sequentiell sein müssen, also gibt es keine Möglichkeit zu wissen, wie viele von ihnen in einem Enum sind (und auch wenig Sinn, da ihre Werte es können) etwas sein), daher kann der Compiler keinen automatischen Mechanismus zum Verketten (Jackson) oder Fork (Duck) bereitstellen, daher liegt es nur an Ihnen, sie zu organisieren. Die oben genannten Lösungsvorschläge sind beide gültig, es sei denn, Sie befinden sich in der Position, in der Sie die Enumerationswerte nicht selbst definieren können (zum Beispiel, weil Sie sie von einer anderen API erhalten haben).

In diesem letzten Fall ist die einzige Möglichkeit, die Kombination (mit anderen Werten) neu zu definieren und durch eine Konvertierungsfunktion dem Original zuzuordnen.

+0

Ich bin in der Lage, die Enums zu ändern. Auch wenn Enums nicht sequentiell sein müssen, sollte ich dann nicht wirklich alle Werte der Sub-Enums nacheinander setzen, bevor ich eine der vorgeschlagenen Methoden benutze? – Skeen

+0

Ja, jedes Enumeral (die Konstante in einem Enum), das ohne Wert deklariert wurde, hat das vorherige plus eins. Du brauchst nur einen Startpunkt oder markierst den Endpunkt mit einem passenden Namen ... aber genau das machen die vorgeschlagenen Methoden. Der Punkt ist, dass du darauf achten musst, dass sie sich nicht überschneiden. Der Compiler wird nichts dagegen tun. Und das ist eine Skalierbarkeitsgrenze. –

0

über ‚Casting‘ die Aufzählungen, ich über eine diskriminierte Vereinigung dachte von Aufzählungen (Art wie Boost-Variante, aber mit (impliziten) Konvertierungen und anderen Annehmlichkeiten speziell auf Aufzählungen ausgerichtet, ohne Umschweife.

Angenommen wir haben zwei Aufzählungen:

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

Hinweis ich mich donot betreffen über Einzigartigkeit enum-Mitglieder, wie ich vorschlagen diskriminiert Vereinigung Nun würden wir einen Typ haben AorB in der Lage sein mag, die wie so verhält. :

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 

Hier ist mein nehmen auf sie:

#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; 
     } 
    } 
}; 

anzeigen live on Coliru, Druck:

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

Fancy Template Version

Da gibt es keinen Weg, um die Mächtigkeit zu wissen von einem enum in C++ ist es mit einem festen Offset stecken (hier fest einprogrammiert als 100, aber man konnte Template-Phantasie mit, dass auch erhalten):

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; 
}; 

Beispiel

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; 
} 

Jedes Mal, wenn Sie fügen Sie eine neue enum Sie fügen es einfach zur Liste in typedef make_enum_list<Foo, Bar>::type unifier hinzu.

+0

Interessanter Ansatz. Hier ist eine mit mehr Variablen, statische (warum hoffe, dass der Compiler all diese unnötige Instanziierung weg optimieren wird? Leere Basisklassen, ok, aber die am weitesten abgeleitete 'enum_list' muss auch nicht instanziiert werden :)). Auch keine make_enum_list, wenn der Basis-Offset auf 100 festgelegt ist. Siehe hier: http://ideone.com/8lL0C3 Oh, und 10 LoC weniger :) – sehe

0

Also habe ich vor kurzem etwas ähnliches mit dem Präprozessor gemacht, ich weiß, dass diese Antwort etwa 2 Jahre zu spät kommt, aber immer noch.

Im Prinzip habe ich verschiedene nicht zusammenhängend Aufzählungstypen definiert, die ich möchte in der Lage zu erstrecken sich von einander, so dass ich geschrieben haben folgende Präprozessordirektiven:

#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; 

Die Aufzählungen in ihren eigenen Namensraum erstellt werden, zu vermeiden, mehrere Definitionen von LAST und BASE. Du könntest sie für Enum-Klassen wechseln, wenn du die Namensraumverschmutzung nicht magst, aber es macht das Hin und Herwerfen zu unsignierten Ints später etwas mühsamer.

Sie benötigen einen Basis Enumerator für aufeinanderfolgende Aufzählungen zu definieren, zu verlängern, aber es kann nur leer sein, je nach Vorliebe für Stil

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

Nachfolgende Aufzählungen können mit den Richtlinien

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

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

Es ist nur irgendein syntaktischer Zucker, um verkettete enums zu schaffen.

Um alle diese Aufzählungen kombinieren, werden Sie wahrscheinlich noch einige Casting muss ich eine einfache Struktur verwenden, um die Aufzählungen zu vereinigen

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} 

}