2011-02-11 18 views
36

Sto leggendo un valore enum da un file binario e vorrei verificare se il valore è realmente parte dei valori enum. Come posso farlo?Come verificare se il valore enum è valido?

#include <iostream> 

enum Abc 
{ 
    A = 4, 
    B = 8, 
    C = 12 
}; 

int main() 
{ 
    int v1 = 4; 
    Abc v2 = static_cast<Abc>(v1); 

    switch (v2) 
    { 
     case A: 
      std::cout<<"A"<<std::endl; 
      break; 
     case B: 
      std::cout<<"B"<<std::endl; 
      break; 
     case C: 
      std::cout<<"C"<<std::endl; 
      break; 
     default : 
      std::cout<<"no match found"<<std::endl; 
    } 
} 

Devo usare l'operatore switch o c'è un modo migliore?

EDIT

ho valori enum impostati e, purtroppo, non posso modificare. Per peggiorare le cose, che non sono continui (i loro valori va 0, 75,76,80,85,90,95,100, etc.)

+3

Qualsiasi enum è solo un numero, quindi non penso che ci sia modo migliore per controllarlo. Probabilmente dovresti definire una struttura più rigida per i tuoi tipi di dati. – Rizo

risposta

21
Il valore

enum è valido in C++ se rientra nell'intervallo [A, B], che è definito dalla seguente regola standard. Pertanto, in caso di enum X { A = 1, B = 3 }, il valore di 2 è considerato un valore enum valido.

consideri 7.2/6 della norma:

Per un'enumerazione dove Emin è il più piccolo enumeratore e Emax è il più grande, i valori del censimento sono i valori del tipo sottostante nell'intervallo bmin a bmax dove bmin e bmax sono, rispettivamente, i valori più piccoli e più grandi del campo di bit più piccolo in grado di memorizzare emin ed emax. È possibile definire un'enumerazione con valori non definiti da nessuno dei suoi enumeratori.

Non c'è retrospezione in C++. Un approccio da adottare è quello di elencare i valori enum in un array in aggiunta e scrivere un wrapper che effettuerebbe la conversione e probabilmente genererebbe un'eccezione in caso di errore.

Vedere Similar Question su come eseguire il cast di enum per ulteriori dettagli.

+1

hai erroneamente interpretato la citazione standard, c'è più di '[A, B]' nei valori validi. –

+5

Infatti, ad esempio se i valori sono 1 e 5, quest'ultimo richiede almeno 3 bit, quindi 6 e 7 saranno anche valori validi dell'enumeratore. – visitor

+0

@Matthieu: Grazie, ha modificato la risposta. – Leonid

9

magari utilizzare enum come questo:

enum MyEnum 
{ 
A, 
B, 
C 
}; 

e di verificare

if (v2 >= A && v2 <= C) 

Se non si specificano i valori per le costanti enum, i valori partono da zero e aumentano di uno con ogni mossa verso il basso nell'elenco. Ad esempio, dato enum MyEnumType { ALPHA, BETA, GAMMA }; ALPHA ha un valore pari a 0, BETA ha un valore di 1, e GAMMA ha un valore di 2.

+1

Mi piace la semplicità di questo e l'ho espanso definendo sempre il primo elemento in un enum come SOMETYPE_UNKNOWN e l'ultimo come SOMETYPE_MAX. Quindi il test sarà sempre AssertTrue (v2> = SOMETYPE_UNKNOWN && v2 <= SOMETYPE_MAX). Ovviamente, aggiungi sempre gli elementi solo dopo SCONOSCIUTO e prima di MAX. –

+0

All'inizio non ho capito la tua idea, ma è davvero un trucco eccellente, a manutenzione zero! – pfabri

2

Parlando di una lingua, non c'è modo migliore, i valori enum esistere tempo di compilazione solo e non c'è modo di enumerarli in modo programmatico. Con un'infrastruttura ben ponderata, potresti comunque evitare di elencare più volte tutti i valori. Vedere Easy way to use variables of enum types as string in C?

tuo campione può quindi essere riscritta utilizzando il "enumFactory.h" a condizione che vi come:

#include "enumFactory.h" 

#define ABC_ENUM(XX) \ 
    XX(A,=4) \ 
    XX(B,=8) \ 
    XX(C,=12) \ 

DECLARE_ENUM(Abc,ABC_ENUM) 

int main() 
{ 
    int v1 = 4; 
    Abc v2 = static_cast<Abc>(v1); 

    #define CHECK_ENUM_CASE(name,assign) case name: std::cout<< #name <<std::endl; break; 
    switch (v2) 
    { 
     ABC_ENUM(CHECK_ENUM_CASE) 
     default : 
      std::cout<<"no match found"<<std::endl; 
    } 
    #undef CHECK_ENUM_CASE 
} 

o addirittura (usando alcune altre strutture già esistenti in quel colpo di testa):

#include "enumFactory.h" 

#define ABC_ENUM(XX) \ 
    XX(A,=4) \ 
    XX(B,=8) \ 
    XX(C,=12) \ 

DECLARE_ENUM(Abc,ABC_ENUM) 
DEFINE_ENUM(Abc,ABC_ENUM) 

int main() 
{ 
    int v1 = 4; 
    Abc v2 = static_cast<Abc>(v1); 
    const char *name = GetString(v2); 
    if (name[0]==0) name = "no match found"; 
    std::cout << name << std::endl; 
} 
7

L'unico modo che ho trovato per renderlo 'facile' era quello di creare (macro) una matrice ordinata di enumerazioni e controllarla.

Il switch trucco esito negativo con enum s perché un enum può avere più di un enumeratore con un dato valore.

È un problema fastidioso, davvero.

3

estensioni gestite per C++ supporta la seguente sintassi:

enum Abc 
{ 
    A = 4, 
    B = 8, 
    C = 12 
}; 

Enum::IsDefined(Abc::typeid, 8); 

Riferimento: MSDN "Managed Extensions for C++ Programming"

+0

Non sono sicuro di cosa sia "managed C++", ma sei sicuro che sia C++ e non C#? [Questo] (http://msdn.microsoft.com/en-us/library/system.enum.isdefined%28v=vs.110%29.aspx) sembra C# –

+3

@ BЈовић: 'managed C++' è una variante Microsoft di 'C++' che è in grado di utilizzare le librerie del '.NET framework'. Sembra che questo sia 'C++' dato che l'operatore '::' non è definito in 'C#' come questo. – Stefan

+0

@ BЈовић hai provato il codice in un progetto C++ di estensioni gestite? Stiamo usando un codice simile in uno dei nostri progetti C++. Nello specifico utilizziamo il metodo Enum :: IsDefined(). – Brett

4

In C++ 11 c'è un modo migliore se siete disposti a elencare i vostri valori enum come parametri del modello . 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.

Una possibile aggiunta utile all'esempio riportato di seguito potrebbe essere qualche asserzione statica attorno al tipo sottostante di EnumType relativo a IntType per evitare problemi di troncamento. Lasciato come esercizio.

#include <stdio.h> 

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<IntType>(V) || super::is_value(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

Poiché il valore di "int v" non è noto in fase di compilazione, "is_value" dovrà essere eseguito in fase di esecuzione. Questo non comporterebbe tutti i tipi di chiamate ricorsive e sarebbe molto inefficiente rispetto a una semplice istruzione switch oa un array di tutti i valori? Devi ancora elencare tutti i valori enum, quindi non è che stai ottenendo qualcosa con questo approccio. O mi sono perso qualcosa qui? –

+0

@InnocentBystander Sono tutte funzioni 'constexpr', quindi il compilatore ha molte possibilità di ottimizzazione. Anche le funzioni non sono ricorsive; è una catena di funzioni che ha lo stesso nome. In alcuni test rapidi con l'esempio precedente, gcc 5.4 genera l'istruzione code one più breve per la versione del modello rispetto alla versione switch. Clang 3.8 è più lungo di due istruzioni per la versione del modello. Il risultato varierà in base a quanti valori e se i valori sono contigui. La grande vittoria, soprattutto quando si esegue la decodifica del protocollo, è che si scrivono i codici che ci si aspetta su una riga. – janm

+1

hai ragione - mi spiace non "ricorsivo" di per sé, ma chiamate di catene di funzioni. È interessante che i compilatori possano ottimizzare tutto ciò. E grazie per aver seguito una risposta di 3 anni :) –

2

Kinda necro, ma ... fa un test di ricezione di int in valori primi/ultimi enum (può essere combinata con l'idea di JANM di effettuare controlli precisi), C++ 11:

Intestazione: dichiarazione

namespace chkenum 
{ 
    template <class T, T begin, T end> 
    struct RangeCheck 
    { 
    private: 
     typedef typename std::underlying_type<T>::type val_t; 
    public: 
     static 
     typename std::enable_if<std::is_enum<T>::value, bool>::type 
     inrange(val_t value) 
     { 
      return value >= static_cast<val_t>(begin) && value <= static_cast<val_t>(end); 
     } 
    }; 

    template<class T> 
    struct EnumCheck; 
} 

#define DECLARE_ENUM_CHECK(T,B,E) namespace chkenum {template<> struct EnumCheck<T> : public RangeCheck<T, B, E> {};} 

template<class T> 
inline 
typename std::enable_if<std::is_enum<T>::value, bool>::type 
testEnumRange(int val) 
{ 
    return chkenum::EnumCheck<T>::inrange(val); 
} 

Enum:

enum MinMaxType 
{ 
    Max = 0x800, Min, Equal 
}; 
DECLARE_ENUM_CHECK(MinMaxType, MinMaxType::Max, MinMaxType::Equal); 

Usage:

bool r = testEnumRange<MinMaxType>(i); 

Principalmente la differenza di sopra ha suggerito che la funzione di test dipende esclusivamente dal tipo enum stesso.

0

Un altro modo per farlo:

#include <algorithm> 
#include <iterator> 
#include <iostream> 

template<typename> 
struct enum_traits { static constexpr void* values = nullptr; }; 

namespace detail 
{ 

template<typename T> 
constexpr bool is_value_of(int, void*) { return false; } 

template<typename T, typename U> 
constexpr bool is_value_of(int v, U) 
{ 
    using std::begin; using std::end; 

    return std::find_if(begin(enum_traits<T>::values), end(enum_traits<T>::values), 
     [=](auto value){ return value == static_cast<T>(v); } 
    ) != end(enum_traits<T>::values); 
} 

} 

template<typename T> 
constexpr bool is_value_of(int v) 
{ return detail::is_value_of<T>(v, decltype(enum_traits<T>::values) { }); } 

//////////////////// 
enum Abc { A = 4, B = 8, C = 12 }; 

template<> 
struct enum_traits<Abc> { static constexpr auto values = { A, B, C }; }; 
decltype(enum_traits<Abc>::values) enum_traits<Abc>::values; 

enum class Def { D = 1, E = 3, F = 5 }; 

int main() 
{ 
    std::cout << "Abc:"; 
    for(int i = 0; i < 10; ++i) 
     if(is_value_of<Abc>(i)) std::cout << " " << i; 
    std::cout << std::endl; 

    std::cout << "Def:"; 
    for(int i = 0; i < 10; ++i) 
     if(is_value_of<Def>(i)) std::cout << " " << i; 
    std::cout << std::endl; 

    return 0; 
} 

La parte "brutta" di questo approccio IMHO si trova a dover definire:

decltype(enum_traits<Abc>::values) enum_traits<Abc>::values 

Se non siamo contrari alle macro, si può avvolgere all'interno di una macro:

#define REGISTER_ENUM_VALUES(name, ...) \ 
template<> struct enum_traits<name> { static constexpr auto values = { __VA_ARGS__ }; }; \ 
decltype(enum_traits<name>::values) enum_traits<name>::values; 
Problemi correlati