2016-06-10 12 views
5

C++11 enumeriche le enumerazioni dell'ambito sono eccezionali, dovresti usarle quando possibile. Tuttavia, a volte è necessario convertire un numero intero in un valore enum dell'ambito (ad esempio se lo si ottiene dall'input dell'utente).Come trasmettere in modo sicuro i tipi interi alle enumerazioni con scope

Esiste un modo sicuro per eseguire questa operazione e rilevare quando il valore non è valido (vale a dire, al di fuori dei valori consentiti dell'enum)?

Credo che usare solo lead statici_cast a comportamento indefinito se il numero intero non è valido. Esiste un modo generico per farlo che non richieda la scrittura di una funzione di conversione per ogni tipo di enume dell'ambito a mano (e che deve essere aggiornato ogni volta che si aggiunge un nuovo valore all'enum)?

+2

Il comportamento non è indefinito. Sezione 7.2, paragrafo 10 dice "Un'espressione di tipo aritmetico o di enumerazione può essere convertita esplicitamente in un tipo di enumerazione, il valore è invariato se si trova nell'intervallo dei valori di enumerazione del tipo di enumerazione; altrimenti il ​​valore di enumerazione risultante non è specificato ** ". Quindi il valore non è specificato, ma il comportamento non è indefinito (nessun demone nasale). – Cornstalks

+0

@Cornstalks, grazie per avermelo fatto notare. Questo è decisamente meglio di quanto non sia definito. Tuttavia, la domanda è la seguente: c'è un modo carino per scoprire se il valore non è valido? – toth

+0

Purtroppo non ne sono a conoscenza. – Cornstalks

risposta

3

Un modo comune per farlo è quello di includere nel vostro enum un marcatore di fine

enum class Colors : char 
{ 
    Red, 
    Green, 
    Blue, 
    Last_Element 
} 

Usando questo approccio, durante la conversione, è possibile controllare se il valore che si sta utilizzando è inferiore al valore di Last_Element .

Si consideri la seguente funzione:

template<typename T> 
typename std::enable_if<std::is_enum<T>::value, bool>::type 
    IsValidEnumIntegral<T>(int integral) 
{ 
    return (int)(T::Last_Element) > integral; 
} 

e si potrebbe usare in questo modo:

if(IsValidEnumIntegral<Colors>(2)) 
    //do whatever 

Questo potrebbe funzionare per qualsiasi enum che hai fatto con un elemento chiamato Last_Element. Potresti andare oltre per creare una funzione simile per poi convertirti automaticamente per te.

Nota: questo non è testato. Al momento non sono in grado di farlo, ma penso che potrebbe funzionare.

EDIT: Funzionerà solo se l'enumerazione in questione utilizza un insieme di numeri interi senza spazi vuoti per i suoi elementi. La funzione fornita presuppone anche che l'enumerazione non contenga interi negativi, sebbene un First_Element possa essere facilmente aggiunto ad esso.

+0

Questo funzionerebbe solo se non ci sono buchi nell'enum. Se ci sono degli spazi vuoti, questo può ancora fallire. – NathanOliver

+0

@NathanOliver Non c'è una soluzione perfetta, devi accettare i limiti. – Barmar

+0

Se davvero hai bisogno di fare questo, il modo giusto sarebbe definire una classe il cui costruttore fa i controlli precisi che vuoi, piuttosto che usare un enum. – Barmar

3

[dcl.enum]/8:

Per un'enumerazione cui tipo sottostante è fisso, i valori della censimento sono i valori del tipo sottostante.

Questo include tutti scope enumerazioni, perché il tipo sottostante di un enum di ambito default per int:

Il tipo sottostante può essere specificato esplicitamente utilizzando un enum base. Per un tipo di enumerazione con ambito, il tipo sottostante è int se non è specificato . In entrambi i casi, il tipo sottostante è detto fisso.

Così, verificando che il valore di ingresso è compreso nell'intervallo di tipo sottostante dell'enumerazione (che è possibile controllare con std::numeric_limits e std::underlying_type), si può essere certi che il static_cast avrà sempre un comportamento ben definito .

Tuttavia, ciò non è sufficiente se il resto del programma non è pronto a gestire ogni valore compreso nell'intervallo del tipo sottostante all'enumerazione. In tal caso, dovrai convalidare te stesso, possibilmente con qualcosa sulla falsariga della risposta di @ Altainia.

+1

Non avevo mai letto attentamente le specifiche sull'enumerazione, e questo non è affatto quello che mi aspettavo. Ho imparato qualcosa oggi; grazie. – Nemo

+0

Questo è davvero un buon punto, aveva frainteso cosa significa "valori dell'enumerazione" nello standard. Grazie per averlo precisato! – toth

1

Un altro modo in cui ho visto questo tipo di cose fatte in alcune codebase è, fondamentalmente, usare un tratto di tipo per decorare le enumerazioni con scope con informazioni extra.

L'idea è che si avrebbe qualche tratto come

namespace mpl { 
    template <typename T> 
    struct GetEnumData; 
} 

Poi, quando si dichiara un'enum "intelligente", sarà anche specializzarsi questo tratto di collegarsi a una struttura che contiene metadati sul enum con ambito , come quello che sono i valori legali. Oppure, un elenco di stringhe corrispondenti ai nomi dei valori enum, in modo da poter eseguire la conversione enum-to-string e viceversa.

enum class my_enum { a, b, c }; 
namespace mpl { 
    template<> 
    struct GetEnumData<my_enum> { 
    static constexpr std::size_t my_number = 3; 
    static const char * const my_strings []() { 
     return {"a", "b", "c"}; 
    } 
    static const int my_values []() { 
     return {0, 1, 2}; 
    } 
    } 
} // end namespace mpl 

N.B. Generare il tipo di carattere sopra è un po 'noioso, quindi invariabilmente, si finisce per utilizzare alcune macro per le dichiarazioni "smart enum". Se davvero non ti piacciono le macro, questo approccio non fa per te. Almeno fino a quando C++ aggiunge alcune funzionalità di introspezione nelle versioni future (dita incrociate).

Una volta che si dispone di questo tipo di tratto, è possibile fare cose utili come "enum_cast" che analizzerà una stringa per un enum, per esempio.

template <typename T> 
T enum_cast(const std::string & input) { 
    using data = GetEnumData<T>; 

    for (std::size_t idx = 0; idx < data::my_number; ++idx) { 
    if (data::my_strings()[idx] == input) { 
     return static_cast<T>(data::my_values()[idx]); 
    } 
    } 
    throw bad_enum_value(input); 
} 

E si potrebbe fare qualcosa di simile per int 's o altri tipi integrali.

Problemi correlati