Como mapear facilmente enums de c ++ para strings

Eu tenho um monte de tipos enum em alguns arquivos de header de biblioteca que eu estou usando e eu quero ter uma maneira de converter valores de enum para seqüências de usuário – e vice-versa.

O RTTI não faz isso por mim, porque as ‘strings de usuário’ precisam ser um pouco mais legíveis do que as enumerações.

Uma solução de força bruta seria um monte de funções como esta, mas eu sinto que é um pouco C-like.

enum MyEnum {VAL1, VAL2,VAL3}; String getStringFromEnum(MyEnum e) { switch e { case VAL1: return "Value 1"; case VAL2: return "Value 2"; case VAL1: return "Value 3"; default: throw Exception("Bad MyEnum"); } } 

Tenho a impressão de que há uma solução elegante usando modelos, mas ainda não consigo me acostumar com isso.

ATUALIZAÇÃO: Obrigado por sugestões – eu deveria ter deixado claro que as enums são definidas em um header de biblioteca de terceiros, portanto, não quero alterar a definição delas.

Minha intuição agora é evitar modelos e fazer algo assim:

 char * MyGetValue(int v, char *tmp); // implementation is trivial #define ENUM_MAP(type, strings) char * getStringValue(const type &T) \ { \ return MyGetValue((int)T, strings); \ } ; enum eee {AA,BB,CC}; - exists in library header file ; enum fff {DD,GG,HH}; ENUM_MAP(eee,"AA|BB|CC") ENUM_MAP(fff,"DD|GG|HH") // To use... eee e; fff f; std::cout<< getStringValue(e); std::cout<< getStringValue(f); 

Se você quiser que o enum se nomeie como string, veja este post . Caso contrário, um std::map funcionará bem. (Nenhum ponto em copiar seus literais de string para std :: strings no mapa)

Para extra acréscimo sintático, aqui está como escrever uma class map_init. O objective é permitir

 std::map MyMap; map_init(MyMap) (eValue1, "A") (eValue2, "B") (eValue3, "C") ; 

O template map_init(T&) function template map_init(T&) retorna um map_init_helper . map_init_helper armazena um T &, e define o map_init_helper& operator()(typename T::key_type const&, typename T::value_type const&) trivial map_init_helper& operator()(typename T::key_type const&, typename T::value_type const&) . (Retornando *this do operator() permite o encadeamento do operator() , como operator< < em std::ostream s)

 template struct map_init_helper { T& data; map_init_helper(T& d) : data(d) {} map_init_helper& operator() (typename T::key_type const& key, typename T::mapped_type const& value) { data[key] = value; return *this; } }; template map_init_helper map_init(T& item) { return map_init_helper(item); } 

Como a function e a class auxiliar são modeladas, você pode usá-las para qualquer mapa ou estrutura semelhante a um mapa. Ou seja, ele também pode adicionar inputs para std::unordered_map

Se você não gosta de escrever esses helpers, boost :: assign oferece a mesma funcionalidade pronta para uso.

A solução MSalters é boa, mas basicamente boost::assign::map_list_of . Se você tem impulso, você pode usá-lo diretamente:

 #include  #include  #include  using boost::assign::map_list_of; enum eee { AA,BB,CC }; const boost::unordered_map eeeToString = map_list_of (AA, "AA") (BB, "BB") (CC, "CC"); int main() { std::cout < < " enum AA = " << eeeToString.at(AA) << std::endl; return 0; } 

Gerar automaticamente um formulário a partir do outro.

Fonte:

 enum { VALUE1, /* value 1 */ VALUE2, /* value 2 */ }; 

Gerado:

 const char* enum2str[] = { "value 1", /* VALUE1 */ "value 2", /* VALUE2 */ }; 

Se os valores de enum forem grandes, um formulário gerado poderia usar unordered_map <> ou modelos, conforme sugerido por Constantin.

Fonte:

 enum State{ state0 = 0, /* state 0 */ state1 = 1, /* state 1 */ state2 = 2, /* state 2 */ state3 = 4, /* state 3 */ state16 = 0x10000, /* state 16 */ }; 

Gerado:

 template  struct enum2str { static const char * const value; }; template  const char * const enum2str::value = "error"; template <> struct enum2str { static const char * const value; }; const char * const enum2str::value = "state 0"; 

Exemplo:

 #include  int main() { std::cout < < enum2str::value < < std::endl; return 0; } 

Sugiro que uma mistura de macros X é a melhor solução e as seguintes funções de modelo:

Para emprestar off marcinkoziukmyopenidcom e estendido

 enum Colours { # define X(a) a, # include "colours.def" # undef X ColoursCount }; char const* const colours_str[] = { # define X(a) #a, # include "colours.def" # undef X 0 }; template  T str2enum( const char* ); template  const char* enum2str( T ); #define STR2ENUM(TYPE,ARRAY) \ template <> \ TYPE str2enum( const char* str ) \ { \ for( int i = 0; i < (sizeof(ARRAY)/sizeof(ARRAY[0])); i++ ) \ if( !strcmp( ARRAY[i], str ) ) \ return TYPE(i); \ return TYPE(0); \ } #define ENUM2STR(TYPE,ARRAY) \ template <> \ const char* enum2str( TYPE v ) \ { \ return ARRAY[v]; \ } #define ENUMANDSTR(TYPE,ARRAY)\ STR2ENUM(TYPE,ARRAY) \ ENUM2STR(TYPE,ARRAY) ENUMANDSTR(Colours,colours_str) 

colour.def

 X(Red) X(Green) X(Blue) X(Cyan) X(Yellow) X(Magenta) 

Lembro-me de ter respondido isso em outro lugar no StackOverflow. Repetindo aqui. Basicamente, é uma solução baseada em macros variadic e é bastante fácil de usar:

 #define AWESOME_MAKE_ENUM(name, ...) enum class name { __VA_ARGS__, __COUNT}; \ inline std::ostream& operator< <(std::ostream& os, name value) { \ std::string enumName = #name; \ std::string str = #__VA_ARGS__; \ int len = str.length(); \ std::vector strings; \ std::ostringstream temp; \ for(int i = 0; i < len; i ++) { \ if(isspace(str[i])) continue; \ else if(str[i] == ',') { \ strings.push_back(temp.str()); \ temp.str(std::string());\ } \ else temp<< str[i]; \ } \ strings.push_back(temp.str()); \ os << enumName << "::" << strings[static_cast(value)]; \ return os;} 

Para usá-lo em seu código, simplesmente faça:

 AWESOME_MAKE_ENUM(Animal, DOG, CAT, HORSE ); auto dog = Animal::DOG; std::cout<  

Se você quiser obter representações de string de variables MyEnum , os modelos não irão cortá-lo. O modelo pode ser especializado em valores integrais conhecidos em tempo de compilation.

No entanto, se é isso que você quer, tente:

 #include  enum MyEnum { VAL1, VAL2 }; template struct StrMyEnum { static char const* name() { return "Unknown"; } }; #define STRENUM(val, str) \ template<> struct StrMyEnum { \ static char const* name() { return str; }}; STRENUM(VAL1, "Value 1"); STRENUM(VAL2, "Value 2"); int main() { std::cout < < StrMyEnum::name(); } 

Isso é detalhado, mas detectará erros como o que você fez em questão – seu case VAL1 é duplicado.

Eu uso essa solução que reproduzo abaixo:

 #define MACROSTR(k) #k #define X_NUMBERS \ X(kZero ) \ X(kOne ) \ X(kTwo ) \ X(kThree ) \ X(kFour ) \ X(kMax ) enum { #define X(Enum) Enum, X_NUMBERS #undef X } kConst; static char *kConstStr[] = { #define X(String) MACROSTR(String), X_NUMBERS #undef X }; int main(void) { int k; printf("Hello World!\n\n"); for (k = 0; k < kMax; k++) { printf("%s\n", kConstStr[k]); } return 0; } 

Eu ficaria tentado a ter um mapa m – e incorporar isso no enum.

setup com m [MyEnum.VAL1] = “Valor 1”;

e tudo está feito.

Eu solicitei essa funcionalidade várias vezes para depurar / analisar código de outras pessoas. Para isso, eu escrevi um script Perl que gera uma class com vários methods toString sobrecarregados. Cada método toString usa um Enum como um argumento e retorna const char* .

Naturalmente, o script não analisa C ++ para enums em si, mas usa ctags para gerar tabelas de símbolos.

O script Perl está aqui: http://heinitz-it.de/download/enum2string/enum2string.pl.html

Suas respostas me inspiraram a escrever algumas macros. Meus requisitos eram os seguintes:

  1. apenas escreva cada valor do enum uma vez, portanto, não há listas duplas para manter

  2. Não mantenha os valores de enumeração em um arquivo separado que seja depois #incluído, para que eu possa escrevê-lo onde quiser

  3. não substitui o enum em si, eu ainda quero ter o tipo enum definido, mas além disso eu quero ser capaz de mapear cada nome enum para a seqüência correspondente (para não afetar o código legado)

  4. a busca deve ser rápida, então, de preferência, nenhum switch-case, para aquelas enormes enums

Esse código cria um enum clássico com alguns valores. Além disso, cria como std :: map que mapeia cada valor de enum para seu nome (por exemplo, map [E_SUNDAY] = “E_SUNDAY”, etc.)

Ok, aqui está o código agora:

EnumUtilsImpl.h :

 map & operator , (map & dest, const pair & keyValue) { dest[keyValue.first] = keyValue.second; return dest; } #define ADD_TO_MAP(name, value) pair(name, #name) 

EnumUtils.h // este é o arquivo que você deseja include sempre que precisar fazer essas coisas, você usará as macros a partir dele:

 #include "EnumUtilsImpl.h" #define ADD_TO_ENUM(name, value) \ name value #define MAKE_ENUM_MAP_GLOBAL(values, mapName) \ int __makeMap##mapName() {mapName, values(ADD_TO_MAP); return 0;} \ int __makeMapTmp##mapName = __makeMap##mapName(); #define MAKE_ENUM_MAP(values, mapName) \ mapName, values(ADD_TO_MAP); 

MyProjectCodeFile.h // este é um exemplo de como usá-lo para criar um enum personalizado:

 #include "EnumUtils.h* #define MyEnumValues(ADD) \ ADD(val1, ), \ ADD(val2, ), \ ADD(val3, = 100), \ ADD(val4, ) enum MyEnum { MyEnumValues(ADD_TO_ENUM) }; map MyEnumStrings; // this is how you initialize it outside any function MAKE_ENUM_MAP_GLOBAL(MyEnumValues, MyEnumStrings); void MyInitializationMethod() { // or you can initialize it inside one of your functions/methods MAKE_ENUM_MAP(MyEnumValues, MyEnumStrings); } 

Felicidades.

Aqui está uma tentativa de obter operadores de stream < < e >> automaticamente em enum com um comando de macro de uma linha apenas …

Definições:

 #include  #include  #include  #include  #include  #include  #include  #define MAKE_STRING(str, ...) #str, MAKE_STRING1_(__VA_ARGS__) #define MAKE_STRING1_(str, ...) #str, MAKE_STRING2_(__VA_ARGS__) #define MAKE_STRING2_(str, ...) #str, MAKE_STRING3_(__VA_ARGS__) #define MAKE_STRING3_(str, ...) #str, MAKE_STRING4_(__VA_ARGS__) #define MAKE_STRING4_(str, ...) #str, MAKE_STRING5_(__VA_ARGS__) #define MAKE_STRING5_(str, ...) #str, MAKE_STRING6_(__VA_ARGS__) #define MAKE_STRING6_(str, ...) #str, MAKE_STRING7_(__VA_ARGS__) #define MAKE_STRING7_(str, ...) #str, MAKE_STRING8_(__VA_ARGS__) #define MAKE_STRING8_(str, ...) #str, MAKE_STRING9_(__VA_ARGS__) #define MAKE_STRING9_(str, ...) #str, MAKE_STRING10_(__VA_ARGS__) #define MAKE_STRING10_(str) #str #define MAKE_ENUM(name, ...) MAKE_ENUM_(, name, __VA_ARGS__) #define MAKE_CLASS_ENUM(name, ...) MAKE_ENUM_(friend, name, __VA_ARGS__) #define MAKE_ENUM_(attribute, name, ...) name { __VA_ARGS__ }; \ attribute std::istream& operator>>(std::istream& is, name& e) { \ const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \ std::string str; \ std::istream& r = is >> str; \ const size_t len = sizeof(name##Str)/sizeof(name##Str[0]); \ const std::vector enumStr(name##Str, name##Str + len); \ const std::vector::const_iterator it = std::find(enumStr.begin(), enumStr.end(), str); \ if (it != enumStr.end())\ e = name(it - enumStr.begin()); \ else \ throw std::runtime_error("Value \"" + str + "\" is not part of enum "#name); \ return r; \ }; \ attribute std::ostream& operator< <(std::ostream& os, const name& e) { \ const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \ return (os << name##Str[e]); \ } 

Uso:

 // Declare global enum enum MAKE_ENUM(Test3, Item13, Item23, Item33, Itdsdgem43); class Essai { public: // Declare enum inside class enum MAKE_CLASS_ENUM(Test, Item1, Item2, Item3, Itdsdgem4); }; int main() { std::cout < < Essai::Item1 << std::endl; Essai::Test ddd = Essai::Item1; std::cout << ddd << std::endl; std::istringstream strm("Item2"); strm >> ddd; std::cout < < (int) ddd << std::endl; std::cout << ddd << std::endl; } 

Não tenho certeza sobre as limitações deste esquema ... comentários são bem-vindos!

Eu só queria mostrar essa possível solução elegante usando macros. Isso não resolve o problema, mas acho que é uma boa maneira de repensar o problema.

 #define MY_LIST(X) X(value1), X(value2), X(value3) enum eMyEnum { MY_LIST(PLAIN) }; const char *szMyEnum[] = { MY_LIST(STRINGY) }; int main(int argc, char *argv[]) { std::cout < < szMyEnum[value1] << value1 <<" " << szMyEnum[value2] << value2 << std::endl; return 0; } 

---- EDITAR ----

Depois de algumas pesquisas na internet e alguns experimentos próprios, cheguei à seguinte solução:

 //this is the enum definition #define COLOR_LIST(X) \ X( RED ,=21) \ X( GREEN ) \ X( BLUE ) \ X( PURPLE , =242) \ X( ORANGE ) \ X( YELLOW ) //these are the macros #define enumfunc(enums,value) enums, #define enumfunc2(enums,value) enums value, #define ENUM2SWITCHCASE(enums) case(enums): return #enums; #define AUTOENUM(enumname,listname) enum enumname{listname(enumfunc2)}; #define ENUM2STRTABLE(funname,listname) char* funname(int val) {switch(val) {listname(ENUM2SWITCHCASE) default: return "undef";}} #define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int values[] = {listname(enumfunc)};int N = sizeof(values)/sizeof(int);ENUM2STRTABLE(enum2str,listname)}; //here the enum and the string enum map table are generated AUTOENUM(testenum,COLOR_LIST) ENUM2STRTABLE(testfunenum,COLOR_LIST) ENUM2STRUCTINFO(colorinfo,COLOR_LIST)//colorinfo structur {int values[]; int N; char * enum2str(int);} //debug macros #define str(a) #a #define xstr(a) str(a) int main( int argc, char** argv ) { testenum x = YELLOW; std::cout < < testfunenum(GREEN) << " " << testfunenum(PURPLE) << PURPLE << " " << testfunenum(x); for (int i=0;i< colorinfo::N;i++) std::cout << std::endl << colorinfo::values[i] << " "<< colorinfo::enum2str(colorinfo::values[i]); return EXIT_SUCCESS; } 

Eu só queria postar, talvez alguém pudesse achar essa solução útil. Não há necessidade de classs de modelos sem necessidade de c ++ 11 e não há necessidade de reforço, então isso também pode ser usado para simples C.

---- EDIT2 ----

a tabela de informações pode produzir alguns problemas ao usar mais de 2 enums (problema do compilador). A seguinte solução alternativa funcionou:

 #define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int spacename##_##values[] = {listname(enumfunc)};int spacename##_##N = sizeof(spacename##_##values)/sizeof(int);ENUM2STRTABLE(spacename##_##enum2str,listname)}; 
 typedef enum { ERR_CODE_OK = 0, ERR_CODE_SNAP, ERR_CODE_NUM } ERR_CODE; const char* g_err_msg[ERR_CODE_NUM] = { /* ERR_CODE_OK */ "OK", /* ERR_CODE_SNAP */ "Oh, snap!", }; 

Acima está minha solução simples. Uma vantagem disso é o ‘NUM’, que controla o tamanho da matriz de mensagens, e também impede o access fora dos limites (se você usá-lo com sabedoria).

Você também pode definir uma function para obter a string:

 const char* get_err_msg(ERR_CODE code) { return g_err_msg[code]; } 

Além da minha solução, achei o seguinte bastante interessante. Geralmente resolveu o problema de synchronization do problema acima.

Slides aqui: http://www.slideshare.net/arunksaha/touchless-enum-tostring-28684724

Código aqui: https://github.com/arunksaha/enum_to_string

no header:

 enum EFooOptions { FooOptionsA = 0, EFooOptionsMin = 0, FooOptionsB, FooOptionsC, FooOptionsD EFooOptionsMax }; extern const wchar* FOO_OPTIONS[EFooOptionsMax]; 

no arquivo .cpp:

 const wchar* FOO_OPTIONS[] = { L"One", L"Two", L"Three", L"Four" }; 

Advertência: não lide com o índice de array inválido. 🙂 Mas você pode facilmente adicionar uma function para verificar o enum antes de obter a string da matriz.

Recentemente tive o mesmo problema com uma biblioteca de fornecedores (Fincad). Felizmente, o fornecedor forneceu a documentação XML para todos os enums. Acabei gerando um mapa para cada tipo de enum e fornecendo uma function de pesquisa para cada enum. Essa técnica também permite interceptar uma pesquisa fora do intervalo do enum.

Tenho certeza que o swig poderia fazer algo parecido para você, mas estou feliz em fornecer os utilitários de geração de código que estão escritos em ruby.

Aqui está uma amostra do código:

 std::map init_FCSW2_map() { std::map ans; ans["Act365Fixed"] = FCSW2::Act365Fixed; ans["actual/365 (fixed)"] = FCSW2::Act365Fixed; ans["Act360"] = FCSW2::Act360; ans["actual/360"] = FCSW2::Act360; ans["Act365Act"] = FCSW2::Act365Act; ans["actual/365 (actual)"] = FCSW2::Act365Act; ans["ISDA30360"] = FCSW2::ISDA30360; ans["30/360 (ISDA)"] = FCSW2::ISDA30360; ans["ISMA30E360"] = FCSW2::ISMA30E360; ans["30E/360 (30/360 ISMA)"] = FCSW2::ISMA30E360; return ans; } switches::FCSW2::type FCSW2_lookup(const char* fincad_switch) { static std::map switch_map = init_FCSW2_map(); std::map::iterator it = switch_map.find(fincad_switch); if(it != switch_map.end()) { return it->second; } else { throw FCSwitchLookupError("Bad Match: FCSW2"); } } 

Parece que você quer ir para o outro lado (enum para string, em vez de string para enum), mas isso deve ser trivial para reverter.

-Whit

Veja se a seguinte syntax combina com você:

 // WeekEnd enumeration enum WeekEnd { Sunday = 1, Saturday = 7 }; // String support for WeekEnd Begin_Enum_String( WeekEnd ) { Enum_String( Sunday ); Enum_String( Saturday ); } End_Enum_String; // Convert from WeekEnd to string const std::string &str = EnumString::From( Saturday ); // str should now be "Saturday" // Convert from string to WeekEnd WeekEnd w; EnumString::To( w, "Sunday" ); // w should now be Sunday 

Em caso afirmativo, talvez você queira conferir este artigo:
http://www.gamedev.net/reference/snippets/features/cppstringizing/

 enum MyEnum { VAL1, VAL2,VAL3 }; #define StringMyEnum(e) ({__unused MyEnum _e = e;std::string(#e);}) #define CStringMyEnum(e) ({__unused MyEnum _e = e;#e;}) 

__unused é um atributo no GCC / LLVM, então você pode usar assim:

 std::string s = StringMyEnum(VAL1); const char *c = CStringMyEnum(VAL1);