2010-11-12 15 views
77

Esiste un modo generico di trasmettere int a enum in C++?Modo generico per eseguire il cast di enum in C++

Se int cade nella gamma di un enum si deve restituire un valore enum, altrimenti genera un exception. C'è un modo per scriverlo genericamente? È necessario supportare più di uno enum type.

Priorità: Ho un esterno di tipo enum e alcun controllo sul codice sorgente. Mi piacerebbe memorizzare questo valore in un database e recuperarlo.

+0

'enum e {x = 10000};' in questo caso '9999' rientra nell'intervallo di' enum'? –

+0

No, '9999' non cade. – Leonid

+7

Buona domanda. Come per ogni "perché?" che sta per apparire, lasciami solo dire "deserializzazione" - mi sembra abbastanza una ragione. Sarei anche felice di sentire una risposta compilatore C++ 0x per 'enum class'. – Kos

risposta

37

La cosa più ovvia è quella di annotare l'enum:

// generic code 
#include <algorithm> 

template <typename T> 
struct enum_traits {}; 

template<typename T, size_t N> 
T *endof(T (&ra)[N]) { 
    return ra + N; 
} 

template<typename T, typename ValType> 
T check(ValType v) { 
    typedef enum_traits<T> traits; 
    const T *first = traits::enumerators; 
    const T *last = endof(traits::enumerators); 
    if (traits::sorted) { // probably premature optimization 
     if (std::binary_search(first, last, v)) return T(v); 
    } else if (std::find(first, last, v) != last) { 
     return T(v); 
    } 
    throw "exception"; 
} 

// "enhanced" definition of enum 
enum e { 
    x = 1, 
    y = 4, 
    z = 10, 
}; 

template<> 
struct enum_traits<e> { 
    static const e enumerators[]; 
    static const bool sorted = true; 
}; 
// must appear in only one TU, 
// so if the above is in a header then it will need the array size 
const e enum_traits<e>::enumerators[] = {x, y, z}; 

// usage 
int main() { 
    e good = check<e>(1); 
    e bad = check<e>(2); 
} 

È necessario la matrice per essere sempre aggiornati con e, che è una seccatura se non sei l'autore di e. Come dice Sjoerd, può probabilmente essere automatizzato con qualsiasi sistema di costruzione decente.

In ogni caso, sei contro 7.2/6:

Per un'enumerazione dove Emin è il più piccolo enumeratore ed Emax è il più grande , i valori della enumerazione sono i valori di il sottostante tipo nell'intervallo da bmin a bmax, dove bmin e bmax sono, rispettivamente, i valori più piccoli e più grandi del bit-field più piccolo che può memorizzare emin ed emax. È possibile definire un enumerazione che ha valori non definiti da uno qualsiasi dei suoi enumeratori.

Quindi, se non sei l'autore di e, si può o non può avere una garanzia che i valori validi di e appaiono in realtà nella sua definizione.

18

Brutto.

enum MyEnum { one = 1, two = 2 }; 

MyEnum to_enum(int n) 
{ 
    switch(n) 
    { 
    case 1 : return one; 
    case 2 : return two; 
    } 
    throw something(); 
} 

Ora per la vera domanda. Perchè ti serve? Il codice è brutto, non facile da scrivere (*?) E non è facile da mantenere, e non è facile da integrare nel tuo codice. Il codice ti dice che è sbagliato. Perché combatterlo?

EDIT:

In alternativa, dato che le enumerazioni sono tipi interi in C++:

enum my_enum_val = static_cast<MyEnum>(my_int_val); 

ma questo è ancora più brutto che al di sopra, molto più incline ad errori, e non getteranno come si desiderio.

+0

supporta solo un tipo, MyEnum. – Simone

+2

@Leonid: A mia conoscenza non può essere fatto genericamente. Ad un certo livello, qualsiasi soluzione che ti viene proposta per "gettare" (o fare qualcosa di speciale) per i tipi non validi deve avere un interruttore come quello che ho pubblicato. –

+0

Esattamente, * reinterpret_cast * non controlla la validità dei valori * enum * ed è brutto. Ma dato che i valori di enum sono noti in fase di compilazione, forse c'è un modo per farlo tramite i template, o forse ci sarà un modo per farlo in 'C++ 0x'? – Leonid

1

Non dovresti desiderare qualcosa come quello che descrivi di esistere, temo ci siano problemi nella progettazione del codice.

Inoltre, si assume che le enumerazioni sono disponibili in una gamma, ma che non è sempre il caso:

enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 }; 

Questo non è in un intervallo: anche se fosse possibile, si fa a controllare ogni numero intero da 0 a 2^n per vedere se corrispondono al valore di qualche enum?

+0

come vorresti altrimenti recuperare i valori enum dal database? Gli interi sono noti al momento della compilazione, quindi perché non è possibile avere una conversione generica basata su modelli? – Leonid

+2

@Leonid: Perché a un certo livello, è necessario avere un interruttore, come ho detto. –

+2

@Leonid Templates non è un proiettile d'argento per risolvere ogni problema che si possa pensare. – Sjoerd

2

No, non vi è alcuna introspezione in C++, né esiste una funzione integrata di "controllo del dominio".

3

Se, come si descrive, i valori si trovano in un database, perché non scrivere un generatore di codice che legge questa tabella e crea un file .h e .cpp con entrambe le funzioni enum e to_enum(int)?

Vantaggi:

  • semplice per aggiungere una funzione to_string(my_enum).
  • Poca manutenzione richiesto
  • database e il codice sono in sincronia
+0

Non c'è controllo su ** enum ** digitare il codice sorgente. Sono d'accordo che una struttura può essere generata che implementa la conversione. Tuttavia, la funzione non sarebbe a conoscenza di eventuali modifiche/estensioni apportate al tipo ** enum ** esterno (a meno che non vengano eseguite ogni volta al momento della compilazione). – Leonid

+0

@Leonid Quindi leggi quell'intestazione enum e genera la funzione 'to_enum (int)' basata su quello. – Sjoerd

+0

@Leonid Ogni serio sistema di gestione dei progetti, anche 'make', può confrontare la data di due file per vedere se il generatore deve essere rieseguito. – Sjoerd

2

Cosa ne pensi di questo?

#include <iostream> 
#include <stdexcept> 
#include <set> 
#include <string> 

using namespace std; 

template<typename T> 
class Enum 
{ 
public: 
    static void insert(int value) 
    { 
     _set.insert(value); 
    } 

    static T buildFrom(int value) 
    { 
     if (_set.find(value) != _set.end()) { 
      T retval; 
      retval.assign(value); 
      return retval; 
     } 
     throw std::runtime_error("unexpected value"); 
    } 

    operator int() const { return _value; } 

private: 
    void assign(int value) 
    { 
     _value = value; 
    } 

    int _value; 
    static std::set<int> _set; 
}; 

template<typename T> std::set<int> Enum<T>::_set; 

class Apples: public Enum<Apples> {}; 

class Oranges: public Enum<Oranges> {}; 

class Proxy 
{ 
public: 
    Proxy(int value): _value(value) {} 

    template<typename T> 
    operator T() 
    { 
     T theEnum; 
     return theEnum.buildFrom(_value); 
    } 

    int _value; 
}; 

Proxy convert(int value) 
{ 
    return Proxy(value); 
} 

int main() 
{  
    Apples::insert(4); 
    Apples::insert(8); 

    Apples a = convert(4); // works 
    std::cout << a << std::endl; // prints 4 

    try { 
     Apples b = convert(9); // throws  
    } 
    catch (std::exception const& e) { 
     std::cout << e.what() << std::endl; // prints "unexpected value" 
    } 
    try { 
     Oranges b = convert(4); // also throws 
    } 
    catch (std::exception const& e) { 
     std::cout << e.what() << std::endl; // prints "unexpected value" 
    } 
} 

È quindi possibile utilizzare il codice che ho inviato here per attivare i valori.

+0

Hai ancora bisogno di aggiungere 'Apples :: insert (4)' da qualche parte, quindi questo non ha alcun vantaggio su un interruttore. – Sjoerd

+1

Ha detto che i valori provengono da un database, ecco perché ho aggiunto un metodo "insert". – Simone

0

C++ 0x alternativa alla versione "brutta", consente più enum. Utilizza gli elenchi di inizializzatori anziché gli switch, un IMO un po 'più pulito. Sfortunatamente, questo non funziona sulla necessità di codificare i valori dell'enumerazione.

#include <cassert> // assert 

namespace // unnamed namespace 
{ 
    enum class e1 { value_1 = 1, value_2 = 2 }; 
    enum class e2 { value_3 = 3, value_4 = 4 }; 

    template <typename T> 
    int valid_enum(const int val, const T& vec) 
    { 
     for (const auto item : vec) 
      if (static_cast<int>(item) == val) return val; 

     throw std::exception("invalid enum value!"); // throw something useful here 
    } // valid_enum 
} // ns 

int main() 
{ 
    // generate list of valid values 
    const auto e1_valid_values = { e1::value_1, e1::value_2 }; 
    const auto e2_valid_values = { e2::value_3, e2::value_4 }; 

    auto result1 = static_cast<e1>(valid_enum(1, e1_valid_values)); 
    assert(result1 == e1::value_1); 

    auto result2 = static_cast<e2>(valid_enum(3, e2_valid_values)); 
    assert(result2 == e2::value_3); 

    // test throw on invalid value 
    try 
    { 
     auto result3 = static_cast<e1>(valid_enum(9999999, e1_valid_values)); 
     assert(false); 
    } 
    catch (...) 
    { 
     assert(true); 
    } 
} 
-4

provare qualcosa di simile:

enum EType 
{ 
    type1, 
    type2 
}; 

unsigned int number = 3; 
EType e = static_cast<EType>(number); 
if(static_cast<unsigned int>(e) != number) 
    throw std::exception(); 
+0

Per coloro che si chiedono se questo codice funzioni - beh, non è così. – 0xC0DEGURU

+0

Beh, puoi aggiustarlo? – Shoe

+0

(Suggerimento: no, non è possibile) –

1

Se siete disposti a elencare i vostri valori enum come parametri del modello si può fare questo in C++ 11 con i modelli varadic. Puoi considerare questo come una buona cosa, permettendoti di accettare sottoinsiemi dei valori enum validi in diversi contesti; spesso utile durante l'analisi di codici da fonti esterne.

Forse non è così generico come vorresti, ma il codice di controllo stesso è generalizzato, devi solo specificare l'insieme di valori. Questo approccio gestisce lacune, valori arbitrari, ecc.

template<typename EnumType, EnumType... Values> class EnumCheck; 

template<typename EnumType> class EnumCheck<EnumType> 
{ 
public: 
    template<typename IntType> 
    static bool constexpr is_value(IntType) { return false; } 
}; 

template<typename EnumType, EnumType V, EnumType... Next> 
class EnumCheck<EnumType, V, Next...> : private EnumCheck<EnumType, Next...> 
{ 
    using super = EnumCheck<EnumType, Next...>; 

public: 
    template<typename IntType> 
    static bool constexpr is_value(IntType v) 
    { 
     return v == static_cast<typename std::underlying_type<EnumType>::type>(V) || super::is_value(v); 
    } 

    EnumType convert(IntType v) 
    { 
     if (!is_value(v)) throw std::runtime_error("Enum value out of range"); 
     return static_cast<EnumType>(v); 
}; 

enum class Test { 
    A = 1, 
    C = 3, 
    E = 5 
}; 

using TestCheck = EnumCheck<Test, Test::A, Test::C, Test::E>; 

void check_value(int v) 
{ 
    if (TestCheck::is_value(v)) 
     printf("%d is OK\n", v); 
    else 
     printf("%d is not OK\n", v); 
} 

int main() 
{ 
    for (int i = 0; i < 10; ++i) 
     check_value(i); 
} 
+0

Sebbene questo collegamento possa rispondere alla domanda, è meglio includere qui le parti essenziali della risposta e fornire il link per riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia. - [Dalla revisione] (/ recensione/post di bassa qualità/10887195) – Tas

+1

@Tas È un collegamento a una risposta SO diversa: non presenta gli stessi problemi di un collegamento esterno. Aggiornato in ogni caso. – janm

Problemi correlati