Existe uma maneira simples de converter enum C ++ em string?


123

Suponha que tenhamos algumas enumerações nomeadas:

enum MyEnum {
      FOO,
      BAR = 0x50
};

O que eu pesquisei no Google é um script (qualquer idioma) que varre todos os cabeçalhos do meu projeto e gera um cabeçalho com uma função por enumeração.

char* enum_to_string(MyEnum t);

E uma implementação com algo como isto:

char* enum_to_string(MyEnum t){
      switch(t){
         case FOO:
            return "FOO";
         case BAR:
            return "BAR";
         default:
            return "INVALID ENUM";
      }
 }

A pegadinha é realmente com enumerações de tipo digitado e enumerações de estilo C sem nome. Alguém sabe alguma coisa para isso?

EDIT: A solução não deve modificar minha fonte, exceto para as funções geradas. As enumerações estão em uma API, portanto, usar as soluções propostas até agora não é apenas uma opção.


A resposta sobre a fábrica baseada em macro foi movida para stackoverflow.com/questions/147267/… - depois que a pergunta foi atualizada, ela não é mais relevante aqui.
Suma

Respostas:


48

Você pode conferir o GCCXML .

A execução do GCCXML no seu código de exemplo produz:

<GCC_XML>
  <Namespace id="_1" name="::" members="_3 " mangled="_Z2::"/>
  <Namespace id="_2" name="std" context="_1" members="" mangled="_Z3std"/>
  <Enumeration id="_3" name="MyEnum" context="_1" location="f0:1" file="f0" line="1">
    <EnumValue name="FOO" init="0"/>
    <EnumValue name="BAR" init="80"/>
  </Enumeration>
  <File id="f0" name="my_enum.h"/>
</GCC_XML>

Você pode usar qualquer idioma que preferir para extrair as tags Enumeration e EnumValue e gerar o código desejado.


Excelente! Funcionou como um encanto com um script python simples. Obrigado.
Edu Felipe

6
+1, o GCCXML parece muito bom! (Embora eu quase -1ed como eu inicialmente interpretou mal isso como uma sugestão para usar o acima detalhado sintaxe XML para codificar o seu enum - uma solução que cheira a overengineering)
j_random_hacker

1
alguma mudança você pode postar o script python?
Phillipwei 13/08/09

74

X-macros são a melhor solução. Exemplo:

#include <iostream>

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
};

std::ostream& operator<<(std::ostream& os, enum Colours c)
{
    if (c >= ColoursCount || c < 0) return os << "???";
    return os << colours_str[c];
}

int main()
{
    std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}

colours.def:

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

No entanto, eu geralmente prefiro o método a seguir, para que seja possível ajustar um pouco a string.

#define X(a, b) a,
#define X(a, b) b,

X(Red, "red")
X(Green, "green")
// etc.

11
bacana, embora eu não goste do arquivo extra #
Ronny Brendel

2
Apenas certifique-se seu processo de construção não #pragma não prepend (uma vez) antes de cada arquivo de inclusão ...
xtofl

24
Não tenho certeza da "melhor" solução!
Lightness Races in Orbit

2
Essa solução é muito superior a qualquer caso de switch ou matriz, porque não duplica os nomes, facilitando a alteração da enumeração.
Julien Guertault

2
@ ikku100 você está incorreto #define X(a, b) #b. Isso só é necessário se os olhares definição como este X(Red, red), em vez da definição mostrada na resposta,X(Red, "red")
learnvst

43

@hydroo: Sem o arquivo extra:

#define SOME_ENUM(DO) \
    DO(Foo) \
    DO(Bar) \
    DO(Baz)

#define MAKE_ENUM(VAR) VAR,
enum MetaSyntacticVariable{
    SOME_ENUM(MAKE_ENUM)
};

#define MAKE_STRINGS(VAR) #VAR,
const char* const MetaSyntacticVariableNames[] = {
    SOME_ENUM(MAKE_STRINGS)
};

Eu amo essa solução. Seria mais claro se SOME_UNION e MAKE_UNION fossem chamados SOME_ENUM e MAKE_ENUM.
de Bruno Martinez

Esta é uma otima soluçao. Eu tenho o gerenciador de recursos C ++ mais sustentável que eu já lidei.
DCurro

Devo agradecer por esta solução simples :-) - Eu fiz modificá-lo um pouco, porém, para ter a MetaSyntacticVariableNames[]parte, ser de uma declaração de classe, fazendo um métodostatic const char* getNameByEnum(MetaSyntacticVariable e) { /*code to return the static string*/ }
DeckerDK

Resposta fantástica! Simplifiquei ainda mais o agrupamento de MAKE_ENUM e MAKE_STRINGS em uma única macro, tornando todo o processo ainda mais simples. Eu adicionei uma resposta neste tópico com esse código, se alguém estiver interessado.
Francois Bertrand

35

O que costumo fazer é criar uma matriz C com os nomes na mesma ordem e posição dos valores da enumeração.

por exemplo.

enum colours { red, green, blue };
const char *colour_names[] = { "red", "green", "blue" };

então você pode usar a matriz em locais onde deseja um valor legível por humanos, por exemplo

colours mycolour = red;
cout << "the colour is" << colour_names[mycolour];

Você pode experimentar um pouco com o operador de stringing (consulte # na referência do pré-processador) que fará o que você deseja, em algumas circunstâncias - por exemplo:

#define printword(XX) cout << #XX;
printword(red);

irá imprimir "vermelho" para stdout. Infelizmente, não funcionará para uma variável (pois você imprimirá o nome da variável)


A última advertência (não funcionará para uma variável) é uma grande desvantagem, mas +1 de qualquer maneira.
Chappjc

3
Funciona apenas se você não definir valores numéricos especiais para enum entradas.
KYB

11

Eu tenho uma macro incrivelmente simples de usar que faz isso de uma maneira completamente SECA. Envolve macros variadicas e alguma mágica simples de análise. Aqui vai:

#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<std::string> 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<int>(value)]; \
return os;} 

Para usar isso em seu código, basta:

AWESOME_MAKE_ENUM(Animal,
    DOG,
    CAT,
    HORSE
);

1
Boa ideia usando uma enumeração fortemente tipada (classe enumeração). Aqui está uma demonstração: cpp.sh/4ife
chappjc

Isso funciona com enumerações / símbolos definidos externamente. Por exemplo, símbolos definidos pelo sistema operacional ou definidos pela biblioteca com lacunas na numeração?
211315 Jason Harrison

Muito bom, mas não compila se colocado dentro de uma classe (não consegui descobrir o porquê).
AlwaysLearning 02/12/2015

Não foi possível compilar isso no VS2015. Recebo um aviso e um erro: warning: comentário de várias linhas [-Wcomment] #define MAKE_ENUM (name, ...) nome da classe de enumeração { VA_ARGS , __COUNT} erro: stray '#' no programa std *: string enumName = #name
Craig.Feied

8

O QT é capaz de extrair isso de (graças ao compilador de objetos meta):

QNetworkReply::NetworkError error;

error = fetchStuff();

if (error != QNetworkReply::NoError) {

    QString errorValue;

    QMetaObject meta = QNetworkReply::staticMetaObject;

    for (int i=0; i < meta.enumeratorCount(); ++i) {

        QMetaEnum m = meta.enumerator(i);

        if (m.name() == QLatin1String("NetworkError")) {

            errorValue = QLatin1String(m.valueToKey(error));

            break;

        }

    }

    QMessageBox box(QMessageBox::Information, "Failed to fetch",

                "Fetching stuff failed with error '%1`").arg(errorValue),

                QMessageBox::Ok);

    box.exec();

    return 1;

}

No Qt, toda classe que possui a macro Q_OBJECT terá automaticamente um membro estático "staticMetaObject" do tipo QMetaObject. Você pode encontrar todo tipo de coisas legais, como propriedades, sinais, slots e até enums.

Fonte


7

Isso pode ser feito em C ++ 11

#include <map>
enum MyEnum { AA, BB, CC, DD };

static std::map< MyEnum, const char * > info = {
   {AA, "This is an apple"},
   {BB, "This is a book"},
   {CC, "This is a coffee"},
   {DD, "This is a door"}
};

void main()
{
    std::cout << info[AA] << endl
              << info[BB] << endl
              << info[CC] << endl
              << info[DD] << endl;
}

1
Isso não responde à pergunta do OP: ele estava procurando uma maneira de gerar automaticamente uma função para retornar o nome de um membro de enum como uma string.
Spooky

7

Acabei de reinventar esta roda hoje e pensei em compartilhá-la.

Essa implementação não requer nenhuma alteração no código que define as constantes, que podem ser enumerações ou #defines ou qualquer outra coisa que se refira a um número inteiro - no meu caso, eu tinha símbolos definidos em termos de outros símbolos. Também funciona bem com valores esparsos. Ele ainda permite vários nomes para o mesmo valor, retornando sempre o primeiro. A única desvantagem é que você precisa criar uma tabela com as constantes, que podem ficar desatualizadas à medida que novas são adicionadas, por exemplo.

struct IdAndName
{
   int          id;
   const char * name;
   bool operator<(const IdAndName &rhs) const { return id < rhs.id; }
};
#define ID_AND_NAME(x) { x, #x }

const char * IdToName(int id, IdAndName *table_begin, IdAndName *table_end)
{
   if ((table_end - table_begin) > 1 && table_begin[0].id > table_begin[1].id)
      std::stable_sort(table_begin, table_end);

   IdAndName searchee = { id, NULL };
   IdAndName *p = std::lower_bound(table_begin, table_end, searchee);
   return (p == table_end || p->id != id) ? NULL : p->name;
}

template<int N>
const char * IdToName(int id, IdAndName (&table)[N])
{
   return IdToName(id, &table[0], &table[N]);
}

Um exemplo de como você o usaria:

static IdAndName WindowsErrorTable[] =
{
   ID_AND_NAME(INT_MAX),               // flag value to indicate unsorted table
   ID_AND_NAME(NO_ERROR),
   ID_AND_NAME(ERROR_INVALID_FUNCTION),
   ID_AND_NAME(ERROR_FILE_NOT_FOUND),
   ID_AND_NAME(ERROR_PATH_NOT_FOUND),
   ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES),
   ID_AND_NAME(ERROR_ACCESS_DENIED),
   ID_AND_NAME(ERROR_INVALID_HANDLE),
   ID_AND_NAME(ERROR_ARENA_TRASHED),
   ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY),
   ID_AND_NAME(ERROR_INVALID_BLOCK),
   ID_AND_NAME(ERROR_BAD_ENVIRONMENT),
   ID_AND_NAME(ERROR_BAD_FORMAT),
   ID_AND_NAME(ERROR_INVALID_ACCESS),
   ID_AND_NAME(ERROR_INVALID_DATA),
   ID_AND_NAME(ERROR_INVALID_DRIVE),
   ID_AND_NAME(ERROR_CURRENT_DIRECTORY),
   ID_AND_NAME(ERROR_NOT_SAME_DEVICE),
   ID_AND_NAME(ERROR_NO_MORE_FILES)
};

const char * error_name = IdToName(GetLastError(), WindowsErrorTable);

A IdToNamefunção depende std::lower_boundde fazer pesquisas rápidas, o que requer que a tabela seja classificada. Se as duas primeiras entradas da tabela estiverem com defeito, a função a classificará automaticamente.

Edit: Um comentário me fez pensar em outra maneira de usar o mesmo princípio. Uma macro simplifica a geração de uma grande switchdeclaração.

#define ID_AND_NAME(x) case x: return #x

const char * WindowsErrorToName(int id)
{
    switch(id)
    {
        ID_AND_NAME(ERROR_INVALID_FUNCTION);
        ID_AND_NAME(ERROR_FILE_NOT_FOUND);
        ID_AND_NAME(ERROR_PATH_NOT_FOUND);
        ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES);
        ID_AND_NAME(ERROR_ACCESS_DENIED);
        ID_AND_NAME(ERROR_INVALID_HANDLE);
        ID_AND_NAME(ERROR_ARENA_TRASHED);
        ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY);
        ID_AND_NAME(ERROR_INVALID_BLOCK);
        ID_AND_NAME(ERROR_BAD_ENVIRONMENT);
        ID_AND_NAME(ERROR_BAD_FORMAT);
        ID_AND_NAME(ERROR_INVALID_ACCESS);
        ID_AND_NAME(ERROR_INVALID_DATA);
        ID_AND_NAME(ERROR_INVALID_DRIVE);
        ID_AND_NAME(ERROR_CURRENT_DIRECTORY);
        ID_AND_NAME(ERROR_NOT_SAME_DEVICE);
        ID_AND_NAME(ERROR_NO_MORE_FILES);
        default: return NULL;
    }
}

Boa solução. Mas para mim eu prefiro switch and case, pois é simples e fácil de entender.
Deqing

6
#define stringify( name ) # name

enum MyEnum {
    ENUMVAL1
};
...stuff...

stringify(EnumName::ENUMVAL1);  // Returns MyEnum::ENUMVAL1

Discussão adicional sobre este método

Diretrizes de diretiva de pré-processador para iniciantes


4
Na verdade, isso é bastante inútil, pois o método stringify está em tempo de compilação e é bastante literal. Se você diz ter o tipo de enum em questão dentro de uma variável, a tentativa de especificar a variável fornecerá apenas o nome da variável, não o nome do tipo de enum.
Srcpider

5

Interessante ver o número de maneiras. aqui está um que eu usei há muito tempo:

no arquivo myenummap.h:

#include <map>
#include <string>
enum test{ one, two, three, five=5, six, seven };
struct mymap : std::map<unsigned int, std::string>
{
  mymap()
  {
    this->operator[]( one ) = "ONE";
    this->operator[]( two ) = "TWO";
    this->operator[]( three ) = "THREE";
    this->operator[]( five ) = "FIVE";
    this->operator[]( six ) = "SIX";
    this->operator[]( seven ) = "SEVEN";
  };
  ~mymap(){};
};

em main.cpp

#include "myenummap.h"

...
mymap nummap;
std::cout<< nummap[ one ] << std::endl;

Não é const, mas é conveniente.

Aqui está outra maneira que usa os recursos do C ++ 11. Isso é const, não herda um contêiner STL e é um pouco mais organizado:

#include <vector>
#include <string>
#include <algorithm>
#include <iostream>

//These stay together and must be modified together
enum test{ one, two, three, five=5, six, seven };
std::string enum_to_str(test const& e)
{
    typedef std::pair<int,std::string> mapping;
    auto m = [](test const& e,std::string const& s){return mapping(static_cast<int>(e),s);}; 
    std::vector<mapping> const nummap = 
    { 
        m(one,"one"), 
        m(two,"two"), 
        m(three,"three"),
        m(five,"five"),
        m(six,"six"),
        m(seven,"seven"),
    };
    for(auto i  : nummap)
    {
        if(i.first==static_cast<int>(e))
        {
            return i.second;
        }
    }
    return "";
}

int main()
{
//  std::cout<< enum_to_str( 46 ) << std::endl; //compilation will fail
    std::cout<< "Invalid enum to string : [" << enum_to_str( test(46) ) << "]"<<std::endl; //returns an empty string
    std::cout<< "Enumval five to string : ["<< enum_to_str( five ) << "] "<< std::endl; //works
    return 0;
}

1
É perfeitamente legal. Eu faço isso o tempo todo.
Jonathan Graehl

Boa solução. Isso é c ++, então o uso do stl map está ok.
Adam Bruss

4
#include <stdarg.h>
#include <algorithm>
#include <string> 
#include <vector>
#include <sstream>
#include <map>

#define SMART_ENUM(EnumName, ...)                                   \
class EnumName                                                      \
{                                                                   \
private:                                                            \
    static std::map<int, std::string> nameMap;                      \
public:                                                             \
    enum {__VA_ARGS__};                                             \
private:                                                            \
    static std::map<int, std::string> initMap()                     \
    {                                                               \
        using namespace std;                                        \
                                                                    \
        int val = 0;                                                \
        string buf_1, buf_2, str = #__VA_ARGS__;                    \
        replace(str.begin(), str.end(), '=', ' ');                  \
        stringstream stream(str);                                   \
        vector<string> strings;                                     \
        while (getline(stream, buf_1, ','))                         \
            strings.push_back(buf_1);                               \
        map<int, string> tmp;                                       \
        for(vector<string>::iterator it = strings.begin();          \
                                               it != strings.end(); \
                                               ++it)                \
        {                                                           \
            buf_1.clear(); buf_2.clear();                           \
            stringstream localStream(*it);                          \
            localStream>> buf_1 >> buf_2;                           \
            if(buf_2.size() > 0)                                    \
                val = atoi(buf_2.c_str());                          \
            tmp[val++] = buf_1;                                     \
        }                                                           \
        return tmp;                                                 \
    }                                                               \
public:                                                             \
    static std::string toString(int aInt)                           \
    {                                                               \
        return nameMap[aInt];                                       \
    }                                                               \
};                                                                  \
std::map<int, std::string>                                          \
EnumName::nameMap = EnumName::initMap();

Uso:

SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
cout<<MyEnum::toString(MyEnum::TWO);
cout<<MyEnum::toString(10);

1
Gosto da sua API, mas, infelizmente, o SmartEnum não cria um "tipo" de enum. Você não pode fazer MyEnum x = MyEnum::TWO;. Publiquei minha edição da sua turma para apoiar isso.
Mark Lakata

4

Solução macro da Suma é boa. Você não precisa ter duas macros diferentes. Felizmente, o C ++ incluirá um cabeçalho duas vezes. Apenas deixe de fora a guarda de inclusão.

Então você teria um foobar.h definindo apenas

ENUM(Foo, 1)
ENUM(Bar, 2)

e você incluiria assim:

#define ENUMFACTORY_ARGUMENT "foobar.h"
#include "enumfactory.h"

enumfactory.h fará 2 #include ENUMFACTORY_ARGUMENTs. No primeiro turno, ele expande o ENUM como o de Suma DECLARE_ENUM; no segundo turno, ENUM funciona como DEFINE_ENUM.

Você também pode incluir enumfactory.h várias vezes, desde que passe diferentes # define para ENUMFACTORY_ARGUMENT


parece que suma mudou a resposta aqui . Você pode incluir o link na sua resposta. Eu só encontrei o comentário por acaso e witout sumas responder este é bastante inútil
idclev 463035818

3

Observe que, idealmente, sua função de conversão retornará um const char *.

Se você puder colocar suas enumerações em seus arquivos de cabeçalho separados, talvez faça algo assim com macros (oh, isso será feio):

#include "enum_def.h"
#include "colour.h"
#include "enum_conv.h"
#include "colour.h"

Onde enum_def.h possui:

#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) enum NAME {
#define ENUM_ADD(NAME, VALUE) NAME = VALUE,
#define ENUM_END };

E enum_conv.h tem:

#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) const char *##NAME##_to_string(NAME val) { switch (val) {
#define ENUM_ADD(NAME, VALUE) case NAME: return #NAME;
#define ENUM_END default: return "Invalid value"; } }

E, finalmente, colour.h tem:

ENUM_START(colour)
ENUM_ADD(red,   0xff0000)
ENUM_ADD(green, 0x00ff00)
ENUM_ADD(blue,  0x0000ff)
ENUM_END

E você pode usar a função de conversão como:

printf("%s", colour_to_string(colour::red));

Isso é feio, mas é a única maneira (no nível do pré-processador) que permite definir sua enumeração apenas em um único local no seu código. Portanto, seu código não está sujeito a erros devido a modificações na enumeração. Sua definição de enumeração e a função de conversão estarão sempre sincronizadas. No entanto, repito, isso é feio :)


3

Outra resposta: em alguns contextos, faz sentido definir sua enumeração em um formato sem código, como um arquivo CSV, YAML ou XML e, em seguida, gere o código de enumeração C ++ e o código de seqüência de caracteres a partir da definição. Essa abordagem pode ou não ser prática em seu aplicativo, mas é algo a ter em mente.


3

Esta é uma modificação na resposta @ user3360260. Possui os seguintes novos recursos

  • MyEnum fromString(const string&) Apoio, suporte
  • compila com o VisualStudio 2012
  • o enum é um tipo de POD real (não apenas declarações const), para que você possa atribuí-lo a uma variável.
  • adicionado o recurso "intervalo" do C ++ (em forma de vetor) para permitir a iteração "foreach" sobre enum

Uso:

SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
MyEnum foo = MyEnum::TWO;
cout << MyEnum::toString(foo);  // static method
cout << foo.toString();         // member method
cout << MyEnum::toString(MyEnum::TWO);
cout << MyEnum::toString(10);
MyEnum foo = myEnum::fromString("TWO");

// C++11 iteration over all values
for( auto x : MyEnum::allValues() )
{
  cout << x.toString() << endl;
}

Aqui está o código

#define SMART_ENUM(EnumName, ...)                                   \
class EnumName                                                      \
{                                                                   \
public:                                                             \
    EnumName() : value(0) {}                                        \
    EnumName(int x) : value(x) {}                                   \
public:                                                             \
    enum {__VA_ARGS__};                                             \
private:                                                            \
    static void initMap(std::map<int, std::string>& tmp)                     \
    {                                                               \
        using namespace std;                                        \
                                                                    \
        int val = 0;                                                \
        string buf_1, buf_2, str = #__VA_ARGS__;                    \
        replace(str.begin(), str.end(), '=', ' ');                  \
        stringstream stream(str);                                   \
        vector<string> strings;                                     \
        while (getline(stream, buf_1, ','))                         \
            strings.push_back(buf_1);                               \
        for(vector<string>::iterator it = strings.begin();          \
                                                it != strings.end(); \
                                                ++it)                \
        {                                                           \
            buf_1.clear(); buf_2.clear();                           \
            stringstream localStream(*it);                          \
            localStream>> buf_1 >> buf_2;                           \
            if(buf_2.size() > 0)                                    \
                val = atoi(buf_2.c_str());                          \
            tmp[val++] = buf_1;                                     \
        }                                                           \
    }                                                               \
    int value;                                                      \
public:                                                             \
    operator int () const { return value; }                         \
    std::string toString(void) const {                              \
            return toString(value);                                 \
    }                                                               \
    static std::string toString(int aInt)                           \
    {                                                               \
        return nameMap()[aInt];                                     \
    }                                                               \
    static EnumName fromString(const std::string& s)                \
    {                                                               \
        auto it = find_if(nameMap().begin(), nameMap().end(), [s](const std::pair<int,std::string>& p) { \
            return p.second == s;                                   \
        });                                                         \
        if (it == nameMap().end()) {                                \
        /*value not found*/                                         \
            throw EnumName::Exception();                            \
        } else {                                                    \
            return EnumName(it->first);                             \
        }                                                           \
    }                                                               \
    class Exception : public std::exception {};                     \
    static std::map<int,std::string>& nameMap() {                   \
      static std::map<int,std::string> nameMap0;                    \
      if (nameMap0.size() ==0) initMap(nameMap0);                   \
      return nameMap0;                                              \
    }                                                               \
    static std::vector<EnumName> allValues() {                      \
      std::vector<EnumName> x{ __VA_ARGS__ };                       \
      return x;                                                     \
    }                                                               \
    bool operator<(const EnumName a) const { return (int)*this < (int)a; } \
};         

Observe que a conversão paraString é uma pesquisa rápida, enquanto a conversão deString é uma pesquisa linear lenta. Mas as strings são tão caras de qualquer maneira (e o IO do arquivo associado), que não senti a necessidade de otimizar ou usar um bimap.


Você e o usuário3360260 têm uma boa solução. Por que não ter um multimapa em vez disso?
Vincent

3

Aqui está uma solução de arquivo único (com base na resposta elegante de @Marcin:

#include <iostream>

#define ENUM_TXT \
X(Red) \
X(Green) \
X(Blue) \
X(Cyan) \
X(Yellow) \
X(Magenta) \

enum Colours {
#   define X(a) a,
ENUM_TXT
#   undef X
    ColoursCount
};

char const* const colours_str[] = {
#   define X(a) #a,
ENUM_TXT
#   undef X
    0
};

std::ostream& operator<<(std::ostream& os, enum Colours c)
{
    if (c >= ColoursCount || c < 0) return os << "???";
    return os << colours_str[c] << std::endl;
}

int main()
{
    std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}

2

Eu faço isso com classes de wrapper enum lado a lado separadas, que são geradas com macros. Existem várias vantagens:

  • Posso gerá-los para enumerações que eu não defino (por exemplo: enumerações de cabeçalho da plataforma do SO)
  • Pode incorporar verificação de faixa na classe de wrapper
  • Pode fazer formatação "mais inteligente" com enumerações de campo de bits

A desvantagem, é claro, é que eu preciso duplicar os valores de enum nas classes do formatador e não tenho nenhum script para gerá-los. Fora isso, porém, parece funcionar muito bem.

Aqui está um exemplo de enumeração da minha base de código, sem todo o código da estrutura que implementa as macros e modelos, mas você pode ter uma idéia:

enum EHelpLocation
{
    HELP_LOCATION_UNKNOWN   = 0, 
    HELP_LOCAL_FILE         = 1, 
    HELP_HTML_ONLINE        = 2, 
};
class CEnumFormatter_EHelpLocation : public CEnumDefaultFormatter< EHelpLocation >
{
public:
    static inline CString FormatEnum( EHelpLocation eValue )
    {
        switch ( eValue )
        {
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCATION_UNKNOWN );
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCAL_FILE );
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_HTML_ONLINE );
        default:
            return FormatAsNumber( eValue );
        }
    }
};
DECLARE_RANGE_CHECK_CLASS( EHelpLocation, CRangeInfoSequential< HELP_HTML_ONLINE > );
typedef ESmartEnum< EHelpLocation, HELP_LOCATION_UNKNOWN, CEnumFormatter_EHelpLocation, CRangeInfo_EHelpLocation > SEHelpLocation;

A ideia é, em vez de usar EHelpLocation, você usa SEHelpLocation; tudo funciona da mesma maneira, mas você obtém a verificação de alcance e um método 'Format ()' na própria variável enum. Se você precisar formatar um valor independente, poderá usar CEnumFormatter_EHelpLocation :: FormatEnum (...).

Espero que isso seja útil. Sei que isso também não aborda a pergunta original sobre um script para realmente gerar a outra classe, mas espero que a estrutura ajude alguém a tentar resolver o mesmo problema ou escreva esse script.


2

É um software não lançado, mas parece que o BOOST_ENUM de Frank Laub poderia ser o ideal. A parte de que gosto é que você pode definir uma enumeração no escopo de uma classe que a maioria das enumerações baseadas em macro geralmente não permite. Está localizado no Boost Vault em: http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=& Não há nenhum desenvolvimento desde 2006, então não vejo saber quão bem ele compila com os novos lançamentos do Boost. Procure em libs / test um exemplo de uso.


2

Esta foi a minha solução com o BOOST:

#include <boost/preprocessor.hpp>

#define X_STR_ENUM_TOSTRING_CASE(r, data, elem)                                 \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define X_ENUM_STR_TOENUM_IF(r, data, elem)                                     \
    else if(data == BOOST_PP_STRINGIZE(elem)) return elem;

#define STR_ENUM(name, enumerators)                                             \
    enum name {                                                                 \
        BOOST_PP_SEQ_ENUM(enumerators)                                          \
    };                                                                          \
                                                                                \
    inline const QString enumToStr(name v)                                      \
    {                                                                           \
        switch (v)                                                              \
        {                                                                       \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                X_STR_ENUM_TOSTRING_CASE,                                       \
                name,                                                           \
                enumerators                                                     \
            )                                                                   \
                                                                                \
            default:                                                            \
                return "[Unknown " BOOST_PP_STRINGIZE(name) "]";                \
        }                                                                       \
    }                                                                           \
                                                                                \
    template <typename T>                                                       \
    inline const T strToEnum(QString v);                                        \
                                                                                \
    template <>                                                                 \
    inline const name strToEnum(QString v)                                      \
    {                                                                           \
        if(v=="")                                                               \
            throw std::runtime_error("Empty enum value");                       \
                                                                                \
        BOOST_PP_SEQ_FOR_EACH(                                                  \
            X_ENUM_STR_TOENUM_IF,                                               \
            v,                                                                  \
            enumerators                                                         \
        )                                                                       \
                                                                                \
        else                                                                    \
            throw std::runtime_error(                                           \
                        QString("[Unknown value %1 for enum %2]")               \
                            .arg(v)                                             \
                            .arg(BOOST_PP_STRINGIZE(name))                      \
                                .toStdString().c_str());                        \
    }

Para criar enum, declare:

STR_ENUM
(
    SERVICE_RELOAD,
        (reload_log)
        (reload_settings)
        (reload_qxml_server)
)

Para conversões:

SERVICE_RELOAD serviceReloadEnum = strToEnum<SERVICE_RELOAD>("reload_log");
QString serviceReloadStr = enumToStr(reload_log);

2

Quero postar isso caso alguém considere útil.

No meu caso, eu simplesmente preciso gerar ToString()e FromString()funções para uma única enumeração C ++ 11 a partir de um único .hpparquivo.

Eu escrevi um script python que analisa o arquivo de cabeçalho que contém os itens enum e gera as funções em um novo .cpparquivo.

Você pode adicionar esse script no CMakeLists.txt com execute_process ou como um evento de pré-compilação no Visual Studio. O .cpparquivo será gerado automaticamente, sem a necessidade de atualizá-lo manualmente sempre que um novo item de enumeração for adicionado.

generate_enum_strings.py

# This script is used to generate strings from C++ enums

import re
import sys
import os

fileName = sys.argv[1]
enumName = os.path.basename(os.path.splitext(fileName)[0])

with open(fileName, 'r') as f:
    content = f.read().replace('\n', '')

searchResult = re.search('enum(.*)\{(.*?)\};', content)
tokens = searchResult.group(2)
tokens = tokens.split(',')
tokens = map(str.strip, tokens)
tokens = map(lambda token: re.search('([a-zA-Z0-9_]*)', token).group(1), tokens)

textOut = ''
textOut += '\n#include "' + enumName + '.hpp"\n\n'
textOut += 'namespace myns\n'
textOut += '{\n'
textOut += '    std::string ToString(ErrorCode errorCode)\n'
textOut += '    {\n'
textOut += '        switch (errorCode)\n'
textOut += '        {\n'

for token in tokens:
    textOut += '        case ' + enumName + '::' + token + ':\n'
    textOut += '            return "' + token + '";\n'

textOut += '        default:\n'
textOut += '            return "Last";\n'
textOut += '        }\n'
textOut += '    }\n'
textOut += '\n'
textOut += '    ' + enumName + ' FromString(const std::string &errorCode)\n'
textOut += '    {\n'
textOut += '        if ("' + tokens[0] + '" == errorCode)\n'
textOut += '        {\n'
textOut += '            return ' + enumName + '::' + tokens[0] + ';\n'
textOut += '        }\n'

for token in tokens[1:]:
    textOut += '        else if("' + token + '" == errorCode)\n'
    textOut += '        {\n'
    textOut += '            return ' + enumName + '::' + token + ';\n'
    textOut += '        }\n'

textOut += '\n'
textOut += '        return ' + enumName + '::Last;\n'
textOut += '    }\n'
textOut += '}\n'

fileOut = open(enumName + '.cpp', 'w')
fileOut.write(textOut)

Exemplo:

ErrorCode.hpp

#pragma once

#include <string>
#include <cstdint>

namespace myns
{
    enum class ErrorCode : uint32_t
    {
        OK = 0,
        OutOfSpace,
        ConnectionFailure,
        InvalidJson,
        DatabaseFailure,
        HttpError,
        FileSystemError,
        FailedToEncrypt,
        FailedToDecrypt,
        EndOfFile,
        FailedToOpenFileForRead,
        FailedToOpenFileForWrite,
        FailedToLaunchProcess,

        Last
    };

    std::string ToString(ErrorCode errorCode);
    ErrorCode FromString(const std::string &errorCode);
}

Corre python generate_enum_strings.py ErrorCode.hpp

Resultado:

ErrorCode.cpp

#include "ErrorCode.hpp"

namespace myns
{
    std::string ToString(ErrorCode errorCode)
    {
        switch (errorCode)
        {
        case ErrorCode::OK:
            return "OK";
        case ErrorCode::OutOfSpace:
            return "OutOfSpace";
        case ErrorCode::ConnectionFailure:
            return "ConnectionFailure";
        case ErrorCode::InvalidJson:
            return "InvalidJson";
        case ErrorCode::DatabaseFailure:
            return "DatabaseFailure";
        case ErrorCode::HttpError:
            return "HttpError";
        case ErrorCode::FileSystemError:
            return "FileSystemError";
        case ErrorCode::FailedToEncrypt:
            return "FailedToEncrypt";
        case ErrorCode::FailedToDecrypt:
            return "FailedToDecrypt";
        case ErrorCode::EndOfFile:
            return "EndOfFile";
        case ErrorCode::FailedToOpenFileForRead:
            return "FailedToOpenFileForRead";
        case ErrorCode::FailedToOpenFileForWrite:
            return "FailedToOpenFileForWrite";
        case ErrorCode::FailedToLaunchProcess:
            return "FailedToLaunchProcess";
        case ErrorCode::Last:
            return "Last";
        default:
            return "Last";
        }
    }

    ErrorCode FromString(const std::string &errorCode)
    {
        if ("OK" == errorCode)
        {
            return ErrorCode::OK;
        }
        else if("OutOfSpace" == errorCode)
        {
            return ErrorCode::OutOfSpace;
        }
        else if("ConnectionFailure" == errorCode)
        {
            return ErrorCode::ConnectionFailure;
        }
        else if("InvalidJson" == errorCode)
        {
            return ErrorCode::InvalidJson;
        }
        else if("DatabaseFailure" == errorCode)
        {
            return ErrorCode::DatabaseFailure;
        }
        else if("HttpError" == errorCode)
        {
            return ErrorCode::HttpError;
        }
        else if("FileSystemError" == errorCode)
        {
            return ErrorCode::FileSystemError;
        }
        else if("FailedToEncrypt" == errorCode)
        {
            return ErrorCode::FailedToEncrypt;
        }
        else if("FailedToDecrypt" == errorCode)
        {
            return ErrorCode::FailedToDecrypt;
        }
        else if("EndOfFile" == errorCode)
        {
            return ErrorCode::EndOfFile;
        }
        else if("FailedToOpenFileForRead" == errorCode)
        {
            return ErrorCode::FailedToOpenFileForRead;
        }
        else if("FailedToOpenFileForWrite" == errorCode)
        {
            return ErrorCode::FailedToOpenFileForWrite;
        }
        else if("FailedToLaunchProcess" == errorCode)
        {
            return ErrorCode::FailedToLaunchProcess;
        }
        else if("Last" == errorCode)
        {
            return ErrorCode::Last;
        }

        return ErrorCode::Last;
    }
}


2

Adicionando ainda mais simplicidade de uso à fantástica resposta de Jasper Bekkers :

Configure uma vez:

#define MAKE_ENUM(VAR) VAR,
#define MAKE_STRINGS(VAR) #VAR,
#define MAKE_ENUM_AND_STRINGS(source, enumName, enumStringName) \
    enum enumName { \
    source(MAKE_ENUM) \
    };\
const char* const enumStringName[] = { \
    source(MAKE_STRINGS) \
    };

Então, para uso:

#define SOME_ENUM(DO) \
    DO(Foo) \
    DO(Bar) \
    DO(Baz)
...
MAKE_ENUM_AND_STRINGS(SOME_ENUM, someEnum, someEnumNames)

2

Você poderia usar uma biblioteca de reflexão, como Ponder . Você registra as enumerações e pode convertê-las com a API.

enum class MyEnum
{
    Zero = 0,
    One  = 1,
    Two  = 2
};

ponder::Enum::declare<MyEnum>()
    .value("Zero", MyEnum::Zero)
    .value("One",  MyEnum::One)
    .value("Two",  MyEnum::Two);

ponder::EnumObject zero(MyEnum::Zero);

zero.name(); // -> "Zero"

1

Um problema com a resposta 0 é que os valores binários da enumeração não iniciam necessariamente em 0 e não são necessariamente contíguos.

Quando eu preciso disso, eu normalmente:

  • puxe a definição de enum para minha fonte
  • editá-lo para obter apenas os nomes
  • faça uma macro para alterar o nome da cláusula case na pergunta, embora normalmente em uma linha: case foo: retorne "foo";
  • adicione a opção, padrão e outra sintaxe para torná-lo legal

1

O script ruby ​​a seguir tenta analisar os cabeçalhos e construir as fontes necessárias juntamente com os cabeçalhos originais.

#! /usr/bin/env ruby

# Let's "parse" the headers
# Note that using a regular expression is rather fragile
# and may break on some inputs

GLOBS = [
  "toto/*.h",
  "tutu/*.h",
  "tutu/*.hxx"
]

enums = {}
GLOBS.each { |glob|
  Dir[glob].each { |header|
    enums[header] = File.open(header, 'rb') { |f|
      f.read
    }.scan(/enum\s+(\w+)\s+\{\s*([^}]+?)\s*\}/m).collect { |enum_name, enum_key_and_values|
      [
        enum_name, enum_key_and_values.split(/\s*,\s*/).collect { |enum_key_and_value|
          enum_key_and_value.split(/\s*=\s*/).first
        }
      ]
    }
  }
}


# Now we build a .h and .cpp alongside the parsed headers
# using the template engine provided with ruby
require 'erb'

template_h = ERB.new <<-EOS
#ifndef <%= enum_name %>_to_string_h_
#define <%= enum_name %>_to_string_h_ 1

#include "<%= header %>"
char* enum_to_string(<%= enum_name %> e);

#endif
EOS

template_cpp = ERB.new <<-EOS
#include "<%= enum_name %>_to_string.h"

char* enum_to_string(<%= enum_name %> e)
{
  switch (e)
  {<% enum_keys.each do |enum_key| %>
    case <%= enum_key %>: return "<%= enum_key %>";<% end %>
    default: return "INVALID <%= enum_name %> VALUE";
  }
}
EOS

enums.each { |header, enum_name_and_keys|
  enum_name_and_keys.each { |enum_name, enum_keys|
    File.open("#{File.dirname(header)}/#{enum_name}_to_string.h", 'wb') { |built_h|
      built_h.write(template_h.result(binding))
    }

    File.open("#{File.dirname(header)}/#{enum_name}_to_string.cpp", 'wb') { |built_cpp|
      built_cpp.write(template_cpp.result(binding))
    }
  }
}

O uso de expressões regulares torna esse "analisador" bastante frágil; talvez não seja possível manipular seus cabeçalhos específicos normalmente.

Digamos que você tenha um cabeçalho toto / ah, contendo definições para enumerações MyEnum e MyEnum2. O script criará:

toto/MyEnum_to_string.h
toto/MyEnum_to_string.cpp
toto/MyEnum2_to_string.h
toto/MyEnum2_to_string.cpp

Soluções mais robustas seriam:

  • Crie todas as fontes que definem enumerações e suas operações de outra fonte. Isso significa que você definirá suas enumerações em um arquivo XML / YML / qualquer que seja muito mais fácil de analisar do que C / C ++.
  • Use um compilador real, como sugerido por Avdi.
  • Use macros de pré-processador com ou sem modelos.

0

Essa é basicamente a única maneira de fazer isso (uma matriz de strings também pode funcionar).

O problema é que, quando um programa C é compilado, o valor binário da enumeração é tudo o que é usado e o nome desaparece.


0

Aqui está um programa CLI que escrevi para converter facilmente enumerações em strings. É fácil de usar e leva cerca de 5 segundos para fazer isso (incluindo o tempo de acessar o diretório que contém o programa e executá-lo, passando para ele o arquivo que contém a enumeração).

Faça o download aqui: http://www.mediafire.com/?nttignoozzz

Tópico de discussão aqui: http://cboard.cprogramming.com/projects-job-recruitment/127488-free-program-im-sharing-convertenumtostrings.html

Execute o programa com o argumento "--help" para obter uma descrição de como usá-lo.


Você poderia colocar isso em um repositório em algum lugar (github, google code ou bitbucket) e postar o link aqui, em vez do mediafire? Gostaria de ajudar as pessoas que querem entender isso :)
Edu Felipe

0

Há pouco tempo, fiz um truque para exibir enumerações corretamente no QComboBox e definir definições de enumerações e representações de string como uma declaração

#pragma once
#include <boost/unordered_map.hpp>

namespace enumeration
{

   struct enumerator_base : boost::noncopyable
   {
      typedef
         boost::unordered_map<int, std::wstring>
         kv_storage_t;
      typedef
         kv_storage_t::value_type
         kv_type;
      kv_storage_t const & kv() const
      {
         return storage_;
      }

      LPCWSTR name(int i) const
      {
         kv_storage_t::const_iterator it = storage_.find(i);
         if(it != storage_.end())
            return it->second.c_str();
         return L"empty";
      }

   protected:
      kv_storage_t storage_;
   };

   template<class T>
   struct enumerator;

   template<class D>
   struct enum_singleton : enumerator_base
   {
      static enumerator_base const & instance()
      {
         static D inst;
         return inst;
      }
   };
}

#define QENUM_ENTRY(K, V, N)  K, N storage_.insert(std::make_pair((int)K, V));

#define QBEGIN_ENUM(NAME, C)   \
enum NAME                     \
{                             \
   C                          \
}                             \
};                            \
}                             \

#define QEND_ENUM(NAME) \
};                     \
namespace enumeration  \
{                      \
template<>             \
struct enumerator<NAME>\
   : enum_singleton< enumerator<NAME> >\
{                      \
   enumerator()        \
   {

//usage
/*
QBEGIN_ENUM(test_t,
   QENUM_ENTRY(test_entry_1, L"number uno",
   QENUM_ENTRY(test_entry_2, L"number dos",
   QENUM_ENTRY(test_entry_3, L"number tres",
QEND_ENUM(test_t)))))
*/

Agora você enumeration::enum_singleton<your_enum>::instance()pode converter enums em strings. Se você substituir kv_storage_tporboost::bimap , também poderá fazer a conversão para trás. A classe base comum para o conversor foi introduzida para armazená-lo no objeto Qt, porque os objetos Qt não podiam ser modelos

Aparência anterior


0

Como variante, use lib simples> http://codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C

No código

#include <EnumString.h>

enum FORM {
    F_NONE = 0,
    F_BOX,
    F_CUBE,
    F_SPHERE,
};

adicionar linhas

Begin_Enum_String( FORM )
{
    Enum_String( F_NONE );
    Enum_String( F_BOX );
    Enum_String( F_CUBE );
    Enum_String( F_SPHERE );
}
End_Enum_String;

Funciona bem, se os valores em enum não forem publicados .

Exemplo de uso

enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );

e vice versa

assert( EnumString< FORM >::To( f, str ) );

0

Aqui está uma tentativa de obter operadores de fluxo << e >> no enum automaticamente com apenas um comando macro de uma linha ...

Definições:

#include <string>
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>

#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<std::string> enumStr(name##Str, name##Str + len); \
        const std::vector<std::string>::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 desse esquema ... comentários são bem-vindos!


0
#include <iostream>
#include <map>
#define IDMAP(x) (x,#x)

std::map<int , std::string> enToStr;
class mapEnumtoString
{
public:
    mapEnumtoString(){  }
    mapEnumtoString& operator()(int i,std::string str)
    {
        enToStr[i] = str;
        return *this;
    }
public:
   std::string operator [] (int i)
    {
        return enToStr[i];
    }

};
mapEnumtoString k;
mapEnumtoString& init()
{
    return k;
}

int main()
{

init()
    IDMAP(1)
    IDMAP(2)
    IDMAP(3)
    IDMAP(4)
    IDMAP(5);
std::cout<<enToStr[1];
std::cout<<enToStr[2];
std::cout<<enToStr[3];
std::cout<<enToStr[4];
std::cout<<enToStr[5];
}

2
Por favor, explique por que essa é a resposta.
Alok
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.