Como converter uma variável de tipo enum em uma string?

128

Como fazer printf para mostrar os valores de variáveis ​​que são do tipo enum? Por exemplo:

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

e o que eu preciso é algo como

printenum(OS_type, "My OS is %s", myOS);

que deve mostrar uma string "Linux", não um número inteiro.

Suponho que primeiro tenha que criar uma matriz de cadeias indexada por valor. Mas não sei se essa é a maneira mais bonita de fazer isso. É possível de alguma forma?

psihodelia
fonte
2
Faça a sua escolha: aqui , aqui , aqui . É uma bagunça, são todos semi-duplicados.
21414 Rubenvb
Outro , este é mais completo.
bit2shift 27/03/16
Possível duplicado de stackoverflow.com/questions/207976/... para C ++ e stackoverflow.com/questions/9907160/... para C
Ciro Santilli郝海东冠状病六四事件法轮功
Tantas respostas ainda sem "classe enum"
Vivick

Respostas:

70

Realmente não há uma maneira bonita de fazer isso. Basta configurar uma matriz de seqüências indexadas pelo enum.

Se você realiza muitas saídas, pode definir um operador << que recebe um parâmetro enum e faz a pesquisa por você.

Bo Persson
fonte
2
Além disso, você pode verificar, em tempo de compilação, se sua matriz possui o número esperado de seqüências de caracteres.
23611 Markh44
2
Eu sei que sou a grande minoria disso, mas para programadores como eu que não desejam confiar em grandes bibliotecas de terceiros e / ou código cheio de marcos para resolver deficiências nativas do idioma, acho que isso é a solução mais simples e pura para o padrão atual de longe. +1
Syndog 13/02/2014
13
@ Syndog Então os 56 enumeradores de enumeração longa em seu código de produção são atualizados por esse programador sob muita pressão para liberar um recurso atrasado, e ele esquece de atualizar essa matriz indexada por enumeração. Isso passa despercebido, porque o recurso de impressão relacionado é usado apenas pelo código de depuração do aplicativo. 2 meses depois, você é o primeiro a realmente executar esse código de depuração: ele fornece as informações incorretas, para que você perca meio dia criando suposições com base nessas informações erradas, antes de perceber que primeiro teve que depurar o código de depuração: o design depende de duplicação explícita.
Ad N
1
@ AdN Esse design está errado. O mapeamento de enum para string legível por humanos não deve ser implementado como uma matriz de strings indexadas pelo valor de enum. Sua experiência (presumivelmente) mostra o porquê. O mapeamento deve ser uma matriz explicativa de pares (enum, string); portanto, se você esquecer de adicionar uma entrada para seu novo valor de enum, obterá "???" como saída, mas pelo menos não irá estragar os nomes de todas as suas outras enumerações.
brewbuck
8
@AdN, seu cenário é o motivo pelo qual prefiro uma função que contém uma opção (sem cláusula padrão) em vez de uma matriz, e definir as opções do compilador no arquivo de compilação para emitir um erro para uma opção sobre uma enumeração que não cobre tudo valores possíveis. Adicionar uma nova entrada de enum sem atualizar as instruções de opção relevantes causará um erro de compilação.
divegeek
131

A solução ingênua, é claro, é escrever uma função para cada enumeração que realiza a conversão em string:

enum OS_type { Linux, Apple, Windows };

inline const char* ToString(OS_type v)
{
    switch (v)
    {
        case Linux:   return "Linux";
        case Apple:   return "Apple";
        case Windows: return "Windows";
        default:      return "[Unknown OS_type]";
    }
}

Isso, no entanto, é um desastre de manutenção. Com a ajuda da biblioteca Boost.Preprocessor, que pode ser usada com o código C e C ++, você pode aproveitar facilmente o pré-processador e deixá-lo gerar essa função para você. A macro de geração é a seguinte:

#include <boost/preprocessor.hpp>

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

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }

A primeira macro (começando com X_) é usada internamente pela segunda. A segunda macro primeiro gera a enumeração e, em seguida, gera uma ToStringfunção que pega um objeto desse tipo e retorna o nome do enumerador como uma seqüência de caracteres (essa implementação, por razões óbvias, requer que os enumeradores sejam mapeados para valores exclusivos).

Em C ++, você poderia implementar a ToStringfunção como uma operator<<sobrecarga, mas acho que é um pouco mais limpo exigir um " ToString" explícito para converter o valor em forma de string.

Como exemplo de uso, sua OS_typeenumeração seria definida da seguinte maneira:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))

Embora a macro pareça muito trabalhosa e a definição de OS_typeaparência bastante estranha, lembre-se de que você deve escrever a macro uma vez e usá-la para todas as enumerações. Você pode adicionar funcionalidade adicional a ele (por exemplo, uma forma de string para conversão de enum) sem muitos problemas e resolve completamente o problema de manutenção, pois você só precisa fornecer os nomes uma vez quando invoca a macro.

A enumeração pode ser usada como se estivesse definida normalmente:

#include <iostream>

int main()
{
    OS_type t = Windows;
    std::cout << ToString(t) << " " << ToString(Apple) << std::endl;
}

Os trechos de código nesta postagem, começando com a #include <boost/preprocessor.hpp>linha, podem ser compilados conforme publicados para demonstrar a solução.

Essa solução específica é para C ++, pois usa sintaxe específica para C ++ (por exemplo, não typedef enum) e sobrecarga de função, mas seria fácil fazer isso funcionar com C também.

James McNellis
fonte
7
+1, o mecanismo de implementação é assustador, mas é difícil superar a interface final por elegância.
deft_code 23/02
4
Existe alguma maneira de obter isso para também permitir que você forneça os valores inteiros enum. Por exemplo, o Windows seria 3, Linux 5 e Apple 7?
Mark
4
Sim, você pode mudar (Windows)em (Windows, 3)seguida, substituir o BOOST_PP_SEQ_ENUMcom uma escrita adequadamente BOOST_PP_SEQ_FOR_EACH. Não tenho um exemplo disso útil, mas posso escrever um, se quiser.
21411 James McNellis
2
@JamesMcNellis Eu definitivamente gostaria de um exemplo de código que atenda ao que Mark pediu, você gostaria de nos mostrar o caminho? :)
Omer Raviv
2
NOTA: o pré-processador boost possui um limite rígido de 256 elementos. Para enum maiores, é necessária uma solução diferente.
dshin
32

Este é o bloco do pré-processador

#ifndef GENERATE_ENUM_STRINGS
    #define DECL_ENUM_ELEMENT( element ) element
    #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME
    #define END_ENUM( ENUM_NAME ) ENUM_NAME; \
            char* GetString##ENUM_NAME(enum tag##ENUM_NAME index);
#else
    #define DECL_ENUM_ELEMENT( element ) #element
    #define BEGIN_ENUM( ENUM_NAME ) char* gs_##ENUM_NAME [] =
    #define END_ENUM( ENUM_NAME ) ; char* GetString##ENUM_NAME(enum \
            tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; }
#endif

Definição de enum

BEGIN_ENUM(Os_type)
{
    DECL_ENUM_ELEMENT(winblows),
    DECL_ENUM_ELEMENT(hackintosh),
} END_ENUM(Os_type)

Ligue usando

GetStringOs_type(winblows);

Retirado daqui . Quão legal é isso ? :)

Reno
fonte
1
Essa é a única solução que funciona quando sua enumeração possui mais de 256 elementos.
Dshin
8

Use std::map<OS_type, std::string>e preencha-o com enumeração como chave e representação de cadeia como valores, então você pode fazer o seguinte:

printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;
Nawaz
fonte
7

O problema com enumerações C é que não é um tipo próprio, como é em C ++. Uma enumeração em C é uma maneira de mapear identificadores para valores integrais. Só isso. É por isso que um valor enum é intercambiável com valores inteiros.

Como você adivinha corretamente, uma boa maneira é criar um mapeamento entre o valor da enumeração e uma string. Por exemplo:

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};
Andrew
fonte
Eu assumi - aparentemente incorretamente - a linguagem de programação é restrita a C. #
238 Andrew Andrew
1
você está um pouco desligado, enum são tipos em C. As constantes do tipo de enumeração integral são do tipo inte não do enumtipo pelo qual elas são definidas, talvez seja o que você quis dizer. Mas não vejo o que isso tem a ver com a questão.
Jens Gustedt 23/02
7

Eu combinei o James' , Howard e de Eder soluções e criou uma aplicação mais genérica:

  • O valor int e a representação de sequência customizada podem ser opcionalmente definidos para cada elemento enum
  • "enum class" é usado

O código completo está escrito abaixo (use "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" para definir uma enumeração) ( demonstração online ).

#include <boost/preprocessor.hpp>
#include <iostream>

// ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ implementation is taken from:
// http://lists.boost.org/boost-users/2012/09/76055.php
//
// This macro do the following:
// input:
//      (Element1, "Element 1 string repr", 2) (Element2) (Element3, "Element 3 string repr")
// output:
//      ((Element1, "Element 1 string repr", 2)) ((Element2)) ((Element3, "Element 3 string repr"))
#define HELPER1(...) ((__VA_ARGS__)) HELPER2
#define HELPER2(...) ((__VA_ARGS__)) HELPER1
#define HELPER1_END
#define HELPER2_END
#define ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(sequence) BOOST_PP_CAT(HELPER1 sequence,_END)


// CREATE_ENUM_ELEMENT_IMPL works in the following way:
//  if (elementTuple.GetSize() == 4) {
//      GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)),
//  } else {
//      GENERATE: elementTuple.GetElement(0),
//  }
// Example 1:
//      CREATE_ENUM_ELEMENT_IMPL((Element1, "Element 1 string repr", 2, _))
//  generates:
//      Element1 = 2,
//
// Example 2:
//      CREATE_ENUM_ELEMENT_IMPL((Element2, _))
//  generates:
//      Element1,
#define CREATE_ENUM_ELEMENT_IMPL(elementTuple)                                          \
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 4),                       \
    BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple),        \
    BOOST_PP_TUPLE_ELEM(0, elementTuple)                                                \
),

// we have to add a dummy element at the end of a tuple in order to make 
// BOOST_PP_TUPLE_ELEM macro work in case an initial tuple has only one element.
// if we have a tuple (Element1), BOOST_PP_TUPLE_ELEM(2, (Element1)) macro won't compile.
// It requires that a tuple with only one element looked like (Element1,).
// Unfortunately I couldn't find a way to make this transformation, so
// I just use BOOST_PP_TUPLE_PUSH_BACK macro to add a dummy element at the end
// of a tuple, in this case the initial tuple will look like (Element1, _) what
// makes it compatible with BOOST_PP_TUPLE_ELEM macro
#define CREATE_ENUM_ELEMENT(r, data, elementTuple)                                      \
    CREATE_ENUM_ELEMENT_IMPL(BOOST_PP_TUPLE_PUSH_BACK(elementTuple, _))

#define DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, element)                                        \
    case enumName::element : return BOOST_PP_STRINGIZE(element);
#define DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, element, stringRepresentation)  \
    case enumName::element : return stringRepresentation;

// GENERATE_CASE_FOR_SWITCH macro generates case for switch operator.
// Algorithm of working is the following
//  if (elementTuple.GetSize() == 1) {
//      DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, elementTuple.GetElement(0))
//  } else {
//      DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, elementTuple.GetElement(0), elementTuple.GetElement(1))
//  }
//
// Example 1:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element1, "Element 1 string repr", 2))
//  generates:
//      case EnumName::Element1 : return "Element 1 string repr";
//
// Example 2:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element2))
//  generates:
//      case EnumName::Element2 : return "Element2";
#define GENERATE_CASE_FOR_SWITCH(r, enumName, elementTuple)                                                                                                 \
    BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 1),                                                                                       \
        DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple)),                                                          \
        DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple), BOOST_PP_TUPLE_ELEM(1, elementTuple))     \
    )


// DEFINE_ENUM_CLASS_WITH_ToString_METHOD final macro witch do the job
#define DEFINE_ENUM_CLASS_WITH_ToString_METHOD(enumName, enumElements)          \
enum class enumName {                                                           \
    BOOST_PP_SEQ_FOR_EACH(                                                      \
        CREATE_ENUM_ELEMENT,                                                    \
        0,                                                                      \
        ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)                     \
    )                                                                           \
};                                                                              \
inline const char* ToString(const enumName element) {                           \
        switch (element) {                                                      \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                GENERATE_CASE_FOR_SWITCH,                                       \
                enumName,                                                       \
                ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)             \
            )                                                                   \
            default: return "[Unknown " BOOST_PP_STRINGIZE(enumName) "]";       \
        }                                                                       \
}

DEFINE_ENUM_CLASS_WITH_ToString_METHOD(Elements,
(Element1)
(Element2, "string representation for Element2 ")
(Element3, "Element3 string representation", 1000)
(Element4, "Element 4 string repr")
(Element5, "Element5", 1005)
(Element6, "Element6 ")
(Element7)
)
// Generates the following:
//      enum class Elements {
//          Element1, Element2, Element3 = 1000, Element4, Element5 = 1005, Element6,
//      };
//      inline const char* ToString(const Elements element) {
//          switch (element) {
//              case Elements::Element1: return "Element1";
//              case Elements::Element2: return "string representation for Element2 ";
//              case Elements::Element3: return "Element3 string representation";
//              case Elements::Element4: return "Element 4 string repr";
//              case Elements::Element5: return "Element5";
//              case Elements::Element6: return "Element6 ";
//              case Elements::Element7: return "Element7";
//              default: return "[Unknown " "Elements" "]";
//          }
//      }

int main() {
    std::cout << ToString(Elements::Element1) << std::endl;
    std::cout << ToString(Elements::Element2) << std::endl;
    std::cout << ToString(Elements::Element3) << std::endl;
    std::cout << ToString(Elements::Element4) << std::endl;
    std::cout << ToString(Elements::Element5) << std::endl;
    std::cout << ToString(Elements::Element6) << std::endl;
    std::cout << ToString(Elements::Element7) << std::endl;

    return 0;
}
Urso polar
fonte
Esta é a melhor resposta até agora
Arnout
6

Este exemplo simples funcionou para mim. Espero que isto ajude.

#include <iostream>
#include <string>

#define ENUM_TO_STR(ENUM) std::string(#ENUM)

enum DIRECTION{NORTH, SOUTH, WEST, EAST};

int main()
{
  std::cout << "Hello, " << ENUM_TO_STR(NORTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(SOUTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(EAST) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(WEST) << "!\n";
}
dgmz
fonte
13
Não funcionará se você tiver a DIREÇÃO a = NORTE; e escreva ENUM_TO_STR (a)
mathreadler
5

Você tentou o seguinte:

#define stringify( name ) # name

enum enMyErrorValue
  {
  ERROR_INVALIDINPUT = 0,
  ERROR_NULLINPUT,
  ERROR_INPUTTOOMUCH,
  ERROR_IAMBUSY
  };

const char* enMyErrorValueNames[] = 
  {
  stringify( ERROR_INVALIDINPUT ),
  stringify( ERROR_NULLINPUT ),
  stringify( ERROR_INPUTTOOMUCH ),
  stringify( ERROR_IAMBUSY )
  };

void vPrintError( enMyErrorValue enError )
  {
  cout << enMyErrorValueNames[ enError ] << endl;
  }

int main()
  {
  vPrintError((enMyErrorValue)1);
  }

A stringify()macro pode ser usada para transformar qualquer texto no seu código em uma string, mas apenas o texto exato entre parênteses. Não há desreferenciamento variável ou substituições de macro ou qualquer outro tipo de ação.

http://www.cplusplus.com/forum/general/2949/

M.Ali
fonte
Este deve realmente ser superior, embora apenas o primeiro de que seria suficiente suficiente :)
pholat
Funciona bem, mas você deve adicionar #ifndef stringify na parte superior para evitar erros de compilação. Também mudei o tipo de enum como std :: string, conforme sugerido pelo dgmz.
astarakastara 24/02
5

Há muitas respostas boas aqui, mas achei que algumas pessoas poderiam achar as minhas úteis. Gosto porque a interface que você usa para definir a macro é o mais simples possível. Também é útil porque você não precisa incluir nenhuma biblioteca extra - tudo vem com C ++ e nem sequer requer uma versão realmente tardia. Peguei peças de vários lugares on-line para não poder receber crédito por tudo isso, mas acho que é único o suficiente para garantir uma nova resposta.

Primeiro, crie um arquivo de cabeçalho ... chame-o de EnumMacros.h ou algo assim e coloque isso:

// Search and remove whitespace from both ends of the string
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { rit++; }
    return std::string(it, rit.base());
}

static void SplitEnumArgs(const char* szArgs, std::string Array[], int nMax)
{
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        Array[nIdx] = TrimEnumString(strSub);
        nIdx++;
    }
};
// This will to define an enum that is wrapped in a namespace of the same name along with ToString(), FromString(), and COUNT
#define DECLARE_ENUM(ename, ...) \
    namespace ename { \
        enum ename { __VA_ARGS__, COUNT }; \
        static std::string _Strings[COUNT]; \
        static const char* ToString(ename e) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            return _Strings[e].c_str(); \
        } \
        static ename FromString(const std::string& strEnum) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            for (int i = 0; i < COUNT; i++) { if (_Strings[i] == strEnum) { return (ename)i; } } \
            return COUNT; \
        } \
    }

Então, no seu programa principal, você pode fazer isso ...

#include "EnumMacros.h"
DECLARE_ENUM(OsType, Windows, Linux, Apple)

void main() {
    OsType::OsType MyOs = OSType::Apple;
    printf("The value of '%s' is: %d of %d\n", OsType::ToString(MyOs), (int)OsType::FromString("Apple"), OsType::COUNT);
}

Onde a saída seria >> O valor de 'Apple' é: 2 de 4

Aproveitar!

Ph0t0n
fonte
A principal coisa que eu gosto nessa abordagem em particular é que ela funciona com a sintaxe normal da vírgula separada por vírgula (desde que não inclua nenhuma atribuição de configuração de valor dentro da enum). No meu caso, eu tive que trabalhar com uma enumeração existente com um grande número de membros, portanto isso foi muito mais fácil do que a abordagem de impulso.
CuriousKea
4

Supondo que sua enumeração já esteja definida, você pode criar uma matriz de pares:

std::pair<QTask::TASK, QString> pairs [] = {
std::pair<OS_type, string>(Linux, "Linux"),
std::pair<OS_type, string>(Windows, "Windows"),
std::pair<OS_type, string>(Apple, "Apple"),
};

Agora, você pode criar um mapa:

std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));

Agora você pode usar o mapa. Se sua enumeração for alterada, você precisará adicionar / remover pares dos pares de matrizes []. Eu acho que é a maneira mais elegante de obter uma string de enum em C ++.

Vladimir
fonte
2
Além do comentário justo de que não há necessidade de Qt aqui, outro ponto é que você pode querer usar o Boost's bimapcaso queira analisar nomes e transformá-los em enumerações (por exemplo, de um arquivo XML).
Dmitri Nesteruk 14/09
4
Não deve usar tipos Qt em uma pergunta C ++ genérica.
Vector
3

Para C99, existe P99_DECLARE_ENUMno P99 que permite que você simplesmente declare enumassim:

P99_DECLARE_ENUM(color, red, green, blue);

e use color_getname(A)para obter uma sequência com o nome da cor.

Jens Gustedt
fonte
2

Aqui está o meu código C ++:

/* 
 * File:   main.cpp
 * Author: y2k1234
 *
 * Created on June 14, 2013, 9:50 AM
 */

#include <cstdlib>
#include <stdio.h>

using namespace std;


#define MESSAGE_LIST(OPERATOR)                          \
                                       OPERATOR(MSG_A), \
                                       OPERATOR(MSG_B), \
                                       OPERATOR(MSG_C)
#define GET_LIST_VALUE_OPERATOR(msg)   ERROR_##msg##_VALUE
#define GET_LIST_SRTING_OPERATOR(msg)  "ERROR_"#msg"_NAME"

enum ErrorMessagesEnum
{
   MESSAGE_LIST(GET_LIST_VALUE_OPERATOR)
};
static const char* ErrorMessagesName[] = 
{
   MESSAGE_LIST(GET_LIST_SRTING_OPERATOR)
};

int main(int argc, char** argv) 
{

    int totalMessages = sizeof(ErrorMessagesName)/4;

    for (int i = 0; i < totalMessages; i++)
    {
        if (i == ERROR_MSG_A_VALUE)
        {
                printf ("ERROR_MSG_A_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_B_VALUE)
        {
                printf ("ERROR_MSG_B_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_C_VALUE)
        {
                printf ("ERROR_MSG_C_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else
        {
                printf ("??? => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
    }   

    return 0;
}

Output:

ERROR_MSG_A_VALUE => [0]=[ERROR_MSG_A_NAME]

ERROR_MSG_B_VALUE => [1]=[ERROR_MSG_B_NAME]

ERROR_MSG_C_VALUE => [2]=[ERROR_MSG_C_NAME]

RUN SUCCESSFUL (total time: 126ms)
y2k1234
fonte
2

Um pouco atrasado para a festa, mas aqui está a minha solução C ++ 11:

namespace std {
    template<> struct hash<enum_one> {
        std::size_t operator()(const enum_one & e) const {
            return static_cast<std::size_t>(e);
        }
    };
    template<> struct hash<enum_two> { //repeat for each enum type
        std::size_t operator()(const enum_two & e) const {
            return static_cast<std::size_t>(e);
        }
    };
}

const std::string & enum_name(const enum_one & e) {
    static const std::unordered_map<enum_one, const std::string> names = {
    #define v_name(n) {enum_one::n, std::string(#n)}
        v_name(value1),
        v_name(value2),
        v_name(value3)
    #undef v_name
    };
    return names.at(e);
}

const std::string & enum_name(const enum_two & e) { //repeat for each enum type
    .................
}
OneOfOne
fonte
1
error: ‘hash’ is not a class template->#include <functional>
Ruggero Turra 11/11
2

Minha preferência é minimizar a digitação repetitiva e difícil de entender as macros e evitar a introdução de definições de macro no espaço geral do compilador.

Então, no arquivo de cabeçalho:

enum Level{
        /**
        * zero reserved for internal use
        */
        verbose = 1,
        trace,
        debug,
        info,
        warn,
        fatal
    };

static Level readLevel(const char *);

e a implementação do cpp é:

 Logger::Level Logger::readLevel(const char *in) { 
 #  define MATCH(x) if (strcmp(in,#x) ==0) return x; 
    MATCH(verbose);
    MATCH(trace);
    MATCH(debug);
    MATCH(info);
    MATCH(warn);
    MATCH(fatal);
 # undef MATCH
    std::string s("No match for logging level ");
    s += in;
    throw new std::domain_error(s);
 }

Observe o #undef da macro assim que terminarmos.

gerardw
fonte
2

Minha solução, não usando boost:

#ifndef EN2STR_HXX_
#define EN2STR_HXX_

#define MAKE_STRING_1(str     ) #str
#define MAKE_STRING_2(str, ...) #str, MAKE_STRING_1(__VA_ARGS__)
#define MAKE_STRING_3(str, ...) #str, MAKE_STRING_2(__VA_ARGS__)
#define MAKE_STRING_4(str, ...) #str, MAKE_STRING_3(__VA_ARGS__)
#define MAKE_STRING_5(str, ...) #str, MAKE_STRING_4(__VA_ARGS__)
#define MAKE_STRING_6(str, ...) #str, MAKE_STRING_5(__VA_ARGS__)
#define MAKE_STRING_7(str, ...) #str, MAKE_STRING_6(__VA_ARGS__)
#define MAKE_STRING_8(str, ...) #str, MAKE_STRING_7(__VA_ARGS__)

#define PRIMITIVE_CAT(a, b) a##b
#define MAKE_STRING(N, ...) PRIMITIVE_CAT(MAKE_STRING_, N)     (__VA_ARGS__)


#define PP_RSEQ_N() 8,7,6,5,4,3,2,1,0
#define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
#define PP_NARG( ...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N())

#define MAKE_ENUM(NAME, ...) enum NAME { __VA_ARGS__ };            \
  struct NAME##_str {                                              \
    static const char * get(const NAME et) {                       \
      static const char* NAME##Str[] = {                           \
                MAKE_STRING(PP_NARG(__VA_ARGS__), __VA_ARGS__) };  \
      return NAME##Str[et];                                        \
      }                                                            \
    };

#endif /* EN2STR_HXX_ */

E aqui está como usá-lo

int main()
  {
  MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d);
  pippo c = d;
  cout << pippo_str::get(c) << "\n";
  return 0;
  }
Marco Amagliani
fonte
2

Outro atraso para a parte, usando o pré-processador:

 1  #define MY_ENUM_LIST \
 2      DEFINE_ENUM_ELEMENT(First) \
 3      DEFINE_ENUM_ELEMENT(Second) \
 4      DEFINE_ENUM_ELEMENT(Third) \
 5  
 6  //--------------------------------------
 7  #define DEFINE_ENUM_ELEMENT(name) , name
 8  enum MyEnum {
 9      Zeroth = 0
10      MY_ENUM_LIST
11  };
12  #undef DEFINE_ENUM_ELEMENT
13 
14  #define DEFINE_ENUM_ELEMENT(name) , #name
15  const char* MyEnumToString[] = {
16      "Zeroth"
17      MY_ENUM_LIST
18  };
19  #undef DEFINE_ENUM_ELEMENT
20
21  #define DEFINE_ENUM_ELEMENT(name) else if (strcmp(s, #name)==0) return name;
22  enum MyEnum StringToMyEnum(const char* s){
23      if (strcmp(s, "Zeroth")==0) return Zeroth;
24      MY_ENUM_LIST
25      return NULL;
26  }
27  #undef DEFINE_ENUM_ELEMENT

(Basta inserir os números das linhas para facilitar a conversa.) As linhas 1 a 4 são as que você edita para definir os elementos da enumeração. (Eu chamei isso de "macro de lista", porque é uma macro que faz uma lista de coisas. @Lundin me informa que essa é uma técnica bem conhecida chamada X-macros.)

A linha 7 define a macro interna para preencher a declaração de enum real nas linhas 8-11. A linha 12 undefine a macro interna (apenas para silenciar o aviso do compilador).

A linha 14 define a macro interna para criar uma versão em cadeia do nome do elemento enum. Em seguida, as linhas 15-18 geram uma matriz que pode converter um valor de enumeração na sequência correspondente.

As linhas 21 a 27 geram uma função que converte uma string no valor de enum ou retorna NULL se a string não corresponder a nenhuma.

Isso é um pouco complicado na maneira como lida com o 0º elemento. Eu realmente trabalhei com isso no passado.

Admito que essa técnica incomoda as pessoas que não querem pensar que o próprio pré-processador pode ser programado para escrever código para você. Eu acho que ilustra fortemente a diferença entre legibilidade e facilidade de manutenção . É difícil ler o código, mas se a enumeração tiver algumas centenas de elementos, você poderá adicionar, remover ou reorganizar elementos e ainda assim garantir que o código gerado não tenha erros.

Mike Dunlavey
fonte
As "macros X" raramente são uma solução elegante para qualquer problema. Nesse caso, seria muito mais legível simplesmente definir os itens de macro como #define TEST_1 hello #define TEST_2 worldentão typedef enum { TEST_1, TEST_2 } test_t;e criar uma tabela de consulta de string que use uma macro stringify: const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; já existem várias respostas sugerindo soluções semelhantes. Muito mais legível.
Lundin 05/10
@Lundin: Eu apenas reivindico 1) isso funciona mesmo com o compilador C mais primitivo e 2) adicionar ou excluir um elemento é uma edição de 1 linha.
Mike Dunlavey
Publiquei uma resposta minha: stackoverflow.com/a/39877228/584518 . Espero que ele salve uma alma pobre das soluções x macros.
Lundin
1
Eu usei sua solução. Eu acho que é o melhor. A sintaxe C ainda está lá para que você entenda o que acontece e a lista seja definida apenas uma vez. Você pode remover o 0º elemento, colocando a vírgula após a entrada no seu DEFINE_ENUM_ELEMENT.
isgoed
1

Aqui está o método Old Skool (usado extensivamente no gcc) usando apenas o pré-processador C. Útil se você estiver gerando estruturas de dados discretas, mas precisar manter a ordem consistente entre elas. As entradas em mylist.tbl podem, obviamente, ser estendidas para algo muito mais complexo.

test.cpp:

enum {
#undef XX
#define XX(name, ignore) name ,
#include "mylist.tbl"
  LAST_ENUM
};

char * enum_names [] = {
#undef XX
#define XX(name, ignore) #name ,
#include "mylist.tbl"
   "LAST_ENUM"
};

E então mylist.tbl:

/*    A = enum                  */
/*    B = some associated value */
/*     A        B   */
  XX( enum_1 , 100)
  XX( enum_2 , 100 )
  XX( enum_3 , 200 )
  XX( enum_4 , 900 )
  XX( enum_5 , 500 )
O duque
fonte
1
Essa técnica é chamada x macros!
Watusimoto 19/03/14
0

Em c ++ como este:

enum OS_type{Linux, Apple, Windows};

std::string ToString( const OS_type v )
{
  const std::map< OS_type, std::string > lut =
    boost::assign::map_list_of( Linux, "Linux" )(Apple, "Apple )( Windows,"Windows");
  std::map< OS_type, std::string >::const_iterator it = lut.find( v );
  if ( lut.end() != it )
    return it->second;
  return "NOT FOUND";
}
BЈовић
fonte
0
#include <EnumString.h>

de http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C e depois

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

inserir

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 na enumeração não forem duplicados.

Código de amostra para converter um valor de enum em string:

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

Código de exemplo para o oposto:

assert( EnumString< FORM >::To( f, str ) );
Andrey Syrokomskiy
fonte
0

Obrigado James pela sua sugestão. Foi muito útil, então implementei o contrário para contribuir de alguma forma.

#include <iostream>
#include <boost/preprocessor.hpp>

using namespace std;

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

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF(r, data, elem) \
    if (BOOST_PP_SEQ_TAIL(data) ==                                     \
            BOOST_PP_STRINGIZE(elem)) return                           \
            static_cast<int>(BOOST_PP_SEQ_HEAD(data)::elem); else

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)         \
    enum class name {                                                  \
        BOOST_PP_SEQ_ENUM(enumerators)                                 \
    };                                                                 \
                                                                       \
    inline const char* ToString(name v)                                \
    {                                                                  \
        switch (v)                                                     \
        {                                                              \
            BOOST_PP_SEQ_FOR_EACH(                                     \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,   \
                name,                                                  \
                enumerators                                            \
            )                                                          \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";  \
        }                                                              \
    }                                                                  \
                                                                       \
    inline int ToEnum(std::string s)                                   \
    {                                                                  \
        BOOST_PP_SEQ_FOR_EACH(                                         \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF,       \
                (name)(s),                                             \
                enumerators                                            \
            )                                                          \
        return -1;                                                     \
    }


DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows));

int main(void)
{
    OS_type t = OS_type::Windows;

    cout << ToString(t) << " " << ToString(OS_type::Apple) << " " << ToString(OS_type::Linux) << endl;

    cout << ToEnum("Windows") << " " << ToEnum("Apple") << " " << ToEnum("Linux") << endl;

    return 0;
}
Éder
fonte
0

Para estender a resposta de James, alguém quer algum código de exemplo para suportar enum define com valor int, também tenho esse requisito, então aqui está o meu caminho:

A primeira é a macro de uso interno, usada por FOR_EACH:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE(r, data, elem)         \
    BOOST_PP_IF(                                                                \
        BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elem), 2),                           \
        BOOST_PP_TUPLE_ELEM(0, elem) = BOOST_PP_TUPLE_ELEM(1, elem),            \
        BOOST_PP_TUPLE_ELEM(0, elem) ),

E, aqui está a macro define:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                  \
    enum name {                                                                 \
        BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \
                              0, enumerators) };

Portanto, ao usá-lo, você pode escrever assim:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum,
    ((FIRST, 1))
    ((SECOND))
    ((MAX, SECOND)) )

que será expandido para:

enum MyEnum
{
    FIRST = 1,
    SECOND,
    MAX = SECOND,
};

A idéia básica é definir um SEQ, que todo elemento é um TUPLE, para que possamos adicionar valor ao membro enum. No loop FOR_EACH, verifique o tamanho do item TUPLE, se o tamanho for 2, expanda o código para KEY = VALUE, caso contrário, mantenha o primeiro elemento do TUPLE.

Como o SEQ de entrada é realmente TUPLEs, portanto, se você deseja definir funções STRINGIZE, pode ser necessário pré-processar os enumeradores de entrada primeiro, eis a macro para executar o trabalho:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM(r, data, elem)           \
    BOOST_PP_TUPLE_ELEM(0, elem),

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ(enumerators)         \
    BOOST_PP_SEQ_SUBSEQ(                                                        \
        BOOST_PP_TUPLE_TO_SEQ(                                                  \
            (BOOST_PP_SEQ_FOR_EACH(                                             \
                DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM, 0, enumerators) \
            )),                                                                 \
            0,                                                                  \
            BOOST_PP_SEQ_SIZE(enumerators))

A macro DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQmanterá apenas o primeiro elemento em cada TUPLE e, posteriormente, será convertida para SEQ, agora modifique o código de James, e você terá todo o poder.

Minha implementação talvez não seja a mais simples; portanto, se você não encontrar nenhum código limpo, use o meu para sua referência.

Howard Gong
fonte
0

Solução limpa e segura em puro padrão C:

#include <stdio.h>

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

/* list of enum constants */
#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N
} test_t;

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

int main()
{  
  _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
                 "Incorrect number of items in enum or look-up table");

  printf("%d %s\n", hello, test_str[hello]);
  printf("%d %s\n", world, test_str[world]);
  test_t x = world;
  printf("%d %s\n", x, test_str[x]);

  return 0;
}

Resultado

0 hello
1 world
1 world

Fundamentação

Ao resolver o problema principal "tenha constantes enum com cadeias correspondentes", um programador sensato apresentará os seguintes requisitos:

  • Evite a repetição de código (princípio "DRY").
  • O código deve ser escalável, sustentável e seguro, mesmo se itens forem adicionados ou removidos dentro da enumeração.
  • Todo o código deve ser de alta qualidade: fácil de ler, fácil de manter.

O primeiro requisito, e talvez também o segundo, pode ser cumprido com várias soluções macro confusas, como o infame truque "x macro" ou outras formas de macro mágica. O problema dessas soluções é que elas deixam uma confusão completamente ilegível de macros misteriosas - elas não atendem ao terceiro requisito acima.

A única coisa necessária aqui é na verdade ter uma tabela de consulta de strings, à qual podemos acessar usando a variável enum como índice. Essa tabela deve naturalmente corresponder diretamente ao enum e vice-versa. Quando um deles é atualizado, o outro também deve ser atualizado ou não funcionará.


Explicação do código

Suponha que tenhamos uma enumeração como

typedef enum
{
  hello,
  world
} test_t;

Isso pode ser alterado para

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

Com a vantagem de que essas constantes de macro agora podem ser usadas em outros lugares, por exemplo, gerar uma tabela de consulta de string. A conversão de uma constante do pré-processador em uma string pode ser feita com uma macro "stringify":

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

E é isso. Ao usar hello, obtemos a constante enum com valor 0. Ao usar test_str[hello], obtemos a string "hello".

Para fazer com que a tabela de enum e consulta correspondam diretamente, precisamos garantir que eles contenham a mesma quantidade de itens. Se alguém manter o código e alterar apenas a enumeração, e não a tabela de consulta, ou vice-versa, esse método não funcionará.

A solução é ter a enumeração para informar quantos itens ela contém. Existe um truque C comumente usado para isso: basta adicionar um item no final, o que apenas cumpre o objetivo de dizer quantos itens a enumeração possui:

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N  // will have value 2, there are 2 enum constants in this enum
} test_t;

Agora podemos verificar em tempo de compilação se o número de itens na enumeração é igual ao número de itens na tabela de consulta, de preferência com uma declaração estática C11:

_Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
               "Incorrect number of items in enum or look-up table");

(Existem maneiras feias, mas totalmente funcionais, de criar afirmações estáticas em versões mais antigas do padrão C também, se alguém insistir em usar compiladores de dinossauros. Quanto ao C ++, ele também suporta afirmações estáticas.)


Como observação lateral, em C11 também podemos obter maior segurança de tipo alterando a macro stringify:

#define STRINGIFY(x) _Generic((x), int : STRF(x))

( intporque as constantes de enumeração são realmente do tipo int, não test_t)

Isso impedirá que o código STRINGIFY(random_stuff)seja compilado.

Lundin
fonte
Entendo o que você está dizendo, mas o ponto permanece. As alterações previsíveis típicas devem exigir edições mínimas (como 1 linha). (Acho que essa é a razão por trás do DRY.) Então, aqui, se o tamanho da enumeração for 500, e você desejar inserir um novo elemento no meio (ou remover / renomear / trocar), quantas linhas de código você precisará alterar e quanta verificação você deve fazer para garantir que não cometeu um erro? Também pode haver outras partes do código que fazem algo uniforme para cada elemento da lista.
precisa saber é o seguinte
Obrigado por me dizer que estas são chamadas X-macros . Eu não sabia disso. O que não vejo são as pessoas que os denegrem em geral.
precisa saber é o seguinte
@MikeDunlavey Não importa o tamanho da enumeração, você terá que alterar exatamente 3 linhas: adicione a #define, adicione uma referência àquela definida na declaração da enumeração e na tabela de consulta. Se você gostaria de adicionar essas linhas, o programa não será compilado. Os números que adicionei aos identificadores não são de forma alguma obrigatórios, você também pode escrever #define APPLES helloe #define ORANGES worldseguir, typedef enum { APPES, ORANGES, TEST_N } test_t;e assim por diante.
Lundin #
@MikeDunlavey Em relação às macros X, os argumentos contra eles são os mesmos que os argumentos contra quaisquer macros semelhantes a funções. Você não precisará procurar muito para encontrar muitas críticas válidas contra macros de funções.
Lundin #
0

O que fiz é uma combinação do que vi aqui e em perguntas semelhantes neste site. Eu fiz isso no Visual Studio 2013. Não testei com outros compiladores.

Antes de tudo, defino um conjunto de macros que fará os truques.

// concatenation macros
#define CONCAT_(A, B) A ## B
#define CONCAT(A, B)  CONCAT_(A, B)

// generic expansion and stringification macros
#define EXPAND(X)           X
#define STRINGIFY(ARG)      #ARG
#define EXPANDSTRING(ARG)   STRINGIFY(ARG)        

// number of arguments macros
#define NUM_ARGS_(X100, X99, X98, X97, X96, X95, X94, X93, X92, X91, X90, X89, X88, X87, X86, X85, X84, X83, X82, X81, X80, X79, X78, X77, X76, X75, X74, X73, X72, X71, X70, X69, X68, X67, X66, X65, X64, X63, X62, X61, X60, X59, X58, X57, X56, X55, X54, X53, X52, X51, X50, X49, X48, X47, X46, X45, X44, X43, X42, X41, X40, X39, X38, X37, X36, X35, X34, X33, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, ...) N
#define NUM_ARGS(...) EXPAND(NUM_ARGS_(__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1))

// argument extraction macros
#define FIRST_ARG(ARG, ...) ARG
#define REST_ARGS(ARG, ...) __VA_ARGS__

// arguments to strings macros
#define ARGS_STR__(N, ...)  ARGS_STR_##N(__VA_ARGS__)
#define ARGS_STR_(N, ...)   ARGS_STR__(N, __VA_ARGS__)
#define ARGS_STR(...)       ARGS_STR_(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

#define ARGS_STR_1(ARG)     EXPANDSTRING(ARG)
#define ARGS_STR_2(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_1(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_3(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_2(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_4(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_3(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_5(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_4(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_6(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_5(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_7(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_6(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_8(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_7(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_9(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_8(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_10(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_9(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_11(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_10(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_12(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_11(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_13(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_12(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_14(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_13(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_15(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_14(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_16(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_15(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_17(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_16(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_18(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_17(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_19(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_18(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_20(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_19(EXPAND(REST_ARGS(__VA_ARGS__)))
// expand until _100 or as much as you need

Em seguida, defina uma única macro que criará a classe enum e as funções para obter as seqüências de caracteres.

#define ENUM(NAME, ...)                                                                                             \
    enum class NAME                                                                                                 \
    {                                                                                                               \
        __VA_ARGS__                                                                                                 \
    };                                                                                                              \
                                                                                                                    \
    static const std::array<std::string, NUM_ARGS(__VA_ARGS__)> CONCAT(NAME, Strings) = { ARGS_STR(__VA_ARGS__) };  \
                                                                                                                    \
    inline const std::string& ToString(NAME value)                                                                  \
    {                                                                                                               \
        return CONCAT(NAME, Strings)[static_cast<std::underlying_type<NAME>::type>(value)];                         \
    }                                                                                                               \
                                                                                                                    \
    inline std::ostream& operator<<(std::ostream& os, NAME value)                                                   \
    {                                                                                                               \
        os << ToString(value);                                                                                      \
        return os;                                                                                                  \
    }

Agora, definir um tipo de enumeração e ter cadeias de caracteres torna-se realmente fácil. Tudo que você precisa fazer é:

ENUM(MyEnumType, A, B, C);

As seguintes linhas podem ser usadas para testá-lo.

int main()
{
    std::cout << MyEnumTypeStrings.size() << std::endl;

    std::cout << ToString(MyEnumType::A) << std::endl;
    std::cout << ToString(MyEnumType::B) << std::endl;
    std::cout << ToString(MyEnumType::C) << std::endl;

    std::cout << MyEnumType::A << std::endl;
    std::cout << MyEnumType::B << std::endl;
    std::cout << MyEnumType::C << std::endl;

    auto myVar = MyEnumType::A;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::B;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::C;
    std::cout << myVar << std::endl;

    return 0;
}

Isso produzirá:

3
A
B
C
A
B
C
A
B
C

Eu acredito que é muito limpo e fácil de usar. Existem algumas limitações:

  • Você não pode atribuir valores aos membros da enumeração.
  • Os valores do membro da enum são usados ​​como índice, mas isso deve ser bom, porque tudo é definido em uma única macro.
  • Você não pode usá-lo para definir um tipo de enumeração dentro de uma classe.

Se você pode contornar isso. Eu acho que, especialmente como usá-lo, isso é agradável e enxuto. Vantagens:

  • Fácil de usar.
  • Nenhuma divisão de cadeia em tempo de execução é necessária.
  • Seqüências de caracteres separadas estão disponíveis em tempo de compilação.
  • Fácil de ler. O primeiro conjunto de macros pode precisar de um segundo extra, mas não é tão complicado assim.
jokr
fonte
0

Uma solução limpa para esse problema seria:

#define RETURN_STR(val, e) {if (val == e) {return #e;}}

std::string conv_dxgi_format_to_string(int value) {
    RETURN_STR(value, DXGI_FORMAT_UNKNOWN);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_FLOAT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_UINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_SINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_FLOAT);

    /* ... */

    return "<UNKNOWN>";
}

O bom dessa solução é que ela é simples e também a construção da função pode ser feita facilmente via cópia e substituição. Observe que, se você realizar muitas conversões e sua enumeração tiver muitos valores possíveis, essa solução poderá tornar-se intensiva da CPU.

Ali Alidoust
fonte
0

Estou um pouco atrasado, mas aqui está a minha solução usando g ++ e apenas bibliotecas padrão. Tentei minimizar a poluição do espaço para nome e remover qualquer necessidade de redigitar nomes de enum.

O arquivo de cabeçalho "my_enum.hpp" é:

#include <cstring>

namespace ENUM_HELPERS{
    int replace_commas_and_spaces_with_null(char* string){
        int i, N;
        N = strlen(string);
        for(i=0; i<N; ++i){
            if( isspace(string[i]) || string[i] == ','){
                string[i]='\0';
            }
        }
        return(N);
    }

    int count_words_null_delim(char* string, int tot_N){
        int i;
        int j=0;
        char last = '\0';
        for(i=0;i<tot_N;++i){
            if((last == '\0') && (string[i]!='\0')){
                ++j;
            }
            last = string[i];
        }
        return(j);
    }

    int get_null_word_offsets(char* string, int tot_N, int current_w){
        int i;
        int j=0;
        char last = '\0';
        for(i=0; i<tot_N; ++i){
            if((last=='\0') && (string[i]!='\0')){
                if(j == current_w){
                    return(i);
                }
                ++j;
            }
            last = string[i];
        }
        return(tot_N); //null value for offset
    }

    int find_offsets(int* offsets, char* string, int tot_N, int N_words){
        int i;
        for(i=0; i<N_words; ++i){
            offsets[i] = get_null_word_offsets(string, tot_N, i);
        }
        return(0);
    }
}


#define MAKE_ENUM(NAME, ...)                                            \
namespace NAME{                                                         \
    enum ENUM {__VA_ARGS__};                                            \
    char name_holder[] = #__VA_ARGS__;                                  \
    int name_holder_N =                                                 \
        ENUM_HELPERS::replace_commas_and_spaces_with_null(name_holder); \
    int N =                                                             \
        ENUM_HELPERS::count_words_null_delim(                           \
            name_holder, name_holder_N);                                \
    int offsets[] = {__VA_ARGS__};                                      \
    int ZERO =                                                          \
        ENUM_HELPERS::find_offsets(                                     \
            offsets, name_holder, name_holder_N, N);                    \
    char* tostring(int i){                                              \
       return(&name_holder[offsets[i]]);                                \
    }                                                                   \
}

Exemplo de uso:

#include <cstdio>
#include "my_enum.hpp"

MAKE_ENUM(Planets, MERCURY, VENUS, EARTH, MARS)

int main(int argc, char** argv){    
    Planets::ENUM a_planet = Planets::EARTH;
    printf("%s\n", Planets::tostring(Planets::MERCURY));
    printf("%s\n", Planets::tostring(a_planet));
}

Isso produzirá:

MERCURY
EARTH

Você só precisa definir tudo uma vez, seu espaço para nome não deve ser poluído e todo o cálculo é feito apenas uma vez (o resto são apenas pesquisas). No entanto, você não obtém a segurança de tipo das classes enum (elas ainda são apenas inteiros curtos), não pode atribuir valores às enumerações, é necessário definir enumerações em algum lugar em que possa definir namespaces (por exemplo, globalmente).

Não tenho certeza de quão bom é o desempenho disso ou se é uma boa ideia (eu aprendi C antes de C ++, para que meu cérebro ainda funcione dessa maneira). Se alguém souber por que essa é uma má idéia, sinta-se à vontade para apontar.

Alias ​​Fakename
fonte
0

É 2017, mas a questão ainda está viva

Ainda outra maneira:

#include <iostream>

#define ERROR_VALUES \
ERROR_VALUE(NO_ERROR, 0, "OK") \
ERROR_VALUE(FILE_NOT_FOUND, 1, "Not found") \
ERROR_VALUE(LABEL_UNINITIALISED, 2, "Uninitialized usage")

enum Error
{
#define ERROR_VALUE(NAME, VALUE, TEXT) NAME = VALUE,
    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, VALUE, TEXT) case NAME: return os << "[" << errVal << "]" << #NAME << ", " << TEXT;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

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

Saídas:

Error: [0]NO_ERROR, OK
Error: [1]FILE_NOT_FOUND, Not found
Error: [2]LABEL_UNINITIALISED, Uninitialized usage
eungenue
fonte
0
#pragma once

#include <string>
#include <vector>
#include <sstream>
#include <algorithm>

namespace StringifyEnum
{
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { ++rit; }
    return std::string(it, rit.base());
}

static std::vector<std::string> SplitEnumArgs(const char* szArgs, int     nMax)
{
    std::vector<std::string> enums;
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        enums.push_back(StringifyEnum::TrimEnumString(strSub));
        ++nIdx;
    }
    return std::move(enums);
}    
}

#define DECLARE_ENUM_SEQ(ename, n, ...) \
    enum class ename { __VA_ARGS__ }; \
    const int MAX_NUMBER_OF_##ename(n); \
    static std::vector<std::string> ename##Strings = StringifyEnum::SplitEnumArgs(#__VA_ARGS__, MAX_NUMBER_OF_##ename); \
    inline static std::string ename##ToString(ename e) { \
        return ename##Strings.at((int)e); \
    } \
    inline static ename StringTo##ename(const std::string& en) { \
        const auto it = std::find(ename##Strings.begin(), ename##Strings.end(), en); \
        if (it != ename##Strings.end()) \
            return (ename) std::distance(ename##Strings.begin(), it); \
        throw std::runtime_error("Could not resolve string enum value");     \
    }

Esta é uma versão de enum estendida de classe elaborada ... não adiciona nenhum outro valor de enum além dos fornecidos.

Uso: DECLARE_ENUM_SEQ (CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)

Michal Turlik
fonte
0

Eu precisava que isso funcionasse nas duas direções E, com frequência, integro minhas enumerações em uma classe que contenha, e então comecei com a solução de James McNellis, bem no topo dessas respostas, mas eu fiz essa solução. Note também que eu prefiro a classe enum do que apenas enum, o que complica um pouco a resposta.

#define X_DEFINE_ENUMERATION(r, datatype, elem) case datatype::elem : return BOOST_PP_STRINGIZE(elem);

// The data portion of the FOR_EACH should be (variable type)(value)
#define X_DEFINE_ENUMERATION2(r, dataseq, elem) \
    if (BOOST_PP_SEQ_ELEM(1, dataseq) == BOOST_PP_STRINGIZE(elem) ) return BOOST_PP_SEQ_ELEM(0, dataseq)::elem;

#define DEFINE_ENUMERATION_MASTER(modifier, name, toFunctionName, enumerators)    \
    enum class name {                                                         \
        Undefined,                                                            \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    modifier const char* ToString(const name & v)                               \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUMERATION,                                         \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }                                                                         \
                                                                              \
    modifier const name toFunctionName(const std::string & value)               \
    {                                                                         \
        BOOST_PP_SEQ_FOR_EACH(                                                \
            X_DEFINE_ENUMERATION2,                                            \
            (name)(value),                                                    \
            enumerators                                                       \
        )                                                                     \
        return name::Undefined;                                               \
    }

#define DEFINE_ENUMERATION(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(inline, name, toFunctionName, enumerators)

#define DEFINE_ENUMERATION_INSIDE_CLASS(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(static, name, toFunctionName, enumerators)

Para usá-lo dentro de uma classe, você pode fazer algo assim:

class ComponentStatus {
public:
    /** This is a simple bad, iffy, and good status. See other places for greater details. */
    DEFINE_ENUMERATION_INSIDE_CLASS(Status, toStatus, (RED)(YELLOW)(GREEN)
}

E eu escrevi um teste CppUnit, que demonstra como usá-lo:

void
ComponentStatusTest::testSimple() {
    ComponentStatus::Status value = ComponentStatus::Status::RED;

    const char * valueStr = ComponentStatus::ToString(value);

    ComponentStatus::Status convertedValue = ComponentStatus::toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

DEFINE_ENUMERATION(Status, toStatus, (RED)(YELLOW)(GREEN))

void
ComponentStatusTest::testOutside() {
    Status value = Status::RED;

    const char * valueStr = ToString(value);

    Status convertedValue = toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

Você precisa escolher qual macro usar, DEFINE_ENUMERATION ou DEFINE_ENUMERATION_INSIDE_CLASS. Você verá que eu usei o último ao definir ComponentStatus :: Status, mas usei o último ao definir Status. A diferença é simples. Dentro de uma classe, prefixo os métodos de / para como "estático" e, se não estiver em uma classe, uso "inline". Diferenças triviais, mas necessárias.

Infelizmente, acho que não há uma maneira limpa de evitar isso:

const char * valueStr = ComponentStatus::ToString(value);

embora você possa criar manualmente um método embutido após sua definição de classe que simplesmente acorrente ao método de classe, algo como:

inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }
Joseph Larson
fonte
0

Minha própria resposta, não usando boost - usando minha própria abordagem sem definir magias pesadas, e esta solução tem uma limitação de não ser capaz de definir um valor específico de enum.

#pragma once
#include <string>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name> {                                         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };

/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

        printf( EnumToString(enumValue3).c_str() );

    From string to enumeration:

       enumName value;

       if( !StringToEnum("enumValue4", value) )
            printf("Conversion failed...");

    WARNING: At the moment assigning enum value to specific number is not supported.
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = (int)t;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (id == 0)
            return std::string(token, next);
        id--;
    } while (*next != 0);

    return std::string();
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = 0;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (strncmp(token, enumName, next - token) == 0)
        {
            t = (T)id;
            return true;
        }

        id++;
    } while (*next != 0);

    return false;
}

A versão mais recente pode ser encontrada no github aqui:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h

TarmoPikaro
fonte
0

Existem muitas outras respostas para isso, mas acho que uma maneira melhor é usar os recursos do C ++ 17 e usar o constexpr para que as traduções sejam feitas em tempo de compilação. Esse é o tipo seguro e não precisamos mexer com macros. Ver abaixo:

//enum.hpp
#include <array>
#include <string_view>

namespace Enum
{

template <class ENUM_TYPE, size_t SIZE>
constexpr ENUM_TYPE findKey(const char * value, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Value not in map":
        (std::string_view(map[index - 1].second) == value) ? map[index- 1].first:
        findKey(value, map, index - 1);
};

template <class ENUM_TYPE, size_t SIZE>
constexpr const char * findValue(ENUM_TYPE key, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Key not in map":
        (map[index - 1].first == key) ? map[index- 1].second:
        findValue(key, map, index - 1);
};

}

//test_enum.hpp
#include "enum.hpp"

namespace TestEnum
{
    enum class Fields
    {
        Test1,
        Test2,
        Test3,
        //This has to be at the end
        NUMBER_OF_FIELDS
    };

    constexpr std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> GetMap()
    {
        std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> map =
        {
            {
                    {Fields::Test1, "Test1"},
                    {Fields::Test2, "Test2"},
                    {Fields::Test3, "Test3"},
            }
        };
        return map;
    };

    constexpr Fields StringToEnum(const char * value)
    {
        return Enum::findKey(value, GetMap());
    }

    constexpr const char * EnumToString(Fields key)
    {
        return Enum::findValue(key, GetMap());
    }

}

Isso pode ser facilmente utilizado para que erros de chave de cadeia sejam detectados em tempo de compilação:

#include "test_enum.hpp"

int main()
{
    auto constexpr a = TestEnum::StringToEnum("Test2"); //a = TestEnum::Fields::Test2
    auto constexpr b = TestEnum::EnumToString(TestEnum::Fields::Test1); //b = "Test1"
    auto constexpr c = TestEnum::StringToEnum("AnyStringNotInTheMap"); //compile time failure
    return 0;
}

O código é mais detalhado do que algumas outras soluções, mas podemos facilmente fazer conversão Enum para String e conversão String para Enum em tempo de compilação e detectar erros de tipo. Com alguns dos futuros recursos do C ++ 20, isso provavelmente pode ser simplificado um pouco mais.

Marius
fonte