2013-08-20 4 views
9

В моем проекте у меня есть несколько перечислений enum, подобных этому;Объединить перечисления C++

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

И я хотел бы объединить несколько из них в единый комбинированный перечислитель, такой, что;

  • Все элементы (из под-перечислений) присутствуют в комбинированном перечислении.
  • Все элементы имеют уникальное значение (очевидно).
  • Все элементы имеют согласованное значение в объединенном перечислении и оригинале.

Как это:

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

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

Кроме того, что я хотел бы быть в состоянии сделать, чтобы «бросить» комбинированное перечисление, к одному из оригинальных, учитывая значение в сочетании только перечисление (должно быть тривиально, если значения согласованы).

Альтернатива перечислению - это решение класса, где классы реализуют оператор operator int().

Примечание; Я верю, что operator int() - это как-то путь.

+0

Что происходит, когда два элемента имеет одинаковое значение в различных 'enum's ? – Jacob

+0

Я не явным образом присваиваю значения любым элементам, я хотел, чтобы компилятор обрабатывал это автоматически, если это было возможно. Мое текущее «исправление» - это просто назначить первый элемент в каждом перечислении, на значение последнего элемента в предыдущем перечислении (+1). - Однако я не считаю это приятным вообще. – Skeen

+0

Почему вы хотите иметь несколько перечислений, а также переименование 'master'? – Kindread

ответ

13

То, что я часто видел это:

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

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

Что дает вам немного больше гибкости, чем простая цепочка, потому что теперь вы можете добавить сравнения, не нарушая ваш арифметик, и арифметика и Сравнения не нужно знать друг друга.Она также становится тривиальной, чтобы получить тип перечисления:

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

Есть ли способ получить количество элементов перечисления (во время компиляции)? – Skeen

+0

@Skeen Нет, вы не можете. –

+0

@Skeen: С помощью этого метода да, но не легко. Вам нужно будет добавить перечисление «Последний ### Element» каждому из них, а затем суммировать их. Вы можете написать функцию, чтобы сделать это, чтобы любой, кто смотрит на код, не поцарапал себе голову и не проклинал вас. Однако, если это тот тип функциональности, который вы используете, большое одиночное перечисление действительно то, что вы хотите. –

5

Обычным (но не исключительно элегантный) способ цепи enum вместе (например, если ребенок классов необходимо расширить уникальный набор) должен иметь каждый enum обеспечить «последнюю» значение и использовать его, чтобы начать следующий:

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

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

Это то, что я использую прямо сейчас, и я ищу что-то более элегантное;) – Skeen

-2

Я не совсем уверен, что вы имеете в виду, желая, чтобы «бросить комбинированное перечисление», но, чтобы позволить комбинации перечислений, вы используете битовое поле:

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

Поскольку некоторые из их нельзя объединить (что-то не может быть й LT и GT, например), вы можете настроить бит-бит, чтобы этого не произошло.

EDIT:

Поскольку кажется, что Вы ищете что-то немного другое:

Если вы хотите объединить перечисления, вы можете использовать метод Бен Джексон уже описал. Альтернатива была бы сделать что-то вроде этого:

enum Comparison 
{ 
    LT, 
    ... 
    NEG 
}; 

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

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

Это будет эффективно переместить все существующие перечислений в новый MyNewCombination перечисления. Для допустимых диапазонов вы можете перечислить перечисление MyNewCombination в перечисление Comparison или Logical.

+1

Я не пытаюсь объединить их вместе как флаги. У меня есть несколько отдельных перечислений, которые мне интересны в объединении в один. – Skeen

+0

В этом случае вы либо объедините их в одно перечисление, либо объедините их (Бен Джексон показывает это ниже). –

+0

Я надеялся на третью альтернативу, возможно, используя некоторые шаблоны шаблонов и 'operator int()'. – Skeen

1

К сожалению перечисления не предназначены быть объединены, так -Ела реализацию некоторых фабричных на основе генераторов ID, но это выходит из перечислений Н. время компиляции решений - вы не можете сделать больше того, что было предложено Ben Jackson или Mooing Duck.

Считайте также, что, поскольку языковые точки-перечисления не обязательно должны быть последовательными, поэтому нет способа узнать, сколько из них входит в перечисление (а также имеет мало смысла его знать, поскольку их значения могут быть кем угодно), поэтому компилятор не может обеспечить какой-либо автоматический механизм для цепи (Джексон) или fork (Duck), поэтому вы можете их организовать. Вышеуказанные приведенные решения являются действительными, если вы не находитесь в положении, в котором вы не можете определить себя в числовых значениях (например, потому что вы получили их от какого-либо другого API).

В этом последнем случае единственная возможность - переопределить себе комбинацию (с другими значениями) и сопоставить оригинал с помощью функции преобразования.

+0

Я могу изменить перечисления. Кроме того, если перечисления не обязательно должны быть последовательными, то не следует ли я действительно устанавливать все значения подэбмена последовательно, прежде чем использовать какой-либо из предложенных методов? – Skeen

+0

Да, каждый enumeral (константа в перечисление), объявленный без значения, имеет предыдущий плюс один. Вам просто нужна начальная точка или отметьте конечную точку подходящим именем ... но это именно то, что предлагают предлагаемые методы. Дело в том, что вы должны заботиться о том, чтобы они не перекрывались. Компилятор ничего не сделает против этого. И это предел масштабируемости. –

0

О «литья» перечислений, я думал о дискриминационного союзе из перечислений (вроде как Boost, Variant, но с (неявными) преобразованиями и другими удобствами, специально предназначенных для перечислений без проволочек:.

Предположима у нас есть два перечислений:

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

Примечание I выигрыш касается меня об уникальности членов перечислений, так как я предлагаю предвзято союз Теперь мы хотели бы, чтобы быть в состоянии иметь тип AorB, который ведет себя, как так. :

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 

Вот мой взгляд на него:

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

Посмотри live on Coliru, печать:

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

Fancy Версия шаблона

Поскольку нет никакого способа узнать мощность от enum в C++ он застрял с фиксированным смещением (здесь жёстко как 100, но вы можете получить шаблонные фантазии с этим, а):

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

Пример

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

Всякий раз, когда вы добавляете новый enum просто добавьте его в список в typedef make_enum_list<Foo, Bar>::type unifier.

+0

Интересный подход. Вот один из них с большим количеством вариаций, статический (почему надеюсь, что компилятор будет оптимизировать все эти ненужные экземпляры? Пустые базовые классы, нормально, но самый производный 'enum_list' не нужно создавать экземплярами :) :). Кроме того, нет необходимости в 'make_enum_list', если базовое смещение фиксировано на 100. См. Здесь: http://ideone.com/8lL0C3 О, и 10 LoC меньше :) – sehe

0

Так что я недавно сделал что-то похожее на препроцессор, я знаю, что этот ответ наступает через 2 года, но все же.

В принципе у меня есть различные не смежно определенные перечислимые типов, которые я хочу, чтобы иметь возможность расширить от Афоризма, так что я написал директивы следующего препроцессора:

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

перечисления созданы в своих собственных пространствах имен, чтобы избежать несколько определений LAST и BASE. Вы можете переключать их для классов перечисления, если вам не нравится загрязнение пространства имен, но оно делает кастинг назад и вперед к неподписанным ints более сложным.

Вам нужно определить базовую энумератор последовательных перечислений продлить, но это может быть просто пустым, в зависимости от ваших предпочтений стиля

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

Последующие перечислений могут быть объявлены с директивами

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

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

Это просто синтаксический сахар, чтобы создать цепочку перечислений.

Чтобы объединить все эти перечислений, вы, вероятно, все еще необходимы сделать некоторый кастинг я использую, простую структуру, чтобы объединить перечисления

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} 

}