Maneira fácil de usar variáveis ​​de tipos enum como string em C?


87

Aqui está o que estou tentando fazer:

typedef enum { ONE, TWO, THREE } Numbers;

Estou tentando escrever uma função que faria um caso de switch semelhante ao seguinte:

char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, num); //some way to get the symbolic constant name in here?
    } break;
    default:
      return 0; //no match
  return 1;
}

Em vez de definir em cada caso, há uma maneira de defini-lo usando a variável enum como estou tentando fazer acima?

Respostas:


14

Não há solução embutida. A maneira mais fácil é com uma variedade dechar* onde o valor int do enum é indexado a uma string contendo o nome descritivo desse enum. Se você tiver um esparso enum(um que não começa em 0 ou tem lacunas na numeração) onde alguns dos intmapeamentos são altos o suficiente para tornar um mapeamento baseado em array impraticável, você pode usar uma tabela de hash.


Expandindo isso, se realmente for uma lista de incremento linear, você pode apenas usar a ferramenta de macro do seu editor para gravar e resolver cada um dos nomes em uma string. Um pouco de digitação extra é necessário e você dispensa a necessidade de definições em primeiro lugar. Eu clico em gravar na última das macros copiadas, adiciono uma citação depois e prossigo para o mesmo lugar na próxima linha. Eu empurro para parar. Eu pressiono executar X vezes e faço quantas forem (ou apenas uma única etapa). Em seguida, posso envolvê-lo em uma matriz de string.
user2262111

70

A técnica de tornar algo tanto um identificador C quanto uma string? pode ser usado aqui.

Como de costume com essas coisas de pré-processador, escrever e entender a parte do pré-processador pode ser difícil, e inclui passar macros para outras macros e envolve o uso de operadores # e ##, mas usá-lo é muito fácil. Acho este estilo muito útil para enums longos, onde manter a mesma lista duas vezes pode ser realmente problemático.

Código de fábrica - digitado apenas uma vez, geralmente escondido no cabeçalho:

enumFactory.h:

// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,

// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;

// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;

/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
  enum EnumType { \
    ENUM_DEF(ENUM_VALUE) \
  }; \
  const char *GetString(EnumType dummy); \
  EnumType Get##EnumType##Value(const char *string); \

/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
  const char *GetString(EnumType value) \
  { \
    switch(value) \
    { \
      ENUM_DEF(ENUM_CASE) \
      default: return ""; /* handle input error */ \
    } \
  } \
  EnumType Get##EnumType##Value(const char *str) \
  { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; /* handle input error */ \
  } \

Fabrica Usada

someEnum.h:

#include "enumFactory.h"
#define SOME_ENUM(XX) \
    XX(FirstValue,) \
    XX(SecondValue,) \
    XX(SomeOtherValue,=50) \
    XX(OneMoreValue,=100) \

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp:

#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)

A técnica pode ser facilmente estendida para que as macros XX aceitem mais argumentos, e você também pode ter preparado mais macros para substituir XX para necessidades diferentes, semelhantes às três que forneci neste exemplo.

Comparação com X-Macros usando #include / #define / #undef

Embora seja semelhante ao X-Macros que outros mencionaram, acho que esta solução é mais elegante porque não requer #undefing nada, o que permite ocultar mais das coisas complicadas está na fábrica o arquivo de cabeçalho - o arquivo de cabeçalho é algo que você não toca em nada quando precisa definir um novo enum; portanto, a nova definição de enum é muito mais curta e mais clara.


2
Não sei como você pode dizer que isso é melhor / pior do que x-macros - isso é x-macros. O SOME_ENUM(XX)é exatamente um X-macro (para ser mais preciso, o "formulário do usuário" que passa a XXfunção em vez de usar #def #undef) e, por sua vez, todo o X-MACRO é então passado para DEFINE_ENUM que o usa. Para não tirar nada da solução - funciona bem. Só para esclarecer que é um uso de macros X.
BeeOnRope de

1
@BeeOnRope A diferença observada é significativa e distingue esta solução de macros X idiomáticas (como os exemplos da Wikipedia ). A vantagem de passar XXpor cima de reing #defineé que o primeiro padrão pode ser usado em expansões macro. Observe que as únicas outras soluções tão concisas como esta requerem a criação e inclusão múltipla de um arquivo separado para definir um novo enum.
pmttavara

1
Outro truque é usar o nome enum como o nome da macro. Você pode simplesmente escrever #define DEFINE_ENUM(EnumType) ..., substituir ENUM_DEF(...)por EnumType(...)e fazer com que o usuário diga #define SomeEnum(XX) .... O pré-processador C se expandirá contextualmente SomeEnumna invocação da macro quando seguido por parênteses e em um token regular caso contrário. (Claro, isso causa problemas se o usuário gosta de usar SomeEnum(2)para lançar para o tipo enum em vez de (SomeEnum)2ou static_cast<SomeEnum>(2).)
pmttavara

1
@pmttavara - claro, se uma pesquisa rápida for qualquer indicação, o uso mais comum de macros x usa um nome de macro interno fixo junto com #definee #undef. No entanto, você discorda de que o "formulário do usuário" (sugerido, por exemplo, no final deste artigo ) seja um tipo de x-macro? Eu certamente sempre o chamei de x-macro também e nas bases de código C em que estive recentemente é a forma mais comum (essa é obviamente uma observação tendenciosa). Eu posso ter analisado o OP de forma errada.
BeeOnRope

2
@BeeOnRope O texto atual é resultado da edição, como você me convenceu naquela época, isso é x-macro, mesmo que talvez fosse uma forma menos usada (ou pelo menos uma menção a menos em artigos) naquela época.
Suma

62
// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
    ENUM(ONE),
    ENUM(TWO),
    ENUM(FOUR)
ENUM_END( Numbers )

// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };

// Now in one and only one .c file, redefine the ENUM macros and reinclude
//  the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED   // whatever you need to do to enable reinclusion
#include "numbers.h"

// Now you can do exactly what you want to do, with no retyping, and for any
//  number of enumerated types defined with the ENUM macro family
//  Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
    } break;
    default:
      return 0; //no match
  return 1;
}

// Sweet no ? After being frustrated by this for years, I finally came up
//  with this solution for my most recent project and plan to reuse the idea
//  forever

3
Este é o tipo de coisa para a qual o cpp foi feito. +1.
Derrick Turk

5
Esta é uma boa resposta, parece ser o melhor que se pode fazer sem usar ferramentas especiais, e eu já fiz esse tipo de coisa antes; mas ainda nunca parece realmente 'certo' e eu nunca gosto de fazer isso ...
Michael Burr

Pequena mudança: #define ENUM_END(typ) }; extern const char * typ ## _name_table[];no defs.harquivo - isso irá declarar sua tabela de nomes nos arquivos que você usa. (Não consigo descobrir uma boa maneira de declarar o tamanho da tabela, no entanto.) Além disso, pessoalmente, eu deixaria de fora o ponto-e-vírgula final, mas os méritos são discutíveis de qualquer maneira.
Chris Lutz

1
@Bill, por que se preocupar com typa linha #define ENUM_END(typ) };?
Pacerier de

Isso não funciona onde eu quero que minha macro seja definida como "ONE = 5"
UKMonkey

13

Definitivamente, há uma maneira de fazer isso - use macros X () . Essas macros usam o pré-processador C para construir enums, arrays e blocos de código a partir de uma lista de dados de origem. Você só precisa adicionar novos itens ao #define que contém a macro X (). A instrução switch se expandiria automaticamente.

Seu exemplo pode ser escrito da seguinte maneira:

 // Source data -- Enum, String
 #define X_NUMBERS \
    X(ONE,   "one") \
    X(TWO,   "two") \
    X(THREE, "three")

 ...

 // Use preprocessor to create the Enum
 typedef enum {
  #define X(Enum, String)       Enum,
   X_NUMBERS
  #undef X
 } Numbers;

 ...

 // Use Preprocessor to expand data into switch statement cases
 switch(num)
 {
 #define X(Enum, String) \
     case Enum:  strcpy(num_str, String); break;
 X_NUMBERS
 #undef X

     default: return 0; break;
 }
 return 1;

Existem maneiras mais eficientes (ou seja, usando Macros X para criar uma matriz de string e um índice de enum), mas esta é a demonstração mais simples.


8

Eu sei que você tem algumas respostas boas e sólidas, mas você sabe sobre o operador # no pré-processador C?

Ele permite que você faça isso:

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}

char const *kConstStr[]
Anne van Rossum

6

C ou C ++ não fornecem essa funcionalidade, embora eu tenha precisado dela com frequência.

O código a seguir funciona, embora seja mais adequado para enums não esparsos.

typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);

Por não esparso, não quero dizer da forma

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

uma vez que tem grandes lacunas nele.

A vantagem desse método é que ele coloca as definições de enums e strings próximas umas das outras; ter uma instrução switch em uma função os lança. Isso significa que é menos provável que você mude um sem o outro.


6

BEIJO. Você fará todos os tipos de outras coisas de switch / case com seus enums, então por que a impressão deveria ser diferente? Esquecer um caso em sua rotina de impressão não é um grande negócio quando você considera que existem cerca de 100 outros lugares onde pode esquecer um caso. Basta compilar -Wall, que avisará sobre correspondências não exaustivas de maiúsculas e minúsculas. Não use "default" porque isso tornará a troca exaustiva e você não receberá avisos. Em vez disso, deixe o switch sair e lidar com o caso padrão assim ...

const char *myenum_str(myenum e)
{
    switch(e) {
    case ONE: return "one";
    case TWO: return "two";
    }
    return "invalid";
}


4

O uso de boost :: preprocessor torna possível uma solução elegante como a seguinte:

Etapa 1: incluir o arquivo de cabeçalho:

#include "EnumUtilities.h"

Etapa 2: declare o objeto de enumeração com a seguinte sintaxe:

MakeEnum( TestData,
         (x)
         (y)
         (z)
         );

Etapa 3: use seus dados:

Obtendo o número de elementos:

td::cout << "Number of Elements: " << TestDataCount << std::endl;

Obtendo a string associada:

std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl;
std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl;
std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl;

Obtendo o valor enum da string associada:

std::cout << "Value of x is " << TestData2Enum("x") << std::endl;
std::cout << "Value of y is " << TestData2Enum("y") << std::endl;
std::cout << "Value of z is " << TestData2Enum("z") << std::endl;

Parece limpo e compacto, sem arquivos extras para incluir. O código que escrevi em EnumUtilities.h é o seguinte:

#include <boost/preprocessor/seq/for_each.hpp>
#include <string>

#define REALLY_MAKE_STRING(x) #x
#define MAKE_STRING(x) REALLY_MAKE_STRING(x)
#define MACRO1(r, data, elem) elem,
#define MACRO1_STRING(r, data, elem)    case elem: return REALLY_MAKE_STRING(elem);
#define MACRO1_ENUM(r, data, elem)      if (REALLY_MAKE_STRING(elem) == eStrEl) return elem;


#define MakeEnum(eName, SEQ) \
    enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \
    last_##eName##_enum}; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    }; \
    static enum eName eName##2Enum(const std::string eStrEl) \
    { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \
        return (enum eName)0; \
    };

Existem algumas limitações, ou seja, as do boost :: preprocessor. Nesse caso, a lista de constantes não pode ser maior que 64 elementos.

Seguindo a mesma lógica, você também pode pensar em criar um enum esparso:

#define EnumName(Tuple)                 BOOST_PP_TUPLE_ELEM(2, 0, Tuple)
#define EnumValue(Tuple)                BOOST_PP_TUPLE_ELEM(2, 1, Tuple)
#define MACRO2(r, data, elem)           EnumName(elem) EnumValue(elem),
#define MACRO2_STRING(r, data, elem)    case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem));

#define MakeEnumEx(eName, SEQ) \
    enum eName { \
    BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \
    last_##eName##_enum }; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    };  

Nesse caso, a sintaxe é:

MakeEnumEx(TestEnum,
           ((x,))
           ((y,=1000))
           ((z,))
           );

O uso é semelhante ao anterior (sem a função eName ## 2Enum, que você pode tentar extrapolar da sintaxe anterior).

Eu testei em mac e linux, mas esteja ciente de que boost :: preprocessor pode não ser totalmente portátil.


3

Ao mesclar algumas das técnicas aqui, eu vim com a forma mais simples:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}

2

Se você estiver usando o gcc, é possível usar:

const char * enum_to_string_map[]={ [enum1]='string1', [enum2]='string2'};

Então é só ligar, por exemplo

enum_to_string_map[enum1]

1

Confira as ideias em Mu Dynamics Research Labs - Arquivo de blogs . Eu encontrei isso no início deste ano - esqueci o contexto exato em que me deparei com isso - e o adaptei a este código. Podemos debater os méritos de adicionar um E na frente; é aplicável ao problema específico abordado, mas não faz parte de uma solução geral. Guardei isso na minha pasta 'vinhetas' - onde guardo fragmentos de código interessantes para o caso de querer depois. Tenho vergonha de dizer que não anotei de onde veio essa ideia na época.

Cabeçalho: paste1.h

/*
@(#)File:           $RCSfile: paste1.h,v $
@(#)Version:        $Revision: 1.1 $
@(#)Last changed:   $Date: 2008/05/17 21:38:05 $
@(#)Purpose:        Automated Token Pasting
*/

#ifndef JLSS_ID_PASTE_H
#define JLSS_ID_PASTE_H

/*
 * Common case when someone just includes this file.  In this case,
 * they just get the various E* tokens as good old enums.
 */
#if !defined(ETYPE)
#define ETYPE(val, desc) E##val,
#define ETYPE_ENUM
enum {
#endif /* ETYPE */

   ETYPE(PERM,  "Operation not permitted")
   ETYPE(NOENT, "No such file or directory")
   ETYPE(SRCH,  "No such process")
   ETYPE(INTR,  "Interrupted system call")
   ETYPE(IO,    "I/O error")
   ETYPE(NXIO,  "No such device or address")
   ETYPE(2BIG,  "Arg list too long")

/*
 * Close up the enum block in the common case of someone including
 * this file.
 */
#if defined(ETYPE_ENUM)
#undef ETYPE_ENUM
#undef ETYPE
ETYPE_MAX
};
#endif /* ETYPE_ENUM */

#endif /* JLSS_ID_PASTE_H */

Fonte de exemplo:

/*
@(#)File:           $RCSfile: paste1.c,v $
@(#)Version:        $Revision: 1.2 $
@(#)Last changed:   $Date: 2008/06/24 01:03:38 $
@(#)Purpose:        Automated Token Pasting
*/

#include "paste1.h"

static const char *sys_errlist_internal[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) desc,
#include "paste1.h"
    0
#undef ETYPE
};

static const char *xerror(int err)
{
    if (err >= ETYPE_MAX || err <= 0)
        return "Unknown error";
    return sys_errlist_internal[err];
}

static const char*errlist_mnemonics[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) [E ## val] = "E" #val,
#include "paste1.h"
#undef ETYPE
};

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < ETYPE_MAX; i++)
    {
        printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
    }
    return(0);
}

Não necessariamente o uso mais limpo do mundo do pré-processador C - mas ele evita a gravação do material várias vezes.



0

Se o índice enum for baseado em 0, você pode colocar os nomes em uma matriz de char * e indexá-los com o valor enum.



0

Eu criei uma classe simples templated streamable_enumque usos transmitir operadores <<e >>e baseia-se na std::map<Enum, std::string>:

#ifndef STREAMABLE_ENUM_HPP
#define STREAMABLE_ENUM_HPP

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

template <typename E>
class streamable_enum
{
public:
    typedef typename std::map<E, std::string> tostr_map_t;
    typedef typename std::map<std::string, E> fromstr_map_t;

    streamable_enum()
    {}

    streamable_enum(E val) :
        Val_(val)
    {}

    operator E() {
        return Val_;
    }

    bool operator==(const streamable_enum<E>& e) {
        return this->Val_ == e.Val_;
    }

    bool operator==(const E& e) {
        return this->Val_ == e;
    }

    static const tostr_map_t& to_string_map() {
        static tostr_map_t to_str_(get_enum_strings<E>());
        return to_str_;
    }

    static const fromstr_map_t& from_string_map() {
        static fromstr_map_t from_str_(reverse_map(to_string_map()));
        return from_str_;
    }
private:
    E Val_;

    static fromstr_map_t reverse_map(const tostr_map_t& eToS) {
        fromstr_map_t sToE;
        for (auto pr : eToS) {
            sToE.emplace(pr.second, pr.first);
        }
        return sToE;
    }
};

template <typename E>
streamable_enum<E> stream_enum(E e) {
    return streamable_enum<E>(e);
}

template <typename E>
typename streamable_enum<E>::tostr_map_t get_enum_strings() {
    // \todo throw an appropriate exception or display compile error/warning
    return {};
}

template <typename E>
std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) {
    auto& mp = streamable_enum<E>::to_string_map();
    auto res = mp.find(e);
    if (res != mp.end()) {
        os << res->second;
    } else {
        os.setstate(std::ios_base::failbit);
    }
    return os;
}

template <typename E>
std::istream& operator>>(std::istream& is, streamable_enum<E>& e) {
    std::string str;
    is >> str;
    if (str.empty()) {
        is.setstate(std::ios_base::failbit);
    }
    auto& mp = streamable_enum<E>::from_string_map();
    auto res = mp.find(str);
    if (res != mp.end()) {
        e = res->second;
    } else {
        is.setstate(std::ios_base::failbit);
    }
    return is;
}

#endif

Uso:

#include "streamable_enum.hpp"

using std::cout;
using std::cin;
using std::endl;

enum Animal {
    CAT,
    DOG,
    TIGER,
    RABBIT
};

template <>
streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() {
    return {
        { CAT, "Cat"},
        { DOG, "Dog" },
        { TIGER, "Tiger" },
        { RABBIT, "Rabbit" }
    };
}

int main(int argc, char* argv []) {
    cout << "What animal do you want to buy? Our offering:" << endl;
    for (auto pr : streamable_enum<Animal>::to_string_map()) {          // Use from_string_map() and pr.first instead
        cout << " " << pr.second << endl;                               // to have them sorted in alphabetical order
    }
    streamable_enum<Animal> anim;
    cin >> anim;
    if (!cin) {
        cout << "We don't have such animal here." << endl;
    } else if (anim == Animal::TIGER) {
        cout << stream_enum(Animal::TIGER) << " was a joke..." << endl;
    } else {
        cout << "Here you are!" << endl;
    }

    return 0;
}

0

Aqui está uma solução usando macros com os seguintes recursos:

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

  2. não guarde os valores enum em um arquivo separado que será # incluído posteriormente, para que eu possa escrevê-lo onde quiser

  3. não substitua o enum em si, ainda quero ter o tipo de enum definido, mas, além disso, quero poder mapear cada nome de enum para a string correspondente (para não afetar o código legado)

  4. a pesquisa deve ser rápida, de preferência sem caixa de troca, para aqueles enormes enums

https://stackoverflow.com/a/20134475/1812866


0

Achei que uma solução como Boost.Fusion one para adaptar structs e classes seria bom, eles até tinham em algum ponto, usar enums como uma sequência de fusão.

Então, fiz apenas algumas pequenas macros para gerar o código para imprimir os enums. Isso não é perfeito e não tem nada a ver com o código clichê gerado pelo Boost.Fusion, mas pode ser usado como as macros do Boost Fusion. Quero realmente gerar os tipos necessários ao Boost.Fusion para integrar nessa infraestrutura que permite imprimir nomes de membros de estrutura, mas isso vai acontecer mais tarde, por enquanto são apenas macros:

#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP

#include <swissarmyknife/detail/config.hpp>

#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>


#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
    R, unused, ENUMERATION_ENTRY)                                               \
    case ENUMERATION_ENTRY:                                                     \
      return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
    break;                                                                      

/**
 * \brief Adapts ENUM to reflectable types.
 *
 * \param ENUM_TYPE To be adapted
 * \param ENUMERATION_SEQ Sequence of enum states
 */
#define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ)                   \
    inline std::string to_string(const ENUM_TYPE& enum_value) {                 \
      switch (enum_value) {                                                     \
      BOOST_PP_SEQ_FOR_EACH(                                                    \
          SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C,                   \
          unused, ENUMERATION_SEQ)                                              \
        default:                                                                \
          return BOOST_PP_STRINGIZE(ENUM_TYPE);                                 \
      }                                                                         \
    }                                                                           \
                                                                                \
    inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
      os << to_string(value);                                                   \
      return os;                                                                \
    }

#endif

A resposta antiga abaixo é muito ruim, por favor, não use isso. :)

Resposta antiga:

Tenho procurado uma maneira que resolva esse problema sem alterar muito a sintaxe da declaração de enums. Cheguei a uma solução que usa o pré-processador para recuperar uma string de uma declaração de enum stringificada.

Sou capaz de definir enums não esparsos como este:

SMART_ENUM(State, 
    enum State {
        RUNNING,
        SLEEPING, 
        FAULT, 
        UNKNOWN
    })

E posso interagir com eles de diferentes maneiras:

// With a stringstream
std::stringstream ss;
ss << State::FAULT;
std::string myEnumStr = ss.str();

//Directly to stdout
std::cout << State::FAULT << std::endl;

//to a string
std::string myStr = State::to_string(State::FAULT);

//from a string
State::State myEnumVal = State::from_string(State::FAULT);

Com base nas seguintes definições:

#define SMART_ENUM(enumTypeArg, ...)                                                     \
namespace enumTypeArg {                                                                  \
    __VA_ARGS__;                                                                         \
    std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) {                 \
            os << swissarmyknife::enums::to_string(#__VA_ARGS__, val);                   \
            return os;                                                                   \
    }                                                                                    \
                                                                                     \
    std::string to_string(const enumTypeArg& val) {                                      \
            return swissarmyknife::enums::to_string(#__VA_ARGS__, val);                  \
    }                                                                                    \
                                                                                     \
    enumTypeArg from_string(const std::string &str) {                                    \
            return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str);   \
    }                                                                                    \
}                                                                                        \


namespace swissarmyknife { namespace enums {

    static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            if (enumVal == count) {
                std::string identifiersSubset = identifiers.substr(0, found);
                size_t beginId = identifiersSubset.find_last_of("{,");
                identifiersSubset = identifiersSubset.substr(beginId+1);
                boost::algorithm::trim(identifiersSubset);
                return identifiersSubset;
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("The enum declaration provided doesn't contains this state.");
    }                                                  

    template <typename EnumType>
    static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            std::string identifiersSubset = identifiers.substr(0, found);
            size_t beginId = identifiersSubset.find_last_of("{,");
            identifiersSubset = identifiersSubset.substr(beginId+1);
            boost::algorithm::trim(identifiersSubset);

            if (identifiersSubset == enumStr) {
                return static_cast<EnumType>(count);
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("No valid enum value for the provided string");
    }                      

}}

Quando eu vou precisar de suporte para esparsa enum e quando eu vou ter mais tempo eu vou melhorar as to_string e from_string implementações com boost :: xpressive, mas esta custos, com o tempo de compilação por causa da modelagem importante executada e os é gerado executáveis provavelmente será muito maior. Mas isso tem a vantagem de ser mais legível e fácil de manter do que esse código de manipulação manual de string feio. : D

Caso contrário, sempre usei boost :: bimap para realizar esses mapeamentos entre o valor enums e a string, mas ele deve ser mantido manualmente.


0

Como prefiro não usar macros por todos os motivos usuais, usei uma solução de macro mais limitada que tem a vantagem de manter a macro de declaração de enum livre. As desvantagens incluem ter que copiar e colar a definição de macro para cada enum e ter que adicionar explicitamente uma invocação de macro ao adicionar valores ao enum.

std::ostream& operator<<(std::ostream& os, provenance_wrapper::CaptureState cs)
{
#define HANDLE(x) case x: os << #x; break;
    switch (cs) {
    HANDLE(CaptureState::UNUSED)
    HANDLE(CaptureState::ACTIVE)
    HANDLE(CaptureState::CLOSED)
    }
    return os;
#undef HANDLE
}
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.