C ++: Imprime o valor enum como texto

91

Se eu tiver um enum como este

enum Errors
{ErrorA=0, ErrorB, ErrorC};

Então eu quero imprimir para o console

Errors anError = ErrorA;
cout<<anError;/// 0 will be printed

mas o que eu quero é o texto "ErrorA", posso fazer isso sem usar if / switch?
E qual é a sua solução para isso?

tibu
fonte
Acho que minha resposta é muito boa, você se importaria de dar uma olhada?
Xiao
2
Para C ++ 11 enum class: stackoverflow.com/questions/11421432/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
2
Enum para string: stackoverflow.com/questions/201593/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respostas:

64

Usando o mapa:

#include <iostream>
#include <map>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    static std::map<Errors, std::string> strings;
    if (strings.size() == 0){
#define INSERT_ELEMENT(p) strings[p] = #p
        INSERT_ELEMENT(ErrorA);     
        INSERT_ELEMENT(ErrorB);     
        INSERT_ELEMENT(ErrorC);             
#undef INSERT_ELEMENT
    }   

    return out << strings[value];
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

Usando matriz de estruturas com pesquisa linear:

#include <iostream>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
#define MAPENTRY(p) {p, #p}
    const struct MapEntry{
        Errors value;
        const char* str;
    } entries[] = {
        MAPENTRY(ErrorA),
        MAPENTRY(ErrorB),
        MAPENTRY(ErrorC),
        {ErrorA, 0}//doesn't matter what is used instead of ErrorA here...
    };
#undef MAPENTRY
    const char* s = 0;
    for (const MapEntry* i = entries; i->str; i++){
        if (i->value == value){
            s = i->str;
            break;
        }
    }

    return out << s;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}

Usando switch / case:

#include <iostream>
#include <string>

enum Errors {ErrorA=0, ErrorB, ErrorC};

std::ostream& operator<<(std::ostream& out, const Errors value){
    const char* s = 0;
#define PROCESS_VAL(p) case(p): s = #p; break;
    switch(value){
        PROCESS_VAL(ErrorA);     
        PROCESS_VAL(ErrorB);     
        PROCESS_VAL(ErrorC);
    }
#undef PROCESS_VAL

    return out << s;
}

int main(int argc, char** argv){
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
    return 0;   
}
SigTerm
fonte
12
-1. Basta fazer um switch-case em vez de usar um hash-map. O aumento da complexidade não é bom.
Simon
8
Bom ponto. Na próxima vez, farei isso :) Mas agora vejo que você já editou sua postagem para adicionar o tipo de funcionalidade que eu estava procurando. Bom trabalho!
Simon
1
o que é #p? se no terceiro exemplo, em vez de enum, eu usar uma classe enum, é possível obter apenas a string enum sem o nome da classe?
rh0x
2
#pé o pré-processador que restringe p. Então, chamando PROCESS_VAL(ErrorA)saída será: case(ErrorA): s = "ErrorA"; break;.
Nashenas
Não a considero uma solução ótima: Razões: 1) Tenho que manter o dobro dos enumvalores, o que considero NÃO-GO . 2) Quando eu entendo a solução corretamente, ela funciona apenas para um enum.
Peter VARGA
30

Use uma matriz ou vetor de strings com valores correspondentes:

char *ErrorTypes[] =
{
    "errorA",
    "errorB",
    "errorC"
};

cout << ErrorTypes[anError];

EDIT: A solução acima é aplicável quando o enum é contíguo, ou seja, começa em 0 e não há valores atribuídos. Funcionará perfeitamente com o enum na questão.

Para comprovar ainda mais o caso em que enum não começa de 0, use:

cout << ErrorTypes[anError - ErrorA];
Igor Oks
fonte
4
infelizmente, enum nos permite atribuir valores aos elementos. Como você aborda o trabalho se tiver enums não contíguos, linha 'enum Status {OK = 0, Fail = -1, OutOfMemory = -2, IOError = -1000, ConversionError = -2000} `(então você pode adicionar IOErrors mais tarde para a faixa -1001-1999)
Nordic Mainframe
@Luther: Sim, isso funcionará apenas com enums contíguos, como a maioria dos enums . No caso de enum não ser contíguo, você precisará usar outra abordagem, ou seja, mapas. Mas, no caso de enum contíguo, sugiro usar essa abordagem, e não complicar demais.
Igor Oks
2
Portanto, se meu colega adicionar NewValue a um enum e não atualizar a matriz ErrorTypes, então ErrorTypes [NewValue] produz o quê? E como faço para lidar com valores enum negativos?
Nordic Mainframe
2
@Luther: Você terá que manter ErrorTypes atualizado. Novamente, há uma compensação entre a simplicidade e a universalidade, depende do que é mais importante para o usuário. Qual é o problema com valores enum negativos?
Igor Oks
1
Este array não deveria ser estático para eficiência de memória? e const para segurança?
Jonathan
15

Aqui está um exemplo baseado em Boost.Preprocessor:

#include <iostream>

#include <boost/preprocessor/punctuation/comma.hpp>
#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/comparison/equal.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/seq/seq.hpp>


#define DEFINE_ENUM(name, values)                               \
  enum name {                                                   \
    BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_VALUE, , values)          \
  };                                                            \
  inline const char* format_##name(name val) {                  \
    switch (val) {                                              \
      BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_FORMAT, , values)       \
    default:                                                    \
        return 0;                                               \
    }                                                           \
  }

#define DEFINE_ENUM_VALUE(r, data, elem)                        \
  BOOST_PP_SEQ_HEAD(elem)                                       \
  BOOST_PP_IIF(BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE(elem), 2),      \
               = BOOST_PP_SEQ_TAIL(elem), )                     \
  BOOST_PP_COMMA()

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


DEFINE_ENUM(Errors,
            ((ErrorA)(0))
            ((ErrorB))
            ((ErrorC)))

int main() {
  std::cout << format_Errors(ErrorB) << std::endl;
}
Philipp
fonte
2
+1, Esta solução não depende de uma ferramenta externa, como a resposta lua acima, mas é C ++ puro, segue o princípio DRY, e a sintaxe do usuário é legível (se formatada corretamente. BTW, você não precisa das barras invertidas ao usar DEFINE_ENUM, que parece um pouco mais natural, IMO)
Fabio Fracassi
3
@Fabio Fracassi: "Esta solução não depende de uma ferramenta externa" Boost é uma ferramenta externa - biblioteca C ++ não padrão. Além disso, é um pouco longo. A solução de um problema deve ser o mais simples possível. Este não se qualifica ...
SigTerm
2
Na verdade, é tudo o que você poderia colocar a maior parte do código (na verdade, tudo, exceto a definição real) pode ser colocado em um único cabeçalho. portanto, esta é realmente a solução mais curta apresentada aqui. E para o boost ser externo, sim, mas menos do que um script fora da linguagem para pré-processamento de partes da fonte como o script lua acima é. Além disso, o boost está tão próximo do padrão que deveria estar em todas as caixas de ferramentas de programadores C ++. Apenas IMHO, é claro
Fabio Fracassi
[Removi o escape desnecessário de novas linhas na invocação da macro. Eles não são necessários: uma invocação de macro pode abranger várias linhas.]
James McNellis
A macro DEFINE_ENUMme dá o erro multiple definition of `format_ProgramStatus(ProgramStatus)'quando tento usá-la.
HelloGoodbye
6

Você pode usar um truque mais simples do pré-processador se quiser listar suas enumentradas em um arquivo externo.

/* file: errors.def */
/* syntax: ERROR_DEF(name, value) */
ERROR_DEF(ErrorA, 0x1)
ERROR_DEF(ErrorB, 0x2)
ERROR_DEF(ErrorC, 0x4)

Então, em um arquivo de origem, você trata o arquivo como um arquivo de inclusão, mas define o que deseja ERROR_DEFfazer.

enum Errors {
#define ERROR_DEF(x,y) x = y,
#include "errors.def"
#undef ERROR_DEF
};

static inline std::ostream & operator << (std::ostream &o, Errors e) {
    switch (e) {
    #define ERROR_DEF(x,y) case y: return o << #x"[" << y << "]";
    #include "errors.def"
    #undef ERROR_DEF
    default: return o << "unknown[" << e << "]";
    }
}

Se você usar alguma ferramenta de navegação de fonte (como cscope), você terá que informá-la sobre o arquivo externo.

jxh
fonte
4

Houve uma discussão aqui que pode ajudar: Existe uma maneira simples de converter enum C ++ em string?

ATUALIZAÇÃO: Aqui # um script para Lua que cria um operador << para cada enum nomeado que encontra. Isso pode exigir algum trabalho para que funcione nos casos menos simples [1]:

function make_enum_printers(s)
    for n,body in string.gmatch(s,'enum%s+([%w_]+)%s*(%b{})') do
    print('ostream& operator<<(ostream &o,'..n..' n) { switch(n){') 
    for k in string.gmatch(body,"([%w_]+)[^,]*") do
    print('  case '..k..': return o<<"'..k..'";')
    end
    print('  default: return o<<"(invalid value)"; }}')
    end
end

local f=io.open(arg[1],"r")
local s=f:read('*a')
make_enum_printers(s)

Dada esta entrada:

enum Errors
{ErrorA=0, ErrorB, ErrorC};

enum Sec {
    X=1,Y=X,foo_bar=X+1,Z
};

Produz:

ostream& operator<<(ostream &o,Errors n) { switch(n){
  case ErrorA: return o<<"ErrorA";
  case ErrorB: return o<<"ErrorB";
  case ErrorC: return o<<"ErrorC";
  default: return o<<"(invalid value)"; }}
ostream& operator<<(ostream &o,Sec n) { switch(n){
  case X: return o<<"X";
  case Y: return o<<"Y";
  case foo_bar: return o<<"foo_bar";
  case Z: return o<<"Z";
  default: return o<<"(invalid value)"; }}

Portanto, é provavelmente um começo para você.

[1] enums em escopos diferentes ou não de namespace, enums com expressões inicializadoras que contêm um komma etc.

Nordic Mainframe
fonte
Não é um costume aqui comentar um '-1' para dar ao autor da postagem a oportunidade de corrigir sua resposta? Só perguntando ..
Nordic Mainframe
2
Acho que a solução Boost PP abaixo (de Philip) é melhor, porque usar ferramentas externas é muito caro em termos de manutenção. mas não -1 porque a resposta é válida de outra forma
Fabio Fracassi
4
O Boost PP também é um problema de manutenção, pois você precisa que todos falem a metalinguagem Boost PP, que é péssima , fácil de quebrar (dando mensagens de erro geralmente inutilizáveis) e apenas de usabilidade limitada (lua / python / perl pode gerar código de forma arbitrária dados externos). Ele adiciona um impulso à sua lista de dependências, o que pode nem mesmo ser permitido devido à política do projeto. Além disso, é invasivo porque requer que você defina seus enums em uma DSL. Sua ferramenta de código-fonte ou IDE favorita pode ter problemas com isso. E por último, mas não menos importante: você não pode definir um ponto de interrupção na expansão.
Nordic Mainframe
4

Eu uso uma matriz de string sempre que defino um enum:

Profile.h

#pragma once

struct Profile
{
    enum Value
    {
        Profile1,
        Profile2,
    };

    struct StringValueImplementation
    {
        const wchar_t* operator[](const Profile::Value profile)
        {
            switch (profile)
            {
            case Profile::Profile1: return L"Profile1";
            case Profile::Profile2: return L"Profile2";
            default: ASSERT(false); return NULL;
            }
        }
    };

    static StringValueImplementation StringValue;
};

Profile.cpp

#include "Profile.h"

Profile::StringValueImplementation Profile::StringValue;
Mark Ingram
fonte
4

Esta é uma boa maneira,

enum Rank { ACE = 1, DEUCE, TREY, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING };

Imprima-o com uma série de matrizes de caracteres

const char* rank_txt[] = {"Ace", "Deuce", "Trey", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Four", "King" } ;

Como isso

std::cout << rank_txt[m_rank - 1]
MrPickles7
fonte
2
E se meu enum começar em 2000? Esta solução não funcionará.
Sitesh
3
#include <iostream>
using std::cout;
using std::endl;

enum TEnum
{ 
  EOne,
  ETwo,
  EThree,
  ELast
};

#define VAR_NAME_HELPER(name) #name
#define VAR_NAME(x) VAR_NAME_HELPER(x)

#define CHECK_STATE_STR(x) case(x):return VAR_NAME(x);

const char *State2Str(const TEnum state)
{
  switch(state)
  {
    CHECK_STATE_STR(EOne);
    CHECK_STATE_STR(ETwo);
    CHECK_STATE_STR(EThree);
    CHECK_STATE_STR(ELast);
    default:
      return "Invalid";
  }
}

int main()
{
  int myInt=12345;
  cout << VAR_NAME(EOne) " " << VAR_NAME(myInt) << endl;

  for(int i = -1; i < 5;   i)
    cout << i << " " << State2Str((TEnum)i) << endl;
  return 0;
}
Vladimir Chernyshev
fonte
2

Você pode usar um contêiner de mapa stl ....

typedef map<Errors, string> ErrorMap;

ErrorMap m;
m.insert(ErrorMap::value_type(ErrorA, "ErrorA"));
m.insert(ErrorMap::value_type(ErrorB, "ErrorB"));
m.insert(ErrorMap::value_type(ErrorC, "ErrorC"));

Errors error = ErrorA;

cout << m[error] << endl;
Adrian Regan
fonte
4
Como este mapa é melhor do que switch(n) { case XXX: return "XXX"; ... }? Qual tem pesquisa O (1) e não precisa ser inicializado? Ou os enums mudam de alguma forma durante o tempo de execução?
Nordic Mainframe
Concordo com @Luther Blissett sobre o uso de instrução switch (ou um ponteiro de função também)
KedarX
1
Bem, ele pode querer produzir "Este meu querido amigo Luther é o erro A ou" Este meu querido amigo Adrian é o erro B. " código com concatenação de string, por exemplo, string x = "Hello" + m [ErrorA], etc.
Adrian Regan
Tenho certeza que std :: map contém muitos if's e switches. Eu leria isso como 'como posso fazer isso sem ter que escrever if's e switches'
Nordic Mainframe
Tenho certeza que sim, mas certamente não requer que você escreva um script em Lua para resolver o problema ...
Adrian Regan
1

Para este problema, faço uma função de ajuda como esta:

const char* name(Id id) {
    struct Entry {
        Id id;
        const char* name;
    };
    static const Entry entries[] = {
        { ErrorA, "ErrorA" },
        { ErrorB, "ErrorB" },
        { 0, 0 }
    }
    for (int it = 0; it < gui::SiCount; ++it) {
        if (entries[it].id == id) {
            return entries[it].name;
        }
    }
   return 0;
}

A pesquisa linear geralmente é mais eficiente do que std::mappara pequenas coleções como esta.

Johan Kotlinski
fonte
1

Esta solução não requer que você use nenhuma estrutura de dados ou crie um arquivo diferente.

Basicamente, você define todos os seus valores enum em um #define e, em seguida, os usa no operador <<. Muito semelhante à resposta de @jxh.

link ideone para iteração final: http://ideone.com/hQTKQp

Código completo:

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR)\
ERROR_VALUE(FILE_NOT_FOUND)\
ERROR_VALUE(LABEL_UNINITIALISED)

enum class Error
{
#define ERROR_VALUE(NAME) NAME,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME) case Error::NAME: return os << "[" << errVal << "]" #NAME;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << Error::NO_ERROR << std::endl;
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
    return 0;
}

Resultado:

Error: [0]NO_ERROR
Error: [1]FILE_NOT_FOUND
Error: [2]LABEL_UNINITIALISED

Uma coisa boa sobre fazer dessa maneira é que você também pode especificar suas próprias mensagens personalizadas para cada erro, se achar que precisa delas:

#include <iostream>

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, "A component tried to the label before it was initialised")

enum class Error
{
#define ERROR_VALUE(NAME,DESCR) NAME,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << Error::NO_ERROR << std::endl;
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
    return 0;
}

Resultado:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised

Se você gosta de tornar seus códigos / descrições de erro muito descritivos, talvez não os queira em compilações de produção. Desativá-los para que apenas o valor seja impresso é fácil:

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
    #ifndef PRODUCTION_BUILD // Don't print out names in production builds
    #define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
        ERROR_VALUES
    #undef ERROR_VALUE
    #endif
    default:
        return os << errVal;
    }
}

Resultado:

Error: 0
Error: 1
Error: 2

Se for esse o caso, encontrar o erro número 525 seria um PITA. Podemos especificar manualmente os números no enum inicial assim:

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, 0, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, 1, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, 2, "A component tried to the label before it was initialised")\
ERROR_VALUE(UKNOWN_ERROR, -1, "Uh oh")

enum class Error
{
#define ERROR_VALUE(NAME,VALUE,DESCR) NAME=VALUE,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#ifndef PRODUCTION_BUILD // Don't print out names in production builds
#define ERROR_VALUE(NAME,VALUE,DESCR) case Error::NAME: return os << "[" #VALUE  "]" #NAME <<"; " << DESCR;
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
        return os <<errVal;
    }
}
    ERROR_VALUES
#undef ERROR_VALUE
#endif
    default:
    {
        // If the error value isn't found (shouldn't happen)
        return os << static_cast<int>(err);
        break;
    }
    }
}

Resultado:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised
Error: [-1]UKNOWN_ERROR; Uh oh
Xiao
fonte
0

Que tal agora?

    enum class ErrorCodes : int{
          InvalidInput = 0
    };

    std::cout << ((int)error == 0 ? "InvalidInput" : "") << std::endl;

etc ... Eu sei que este é um exemplo altamente elaborado, mas acho que tem aplicação onde aplicável e necessário e é certamente mais curto do que escrever um script para ele.

user633658
fonte
0

Use o pré-processador:

#define VISIT_ERROR(FIRST, MIDDLE, LAST) \
    FIRST(ErrorA) MIDDLE(ErrorB) /* MIDDLE(ErrorB2) */ LAST(ErrorC)

enum Errors
{
    #define ENUMFIRST_ERROR(E)  E=0,
    #define ENUMMIDDLE_ERROR(E) E,
    #define ENUMLAST_ERROR(E)   E
    VISIT_ERROR(ENUMFIRST_ERROR, ENUMMIDDLE_ERROR, ENUMLAST_ERROR)
    // you might undefine the 3 macros defined above
};

std::string toString(Error e)
{
    switch(e)
    {
    #define CASERETURN_ERROR(E)  case E: return #E;
    VISIT_ERROR(CASERETURN_ERROR, CASERETURN_ERROR, CASERETURN_ERROR)
    // you might undefine the above macro.
    // note that this will produce compile-time error for synonyms in enum;
    // handle those, if you have any, in a distinct macro

    default:
        throw my_favourite_exception();
    }
}

A vantagem desta abordagem é que: - ainda é simples de entender - permite várias visitas (não apenas string)

Se você estiver disposto a descartar o primeiro, crie uma macro FOREACH () e, em seguida, #define ERROR_VALUES() (ErrorA, ErrorB, ErrorC)escreva seus visitantes em termos de FOREACH (). Em seguida, tente passar por uma revisão de código :).

Lorro
fonte