2013-04-16 2 views
7

Questa domanda potrebbe essere l'ennesima iterazione di "How to map strings to enums".Tempo di compilazione controllare che stringa a enum map sia completa

Le mie esigenze vanno un po 'oltre e voglio throw una certa eccezione quando non si trova una chiave nell'intervallo di input validi. Così ho questa implementazione di questo EnumMap (bisogni spinta per const std::map definizione):

#include <map> 
#include <string> 
#include <sstream> 
#include <stdexcept> 
#include <boost/assign.hpp> 

typedef enum colors { 
    RED, 
    GREEN, 
} colors; 
// boost::assign::map_list_of 
const std::map<std::string,int> colorsMap = boost::assign::map_list_of 
              ("red", RED) 
              ("green", GREEN); 
//----------------------------------------------------------------------------- 
// wrapper for a map std::string --> enum 
class EnumMap { 
private: 
    std::map<std::string,int> m_map; 
    // print the map to a string 
    std::string toString() const { 
    std::string ostr; 
    for(auto x : m_map) { 
     ostr += x.first + ", "; 
    } 
    return ostr; 
    } 
public: 
    // constructor 
    EnumMap(const std::map<std::string,int> &m) : m_map(m) { } 
    // access 
    int at(const std::string &str_type) { 
    try{ 
     return m_map.at(str_type); 
    } 
    catch(std::out_of_range) { 
     throw(str_type + " is not a valid input, try : " + toString()); 
    } 
    catch(...) { 
     throw("Unknown exception"); 
    } 
    } 
}; 
//----------------------------------------------------------------------------- 
int main() 
{ 
    EnumMap aColorMap(colorsMap); 
    try { 
    aColorMap.at("red"); // ok 
    aColorMap.at("yellow"); // exception : "yellow is not a valid input ..." 
    } 
    catch(std::string &ex) { 
    std::cout << ex << std::endl; 
    } 
    return 0; 
} 

questo funziona bene e fa quello che mi serve. Ora, voglio rendere possibile sapere al momento della compilazione che tutti gli elementi di un certo enum sono passati al costruttore EnumMap e anche che tutti gli elementi nello enum sono abbinati alla stringa corrispondente.

ho provato con std::initializer_list e static_assert, ma sembra che VC2010 non ancora supporta std::initializer_list (vedi here).

Qualcuno ha un'idea su come sarebbe possibile implementarlo? Forse con i modelli o implementando la mia classe Enum?

+1

Come ospite testare il codice prima della spedizione, giusto? Quindi un controllo di runtime sarebbe sufficiente per assicurarsi che funzioni, non credi? –

+0

L'unico costrutto di linguaggio che conosco che ti avviserà se hai perso un valore enum è 'switch' se aggiungi un' caso' per ogni valore e non hai un ramo 'default'. Immagino che questo significhi che avrai bisogno di una macro ma sono lontano dal suggerire qualcosa di specifico :) –

+0

Sono d'accordo che i test di run-time dovrebbero essere sufficienti, e so anche dell'avviso. Penso che sto solo cercando di capire fino a che punto si può spingere fuori – FKaria

risposta

2

Qualcuno ha un'idea su come sarebbe possibile implementarlo? Forse con i modelli o implementando la mia classe Enum?

Non è possibile fare. Non con std :: map, e non con meta-template template.

4
typedef enum colors { 
    MIN_COLOR, 
    RED = MIN_COLOR, 
    GREEN, 
    MAX_COLOR 
} colors; 

template< colors C > 
struct MapEntry { 
    std::string s; 
    MapEntry(std::string s_):s(s_) {} 
}; 
void do_in_order() {} 
template<typename F0, typename... Fs> 
void do_in_order(F0&& f0, Fs&&... fs) { 
    std::forward<F0>(f0)(); 
    do_in_order(std::forward<Fs>(fs)...); 
} 
struct MapInit { 
    std::map< std::string, color > retval; 
    operator std::map< std::string, color >() { 
    return std::move(retval); 
    } 
    template<colors C> 
    void AddToMap(MapEntry<C>&& ent) { 
    retval.insert(std::make_pair(std::move(end.s), C)); 
    } 
    template< typename... Entries > 
    MapInit(Entries&& entries) { 
    do_in_order([&](){ AddToMap(entries); }...); 
    } 
}; 
template<typename... Entries> 
MapInit mapInit(Entries&&... entries) { 
    return MapInit(std::forward<Entries>(entries)...); 
} 
const std::map<std::string, colors> = mapInit(MapEntry<RED>("red"), MapEntry<GREEN>("green")); 

che ti dà un modo C++ 11 per costruire una std::map dal momento della compilazione color e dati string run-time.

Inserire una "lista di MapEntry<colors> nell'elenco" colors "metafunzione successiva.

template<colors... Cs> 
struct color_list {}; 
template<typename... Ts> 
struct type_list {}; 
template<typename MapEnt> 
struct extract_color; 
template<colors C> 
struct extract_color<MapEntry<C>> { 
    enum {value=C}; 
}; 
template<typename Entries> 
struct extract_colors; 
template<typename... MapEntries> 
struct extract_colors<type_list<MapEntries...>> { 
    typedef color_list< ((colors)extract_colors<MapEntries>::value)... > type; 
}; 

Ordinare quell'elenco. Rileva duplicati: se ci sono, hai sbagliato.

L'ordinamento in fase di compilazione è più difficile rispetto al resto di questo e ad oltre 100 righe di codice. Lo lascerò fuori se non ti dispiace troppo! Here is a compile time merge sort I wrote in the past to answer a stack overflow question che funzionerebbe con un adattamento relativamente semplice (ordina tipi con valori, in questo caso stiamo ordinando direttamente un elenco di valori in fase di compilazione).

// takes a sorted list of type L<T...>, returns true if there are adjacent equal 
// elements: 
template<typename clist, typename=void> 
struct any_duplicates:std::false_type {}; 
template<typename T, template<T...>class L, T t0, T t1, T... ts> 
struct any_duplicates< L<t0, t1, ts...>, typename std::enable_if<t0==t1>::type>: 
    std::true_type {}; 
template<typename T, template<T...>class L, T t0, T t1, T... ts> 
struct any_duplicates< L<t0, t1, ts...>, typename std::enable_if<t0!=t1>::type>: 
    any_duplicates< L<t1, ts...> > {}; 

Detect elementi di fuori della gamma valida di colors (cioè <MIN_COLOR o >=MAX_COLOR). Se è così, hai sbagliato.

template<typename List> 
struct min_max; 
template<typename T, template<T...>class L, T t0> 
struct min_max { 
    enum { 
    min = t0, 
    max = t1, 
    }; 
}; 
template<typename T, template<T...>class L, T t0, T t1, T... ts> 
struct min_max { 
    typedef min_max<L<t1, ts...>> rest_of_list; 
    enum { 
    rest_min = rest_of_list::min, 
    rest_max = rest_of_list::max, 
    min = (rest_min < t0):rest_min:t0, 
    max = (rest_max > t0):rest_max:t0, 
    }; 
}; 
template< typename T, T min, T max, typename List > 
struct bounded: std::integral_constant< bool, 
    (min_max<List>::min >= min) && (min_max<List>::max < max) 
> {}; 

contare quanti elementi ci sono - ci dovrebbe essere MAX_COLOR elementi. Altrimenti, hai sbagliato.

template<typename List> 
struct element_count; 
template<typename T, template<T...>L, T... ts> 
struct element_count<L<ts...>>:std::integral_constant< std::size_t, sizeof...(ts) > {}; 

Se nessuno di questi si è verificato, è necessario aver inizializzato ciascuno di essi in una casella.

L'unica cosa che manca è che si potrebbe essere andati fuori e utilizzato lo stesso string per due valori.Poiché il tempo di compilazione string s è un problema, è sufficiente controllare questo in fase di esecuzione (che il numero di voci nel map corrisponde al numero di colors dopo averlo inizializzato).

Fare questo in C++ 03 sarà più difficile. Ti mancano modelli variardici, quindi finisci per doverli falsificare. Che è un dolore mpl potrebbe essere in grado di aiutarti.

I modelli variardici sono disponibili nell'aggiornamento del compilatore CTP MSVC Nov 2012.

Ecco un esempio di giocattolo senza controllo duplicato e senza controllo dei limiti (controlla solo che il numero di voci della mappa corrisponda);

#include <cstddef> 
#include <utility> 
#include <string> 
#include <map> 

enum TestEnum { 
    BeginVal = 0, 
    One = BeginVal, 
    Two, 
    Three, 
    EndVal 
}; 

template<TestEnum e> 
struct MapEntry { 
    enum { val = e }; 
    std::string s; 
    MapEntry(std::string s_):s(s_) {} 
}; 

void do_in_order() {} 
template<typename F0, typename... Fs> 
void do_in_order(F0&& f0, Fs&&... fs) { 
    std::forward<F0>(f0)(); 
    do_in_order(std::forward<Fs>(fs)...); 
} 

template<typename... MapEntries> 
struct count_entries:std::integral_constant< std::size_t, sizeof...(MapEntries) > {}; 

// should also detect duplicates and check the range of the values: 
template<typename... MapEntries> 
struct caught_them_all: 
    std::integral_constant< 
    bool, 
    count_entries<MapEntries...>::value == (TestEnum::EndVal-TestEnum::BeginVal) 
    > 
{}; 

struct BuildMap { 
    typedef std::map<std::string, TestEnum> result_map; 
    mutable result_map val; 
    operator result_map() const { 
    return std::move(val); 
    } 
    template<typename... MapEntries> 
    BuildMap(MapEntries&&... entries) { 
    static_assert(caught_them_all<MapEntries...>::value, "Missing enum value"); 
    bool _[] = { ((val[ entries.s ] = TestEnum(MapEntries::val)), false)... }; 
    } 
}; 

std::map< std::string, TestEnum > bob = BuildMap(
    MapEntry<One>("One") 
    ,MapEntry<Two>("Two") 
#if 0 
    ,MapEntry<Three>("Three") 
#endif 
); 

int main() {} 

Sostituire la #if 0 con #if 1 per guardarlo compilare. Live link se vuoi giocare.

+0

non hai mostrato come usarlo per ottenere un errore di compilazione –

+0

'static_assert (clausola," le cose non vanno bene ")' è il modo C++ 11 per ottenere un errore di compilazione. Oppure intendevi test di ordinamento in fase di compilazione e test di unicità, controllo dei limiti e classi di tratti simili? – Yakk

+0

Ottima risposta. Sembra che ciò funzionerebbe solo per uno specifico tipo 'enum', ovvero' colors'. Sembra che questo possa essere il più vicino possibile per ottenere ciò che sto cercando di fare, e nessuna soluzione più semplice sembra possibile visto che stai usando un sacco di wapons pesanti. Sto pensando che l'unica altra alternativa sarebbe quella di implementare una classe 'Enum' personalizzata e la' EnumMap' personalizzata per contenere i suoi elementi. – FKaria

0

Voglio postare la soluzione Sono arrivato finalmente. Non voglio contrassegnarlo come una risposta definitiva perché mi piacerebbe capire se siamo in grado di definire i vettori di stringhe in fase di compilazione.

// 
// Cumstom Enum Header File 
// 

#include <vector> 
#include <string> 

#include <boost/algorithm/string/classification.hpp> 
#include <boost/algorithm/string/split.hpp> 
#include <boost/range/algorithm/find.hpp> 

std::vector<std::string> split_to_vector(std::string s) 
{ 
    // splits a comma separated string to a vector of strings 
    std::vector<std::string> v; 
    boost::split(v, s, boost::is_any_of(", "), boost::token_compress_on); 
    return v; 
} 

#define ENUM_TO_STRING(X,...) \ 
    struct X { \ 
     enum Enum {__VA_ARGS__}; \ 
     static \ 
     std::string to_string(X::Enum k) { \ 
      std::vector<std::string> keys = split_to_vector(#__VA_ARGS__); \ 
      return keys[static_cast<int>(k)]; \ 
     } \ 
     static \ 
     X::Enum which_one(const std::string s) { \ 
      std::vector<std::string> keys = split_to_vector(#__VA_ARGS__); \ 
      auto it = boost::find(keys, s); \ 
      if(it == keys.end()) { \ 
       throw("not a valid key"); \ 
      } \ 
      return static_cast<X::Enum>(it - keys.begin()); \ 
     } \ 
    } 


// 
// Usage 
// 

#include <iostream> 

ENUM_TO_STRING(Color, Red, Green, Blue); 

int main() 
{ 
    std::string red_s = Color::to_string(Color::Red); 
    std::cout << red_s << std::endl; 

    Color::Enum red_e = Color::which_one("Red"); 
    std::cout << red_e << std::endl; 

    // won't compile 
    // std::string yellow_s = Colors::to_string(Colors::Yellow); 

    // run-time error 
    // Color::Enum yellow_e = Colors::which_one("Yellow"); 
} 

eseguire sul Coliru: http://coliru.stacked-crooked.com/a/e81e1af0145df99a

+0

Puoi farlo compilare-tempo. Vedi [qui] (http://stackoverflow.com/questions/28828957/enum-to-string-in-modern-c-and-future-c17/31362042#31362042). Esiste anche una variante di tale soluzione con 'to_string' in fase di compilazione'. – antron

+0

Grazie a @ antron. Ho anche trovato un modo per definire il vettore al momento della compilazione usando la libreria del preprocessore Boost, ma non ho semplicemente aggiornato questa risposta. Ho appena pubblicato la mia soluzione in [quel thread] (http://stackoverflow.com/a/35788543/1405424). Credo che sia simile alla tua soluzione, tranne per il fatto che ha usato il preprocessore Boost per le macro. Ho anche omesso la stringa -> conversione enum perché la domanda richiede solo enum -> stringa. – FKaria