Fácil maneira de usar variáveis ??de tipos enum como seqüência de caracteres em C?

StackOverflow https://stackoverflow.com/questions/147267

  •  02-07-2019
  •  | 
  •  

Pergunta

Aqui está o que estou tentando fazer:

typedef enum { ONE, TWO, THREE } Numbers;

Eu estou tentando escrever uma função que faria um caso interruptor 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 eu estou tentando fazer acima?

Foi útil?

Solução

Não há nenhuma built-in solução. A maneira mais fácil é com uma matriz de char* onde os índices de valor int do enum para uma cadeia contendo o nome descritivo que enum. Se você tem um enum escassa (um que não começam em 0 ou tem lacunas na numeração), onde alguns dos mapeamentos int são altos o suficiente para fazer um impraticável mapeamento baseada em array, em seguida, você poderia usar uma tabela hash em vez.

Outras dicas

A técnica de Fazer algo ao mesmo tempo e uma corda? podem ser usados ??aqui.

Como de costume com tais coisas pré-processador, escrita e compreensão da parte pré-processador pode ser difícil, e inclui a passagem macros para outras macros e envolve o uso de # e operadores ##, mas usá-lo é muito fácil. I encontrar este estilo muito útil para longas enums, onde a manutenção da mesma lista duas vezes pode ser muito problemático.

código Factory - digitadas apenas uma vez, normalmente escondidos 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 */ \
  } \

Fábrica usado

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 estendido para que macros XX aceita mais argumentos, e você também pode ter preparado mais macros para substituir XX para diferentes necessidades, semelhante aos três que eu forneci neste exemplo.

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

Embora este seja semelhante ao X-Macros outros já mencionados, penso que esta solução é mais elegante na medida em que não exige #undefing nada, o que lhe permite esconder mais do material complicado é na fábrica cabeçalho do arquivo - o arquivo de cabeçalho é algo que você não está tocando em tudo quando você precisa definir uma nova enumeração, por isso nova definição enum é muito mais curto e mais limpo.

// 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

Há definitivamente uma maneira de fazer isso - uso X () macros . Estas macros utilizar o pré-processador C para construir enumerações, matrizes e os blocos de código a partir de uma lista de dados de origem. Você só precisa adicionar novos itens à #define que contém a macro X (). A instrução switch iria expandir automaticamente.

O seu exemplo pode ser escrito da seguinte forma:

 // 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 X Macros para criar uma matriz de cadeia e índice de enum), mas esta é a demonstração mais simples.

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

Ele permite que você faça o seguinte:

#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]);
}

KISS. Você estará fazendo todos os tipos de outros parâmetro / caso as coisas com os seus enums então por que a impressão seria diferente? Esquecendo-se de um caso em sua rotina de impressão não é um grande negócio quando você considerar há cerca de 100 outros lugares que você pode esquecer um caso. Apenas compilar -Wall, que irá avisá de fósforos caso não exaustivas. Não use "default", porque isso vai fazer a exaustiva interruptor e você não vai conseguir avisos. Em vez disso, deixe a saída interruptor 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";
}

C ou C ++ não fornece essa funcionalidade, embora eu precisava lo muitas vezes.

As seguintes obras de código, 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-esparsos, quero dizer não de forma

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

desde que tem enormes lacunas.

A vantagem deste método é que ele colocou as definições dos enums e cordas perto um do outro; ter uma instrução switch em uma função spearates eles. Isto significa que você é menos provável que alterar um sem o outro.

Tente Convertendo enums C ++ para cordas . Os comentários tem melhorias que resolvem o problema quando enum itens têm valores arbitrários.

O uso de boost :: pré-processador torna possível uma solução elegante como o seguinte:

Passo 1: incluir o arquivo de cabeçalho:

#include "EnumUtilities.h"

Passo 2: declarar o objecto enumeração com a seguinte sintaxe:

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

Passo 3: utilizar os seus dados:

Obtendo o número de elementos:

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

Começar a seqüência 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 cadeia 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;

Este parece limpo e compacto, sem arquivos extras para incluir. O código que eu escrevi dentro 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; \
    };

Há alguma limitação, ou seja, os de boost :: pré-processador. Neste caso, a lista de constantes não pode ser maior do que 64 elementos.

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

#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."; \
        }; \
    };  

Neste caso, a sintaxe é:

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

O uso é semelhante ao citado acima (menos a função ENAME ## 2Enum, que você poderia tentar extrapolar a sintaxe anterior).

Eu testei no Mac e Linux, mas esteja ciente de que boost :: pré-processador pode não ser totalmente portátil.

Ao fundir algumas das técnicas para cá 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;
}

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

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

Em seguida, basta chamar, por exemplo,

enum_to_string_map[enum1]

Confira as ideias em Mu Dynamics Research Labs - Blog Archive . Achei isso no início deste ano - I esquecer o contexto exato onde me deparei com isso - e se adaptaram-lo em este código. Podemos debater os méritos de adicionar um E na frente; é aplicável ao problema específico abordada, mas não faz parte de uma solução geral. Escondi este afastado em pasta meu 'vinhetas' - onde guardo restos interessantes do código no caso eu quero que eles mais tarde. Tenho vergonha de dizer que eu não manter uma nota de onde veio essa idéia no momento.

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 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 C-processador pré -. Mas impede de escrever o material para fora várias vezes

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

#define stringify( name ) # name

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

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

Uma discussão mais aprofundada sobre este método

Preprocessor truques directiva para os recém-chegados

Eu criei um simples templated streamable_enum classe que usa transmitir operadores << e >> e é baseado no 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;
}

Aqui está uma solução usando macros com as seguintes características:

  1. só escrevo cada valor da enumeração uma vez, para que não haja listas duplas para manter

  2. não manter os valores enum em um arquivo separado que é depois #included, para que eu possa escrevê-lo onde quer que eu quero

  3. não substituem o próprio enum, eu ainda quero ter o tipo enum definido, mas, além de que eu quero ser capaz de mapear todos os nomes enum para a cadeia correspondente (para não afetar o código legado)

  4. a busca deve ser rápido, por isso, de preferência não-caso switch, para aquelas enormes enums

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

Eu pensei que uma solução como Boost.Fusion um para adaptar estruturas e classes seria bom, eles ainda tinham isso em algum momento, para usar enums como uma sequência de fusão.

Então eu fiz apenas algumas pequenas macros para gerar o código para imprimir os enums. Esta não é perfeito e não tem nada a ver com Boost.Fusion código clichê gerado, mas pode ser usado como o impulso macros de fusão. Eu quero realmente gerar os tipos necessários para Boost.Fusion para integrar nesta infra-estrutura que permite imprimir nomes dos membros da estrutura, mas isso vai acontecer mais tarde, por agora esta é 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 de idade abaixo é muito ruim, por favor, não use isso. :)

resposta antiga:

Eu tenho procurado uma maneira que resolve este problema sem alterar muito a sintaxe declaração enums. Eu vim para uma solução que utiliza o pré-processador para recuperar uma seqüência de caracteres de uma declaração enum stringified.

Eu sou capaz de definir enums não-esparsos como esta:

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

E eu 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 a to_string e from_string implementações com boost :: xpressive, mas esta custos, com o tempo de compilação por causa da modelagem importante desempenhada eo executável gerado é provável que seja muito maior. Mas isto tem a vantagem de que será mais legível e maintanable que este código manipulação de strings manual do feio. : D

Caso contrário, eu sempre usei boost :: bitmap para executar tais mapeamentos entre valor enums e corda, mas tem que ser mantida manualmente.

Porque eu prefiro não usar macros para todas as razões habituais, eu usei uma solução macro mais limitado que tem a vantagem de manter a declaração enum macro livre. As desvantagens incluem ter que copiar colar o defintion macro para cada enum, e ter que adicionar explicitamente uma invocação de macro quando acrescentando valores a 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
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top