enum para string no C ++ 11 / C ++ 14 / C ++ 17 moderno e no C ++ 20 futuro


354

Ao contrário de todas as outras perguntas semelhantes, esta pergunta é sobre o uso dos novos recursos do C ++.

Depois de ler muitas respostas, ainda não encontrei nenhuma:

Exemplo

Um exemplo geralmente é melhor que uma longa explicação.
Você pode compilar e executar esse trecho no Coliru .
( Outro exemplo anterior também está disponível)

#include <map>
#include <iostream>

struct MyClass
{
    enum class MyEnum : char {
        AAA = -8,
        BBB = '8',
        CCC = AAA + BBB
    };
};

// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
    const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
        { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
        { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
        { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
    };
    auto   it  = MyEnumStrings.find(e);
    return it == MyEnumStrings.end() ? "Out of range" : it->second;
}

int main()
{
   std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
   std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
   std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}

Restrições

  • Por favor, sem duplicação sem valor de outras respostas ou link básico .
  • Evite inchar a resposta baseada em macro ou tente reduzir a #definesobrecarga o mínimo possível.
  • Por favor, não manual enum-> stringmapeamento.

Bom ter

  • enumValores de suporte a partir de um número diferente de zero
  • Suporte enumvalores negativos
  • Suporte a enumvalores fragmentados
  • Suporte class enum(C ++ 11)
  • Suporte class enum : <type>com qualquer permissão <type>(C ++ 11)
  • Conversões em tempo de compilação (não em tempo de execução) em uma string
    ou , pelo menos, execução rápida em tempo de execução (por exemplo, std::mapnão é uma boa idéia ...)
  • constexpr (C ++ 11, depois relaxado em C ++ 14/17/20)
  • noexcept (C ++ 11)
  • Snippet amigável em C ++ 17 / C ++ 20

Uma idéia possível seria utilizar os recursos de C ++ compilador para gerar código C ++ a compilação em tempo usando truques meta-programação com base em variadic template classe constexprfunções ...


4
(talvez of-topic) veja este blog relacionado ao Qt. woboq.com/blog/reflection-in-cpp-and-qt-moc.html . Descreve a possibilidade de substituir o moc de Qt (meta-objeto-compilador) usando a reflexão C ++ (padrão proposto).
Ola5041 03/03

10
N4113 :std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
ecatmur

11
Pessoalmente, resolvi esse problema implementando uma pequena biblioteca de utilitários de pré-processador que me permite fazer um loop sobre argumentos de macro variados e executar uma função em todos eles. Eu passo os valores de enumeração como argumentos de macro e gero automaticamente a enumeração e a matriz de strings através do pré-processador. Provavelmente você também pode fazer isso usando o Boost Preprocessor.
Vittorio Romeo

2
tudo tem que ser resolvido com C ++? É tão fácil gerar código automaticamente para a representação de strings, apenas algumas linhas de código.
309 Karoly Horvath #

2
"Por favor, não fornecem C respostas baseadas em macro se possível" bem, a menos que você está disposto a esperar para C ++ 17 não há quase nada útil, e não é que ruim para declarar suas enums como DEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))a menos que você tem que maintaint o gerar macro ... e colocar esses casos na linguagem é apenas outro tipo de invasão, em vez de um modelo / macro híbrido mais poderoso. Não devemos adicionar todos os casos úteis de macros ao idioma apenas para poder dizer que as macros não têm mais uso.
PlasmaHH

Respostas:


43

A biblioteca somente de cabeçalho do Magic Enum fornece reflexão estática para enumerações (para string, de string, iteração) para C ++ 17.

#include <magic_enum.hpp>

enum Color { RED = 2, BLUE = 4, GREEN = 8 };

Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"

std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name)
if (color.has_value()) {
  // color.value() -> Color::GREEN
};

Para mais exemplos, consulte o repositório inicial https://github.com/Neargye/magic_enum .

Onde está a desvantagem?

Essa biblioteca usa um hack específico do compilador (baseado em __PRETTY_FUNCTION__/ __FUNCSIG__), que funciona em Clang> = 5, MSVC> = 15.3 e GCC> = 9.

O valor de enum deve estar no intervalo [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • Por padrão MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.

  • Se precisar de outro intervalo para todos os tipos de enumeração por padrão, redefina a macro MAGIC_ENUM_RANGE_MINe MAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MINdeve ser menor ou igual a 0e deve ser maior que INT16_MIN.

  • MAGIC_ENUM_RANGE_MAXdeve ser maior que 0e deve ser menor que INT16_MAX.

  • Se precisar de outro intervalo para um tipo de enum específico, adicione especialização enum_range para o tipo de enum necessário.

    #include <magic_enum.hpp>
    
    enum number { one = 100, two = 200, three = 300 };
    
    namespace magic_enum {
    template <>
      struct enum_range<number> {
        static constexpr int min = 100;
        static constexpr int max = 300;
    };
    }

Por que o alcance limita? É para limitar algum tipo de profundidade de recursão ou devido a algum tipo de pesquisa linear em tempo de compilação?
Emile Cormier

Isso é incrível. Obrigado! Provavelmente é até eficiente se o compilador for inteligente o suficiente para avaliar o constexpr std :: array apenas uma vez. Muito muito bom.
iestyn

87

(A abordagem da biblioteca better_enums )

Existe uma maneira de fazer enum para string no C ++ atual que se parece com isso:

ENUM(Channel, char, Red = 1, Green, Blue)

// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };

Uso:

Channel     c = Channel::_from_string("Green");  // Channel::Green (2)
c._to_string();                                  // string "Green"

for (Channel c : Channel::_values())
    std::cout << c << std::endl;

// And so on...

Todas as operações podem ser feitas constexpr. Você também pode implementar a proposta de reflexão do C ++ 17 mencionada na resposta por @ecatmur.

  • Existe apenas uma macro. Acredito que isso seja o mínimo possível, porque a stringização do pré-processador ( #) é a única maneira de converter um token em uma string no C ++ atual.
  • A macro é bastante discreta - as declarações constantes, incluindo inicializadores, são coladas em uma declaração de enum interna. Isso significa que eles têm a mesma sintaxe e significado que em uma enum interna.
  • A repetição é eliminada.
  • A implementação é mais natural e útil em pelo menos C ++ 11, devido a constexpr. Também pode ser feito para trabalhar com C ++ 98 + __VA_ARGS__. É definitivamente C ++ moderno.

A definição da macro está um pouco envolvida, por isso estou respondendo a isso de várias maneiras.

  • A maior parte desta resposta é uma implementação que eu acho adequada para as restrições de espaço no StackOverflow.
  • Há também um artigo do CodeProject que descreve os conceitos básicos da implementação em um tutorial de formato longo. [ Devo movê-lo aqui? Eu acho que é demais para uma resposta SO ].
  • Existe uma biblioteca completa "Better Enums" que implementa a macro em um único arquivo de cabeçalho. Ele também implementa as Consultas de propriedades de tipo N4428 , a revisão atual da proposta de reflexão C ++ 17 N4113. Portanto, pelo menos para enumerações declaradas por meio dessa macro, você pode ter a reflexão de enumeração C ++ 17 proposta agora, em C ++ 11 / C ++ 14.

É simples estender essa resposta aos recursos da biblioteca - nada "importante" é deixado aqui. É, no entanto, bastante tedioso, e há preocupações de portabilidade do compilador.

Isenção de responsabilidade : sou o autor do artigo do CodeProject e da biblioteca.

Você pode tentar o código nesta resposta , a biblioteca e a implementação do N4428 on-line no Wandbox. A documentação da biblioteca também contém uma visão geral de como usá-lo como N4428 , o que explica a parte de enumerações dessa proposta.


Explicação

O código abaixo implementa conversões entre enums e strings. No entanto, também pode ser estendido para fazer outras coisas, como iteração. Essa resposta envolve uma enumeração em a struct. Você também pode gerar traços structao lado de uma enumeração.

A estratégia é gerar algo como isto:

struct Channel {
    enum _enum : char { __VA_ARGS__ };
    constexpr static const Channel          _values[] = { __VA_ARGS__ };
    constexpr static const char * const     _names[] = { #__VA_ARGS__ };

    static const char* _to_string(Channel v) { /* easy */ }
    constexpr static Channel _from_string(const char *s) { /* easy */ }
};

Os problemas são:

  1. Nós vamos acabar com algo como {Red = 1, Green, Blue} o inicializador da matriz de valores. Este não é um C ++ válido, porque Rednão é uma expressão atribuível. Isto é resolvido por vazamento de cada constante para um tipo Tque tem um operador de atribuição, mas vai cair a atribuição: {(T)Red = 1, (T)Green, (T)Blue}.
  2. Da mesma forma, acabaremos com {"Red = 1", "Green", "Blue"}o inicializador da matriz de nomes. Precisamos cortar o " = 1". Não conheço uma ótima maneira de fazer isso em tempo de compilação; portanto, adiaremos isso para executar o tempo. Como um resultado,_to_string não será constexpr, mas _from_stringainda poderá ser constexpr, porque podemos tratar os espaços em branco e os sinais de igual como terminadores quando comparados com cadeias não-aparadas.
  3. Ambos os itens acima precisam de uma macro de "mapeamento" que possa aplicar outra macro a cada elemento __VA_ARGS__. Isso é bastante padrão. Esta resposta inclui uma versão simples que pode lidar com até 8 elementos.
  4. Para que a macro seja realmente independente, ela precisa declarar nenhum dado estático que exija uma definição separada. Na prática, isso significa que as matrizes precisam de tratamento especial. Existem duas soluções possíveis: constexpr(ou apenas const) matrizes no escopo de namespace ou matrizes regulares em constexprfunções embutidas não estáticas. O código nesta resposta é para C ++ 11 e segue a abordagem anterior. O artigo do CodeProject é para C ++ 98 e leva o último.

Código

#include <cstddef>      // For size_t.
#include <cstring>      // For strcspn, strncpy.
#include <stdexcept>    // For runtime_error.



// A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
// macro(a) macro(b) macro(c) ...
// The helper macro COUNT(a, b, c, ...) expands to the number of
// arguments, and IDENTITY(x) is needed to control the order of
// expansion of __VA_ARGS__ on Visual C++ compilers.
#define MAP(macro, ...) \
    IDENTITY( \
        APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
            (macro, __VA_ARGS__))

#define CHOOSE_MAP_START(count) MAP ## count

#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))

#define IDENTITY(x) x

#define MAP1(m, x)      m(x)
#define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
#define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
#define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
#define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
#define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
#define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
#define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))

#define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
    count

#define COUNT(...) \
    IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))



// The type "T" mentioned above that drops assignment operations.
template <typename U>
struct ignore_assign {
    constexpr explicit ignore_assign(U value) : _value(value) { }
    constexpr operator U() const { return _value; }

    constexpr const ignore_assign& operator =(int dummy) const
        { return *this; }

    U   _value;
};



// Prepends "(ignore_assign<_underlying>)" to each argument.
#define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
#define IGNORE_ASSIGN(...) \
    IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))

// Stringizes each argument.
#define STRINGIZE_SINGLE(e) #e,
#define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))



// Some helpers needed for _from_string.
constexpr const char    terminators[] = " =\t\r\n";

// The size of terminators includes the implicit '\0'.
constexpr bool is_terminator(char c, size_t index = 0)
{
    return
        index >= sizeof(terminators) ? false :
        c == terminators[index] ? true :
        is_terminator(c, index + 1);
}

constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
                                 size_t index = 0)
{
    return
        is_terminator(untrimmed[index]) ? s[index] == '\0' :
        s[index] != untrimmed[index] ? false :
        matches_untrimmed(untrimmed, s, index + 1);
}



// The macro proper.
//
// There are several "simplifications" in this implementation, for the
// sake of brevity. First, we have only one viable option for declaring
// constexpr arrays: at namespace scope. This probably should be done
// two namespaces deep: one namespace that is likely to be unique for
// our little enum "library", then inside it a namespace whose name is
// based on the name of the enum to avoid collisions with other enums.
// I am using only one level of nesting.
//
// Declaring constexpr arrays inside the struct is not viable because
// they will need out-of-line definitions, which will result in
// duplicate symbols when linking. This can be solved with weak
// symbols, but that is compiler- and system-specific. It is not
// possible to declare constexpr arrays as static variables in
// constexpr functions due to the restrictions on such functions.
//
// Note that this prevents the use of this macro anywhere except at
// namespace scope. Ironically, the C++98 version of this, which can
// declare static arrays inside static member functions, is actually
// more flexible in this regard. It is shown in the CodeProject
// article.
//
// Second, for compilation performance reasons, it is best to separate
// the macro into a "parametric" portion, and the portion that depends
// on knowing __VA_ARGS__, and factor the former out into a template.
//
// Third, this code uses a default parameter in _from_string that may
// be better not exposed in the public interface.

#define ENUM(EnumName, Underlying, ...)                               \
namespace data_ ## EnumName {                                         \
    using _underlying = Underlying;                                   \
    enum { __VA_ARGS__ };                                             \
                                                                      \
    constexpr const size_t           _size =                          \
        IDENTITY(COUNT(__VA_ARGS__));                                 \
                                                                      \
    constexpr const _underlying      _values[] =                      \
        { IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) };                     \
                                                                      \
    constexpr const char * const     _raw_names[] =                   \
        { IDENTITY(STRINGIZE(__VA_ARGS__)) };                         \
}                                                                     \
                                                                      \
struct EnumName {                                                     \
    using _underlying = Underlying;                                   \
    enum _enum : _underlying { __VA_ARGS__ };                         \
                                                                      \
    const char * _to_string() const                                   \
    {                                                                 \
        for (size_t index = 0; index < data_ ## EnumName::_size;      \
             ++index) {                                               \
                                                                      \
            if (data_ ## EnumName::_values[index] == _value)          \
                return _trimmed_names()[index];                       \
        }                                                             \
                                                                      \
        throw std::runtime_error("invalid value");                    \
    }                                                                 \
                                                                      \
    constexpr static EnumName _from_string(const char *s,             \
                                           size_t index = 0)          \
    {                                                                 \
        return                                                        \
            index >= data_ ## EnumName::_size ?                       \
                    throw std::runtime_error("invalid identifier") :  \
            matches_untrimmed(                                        \
                data_ ## EnumName::_raw_names[index], s) ?            \
                    (EnumName)(_enum)data_ ## EnumName::_values[      \
                                                            index] :  \
            _from_string(s, index + 1);                               \
    }                                                                 \
                                                                      \
    EnumName() = delete;                                              \
    constexpr EnumName(_enum value) : _value(value) { }               \
    constexpr operator _enum() const { return (_enum)_value; }        \
                                                                      \
  private:                                                            \
    _underlying     _value;                                           \
                                                                      \
    static const char * const * _trimmed_names()                      \
    {                                                                 \
        static char     *the_names[data_ ## EnumName::_size];         \
        static bool     initialized = false;                          \
                                                                      \
        if (!initialized) {                                           \
            for (size_t index = 0; index < data_ ## EnumName::_size;  \
                 ++index) {                                           \
                                                                      \
                size_t  length =                                      \
                    std::strcspn(data_ ## EnumName::_raw_names[index],\
                                 terminators);                        \
                                                                      \
                the_names[index] = new char[length + 1];              \
                                                                      \
                std::strncpy(the_names[index],                        \
                             data_ ## EnumName::_raw_names[index],    \
                             length);                                 \
                the_names[index][length] = '\0';                      \
            }                                                         \
                                                                      \
            initialized = true;                                       \
        }                                                             \
                                                                      \
        return the_names;                                             \
    }                                                                 \
};

e

// The code above was a "header file". This is a program that uses it.
#include <iostream>
#include "the_file_above.h"

ENUM(Channel, char, Red = 1, Green, Blue)

constexpr Channel   channel = Channel::_from_string("Red");

int main()
{
    std::cout << channel._to_string() << std::endl;

    switch (channel) {
        case Channel::Red:   return 0;
        case Channel::Green: return 1;
        case Channel::Blue:  return 2;
    }
}

static_assert(sizeof(Channel) == sizeof(char), "");

O programa acima é impresso Red, como seria de esperar. Há um certo grau de segurança de tipo, já que você não pode criar uma enumeração sem inicializá-la e excluir um dos casos switchresultará em um aviso do compilador (dependendo do compilador e dos sinalizadores). Além disso, observe que "Red"foi convertido em uma enumeração durante a compilação.


Heya @mrhthepie, desculpe que sua edição tenha sido rejeitada. Acabei de ver o e-mail sobre isso. Vou incorporá-lo à resposta - obrigado pela correção do bug!
Antron

isso é ótimo. Isso também funcionaria se eu quisesse uma enumeração de bits? Como eu quero uma enumeração de BitFlags, cada um é 1Udeslocado em alguma quantia?
precisa saber é o seguinte

11
parece haver um vazamento de memória no _trimmed_names()código que você postou aqui ( new char[length + 1]mas não foi definido initializedcomo verdadeiro). estou esquecendo de algo? não vejo o mesmo problema no seu código do github.
user3240688

11
Está definido como true, mas fora da iframificação (vazamento de memória originalmente capturado por @mrhthepie). Deve movê-lo para dentro ... Edição. Obrigado pelo olhar mais atento a este e ao código GH.
Antron

11
to_stringpoderia retornar um string_viewdo C ++ 17, que não requer rescisão nula, e se tornar constexpr.
Yakk - Adam Nevraumont 01/09/19

74

Para C ++ 17 C ++ 20, você estará interessado no trabalho do Grupo de Estudo de Reflexão (SG7). Há uma série paralela de artigos que cobrem a redação ( P0194 ) e justificativa, design e evolução ( P0385 ). (Os links são resolvidos para o artigo mais recente de cada série.)

A partir de P0194r2 (15/10/2016), a sintaxe usaria a reflexprpalavra-chave proposta :

meta::get_base_name_v<
  meta::get_element_m<
    meta::get_enumerators_m<reflexpr(MyEnum)>,
    0>
  >

Por exemplo (adaptado do ramo reflexo de Matus Choclik ):

#include <reflexpr>
#include <iostream>

enum MyEnum { AAA = 1, BBB, CCC = 99 };

int main()
{
  auto name_of_MyEnum_0 = 
    std::meta::get_base_name_v<
      std::meta::get_element_m<
        std::meta::get_enumerators_m<reflexpr(MyEnum)>,
        0>
    >;

  // prints "AAA"
  std::cout << name_of_MyEnum_0 << std::endl;
}

A reflexão estática falhou em transformá-lo em C ++ 17 (em vez disso, no rascunho provavelmente final apresentado na reunião de padrões de novembro de 2016 em Issaquah), mas há confiança de que será convertido em C ++ 20; do relatório de viagem de Herb Sutter :

Em particular, o grupo de estudo Reflection revisou a mais recente proposta de reflexão estática mesclada e a encontrou pronta para entrar nos principais grupos do Evolution em nossa próxima reunião para começar a considerar a proposta de reflexão estática unificada para um TS ou para o próximo padrão.


2
@ antron desculpe sua edição foi rejeitada; Eu teria aprovado se tivesse visto a tempo. Eu não tinha visto a N4428, então obrigada por dar a dica.
Ecatmur

3
Não tem problema, obrigado por incorporá-lo. Eu meio que me pergunto por que foi rejeitado. Vejo o motivo "não o torna mais preciso", mas é claramente mais preciso para os dias atuais.
Antron

11
Obrigado :-) Dividi o exemplo final para evitar a barra de rolagem horizontal. Pena que o valor MyEnum::AAAnão possa ser passado como segundo argumento de std::meta::get_enumerators_m: - /
olibre 16/01/17

11
O fato de que uma tarefa tão conceitualmente simples requer 3 níveis de argumentos de modelo aninhado é muito superengenharia. Tenho certeza de que existem razões técnicas específicas para isso. Mas isso não significa que o resultado final seja fácil de usar. Eu amo C ++ e o código faz sentido para mim. Mas 90% dos outros programadores com quem trabalho diariamente evitam C ++ por causa de códigos como este. Estou decepcionado por não ter visto nenhuma solução mais simples e integrada.
void.pointer

2
Parece que a estimativa atual para inclusão do próximo Reflection TS no padrão é C ++ 23 : herbsutter.com/2018/04/02/…
Tim Rae

25

Isso é semelhante a Yuri Finkelstein; mas não requer aumento. Estou usando um mapa para que você possa atribuir qualquer valor às enumerações, em qualquer ordem.

Declaração da classe enum como:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

O código a seguir criará automaticamente a classe enum e a sobrecarga:

  • '+' '+ =' para std :: string
  • '<<' para fluxos
  • '~' apenas para converter em string (qualquer operador unário fará, mas eu pessoalmente não gosto disso por questões de clareza)
  • '*' para obter a contagem de enumerações

Não requer aumento, todas as funções necessárias são fornecidas.

Código:

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

#define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())

std::vector<std::string> splitString(std::string str, char sep = ',') {
    std::vector<std::string> vecString;
    std::string item;

    std::stringstream stringStream(str);

    while (std::getline(stringStream, item, sep))
    {
        vecString.push_back(item);
    }

    return vecString;
}

#define DECLARE_ENUM_WITH_TYPE(E, T, ...)                                                                     \
    enum class E : T                                                                                          \
    {                                                                                                         \
        __VA_ARGS__                                                                                           \
    };                                                                                                        \
    std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__));                                    \
    std::ostream &operator<<(std::ostream &os, E enumTmp)                                                     \
    {                                                                                                         \
        os << E##MapName[static_cast<T>(enumTmp)];                                                            \
        return os;                                                                                            \
    }                                                                                                         \
    size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); }                                 \
    std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; }                          \
    std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } \
    std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } \
    std::string &operator+=(std::string &str, E enumTmp)                                                      \
    {                                                                                                         \
        str += E##MapName[static_cast<T>(enumTmp)];                                                           \
        return str;                                                                                           \
    }                                                                                                         \
    E operator++(E &enumTmp)                                                                                  \
    {                                                                                                         \
        auto iter = E##MapName.find(static_cast<T>(enumTmp));                                                 \
        if (iter == E##MapName.end() || std::next(iter) == E##MapName.end())                                  \
            iter = E##MapName.begin();                                                                        \
        else                                                                                                  \
        {                                                                                                     \
            ++iter;                                                                                           \
        }                                                                                                     \
        enumTmp = static_cast<E>(iter->first);                                                                \
        return enumTmp;                                                                                       \
    }                                                                                                         \
    bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }

#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
template <typename T>
std::map<T, std::string> generateEnumMap(std::string strMap)
{
    STRING_REMOVE_CHAR(strMap, ' ');
    STRING_REMOVE_CHAR(strMap, '(');

    std::vector<std::string> enumTokens(splitString(strMap));
    std::map<T, std::string> retMap;
    T inxMap;

    inxMap = 0;
    for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
    {
        // Token: [EnumName | EnumName=EnumValue]
        std::string enumName;
        T enumValue;
        if (iter->find('=') == std::string::npos)
        {
            enumName = *iter;
        }
        else
        {
            std::vector<std::string> enumNameValue(splitString(*iter, '='));
            enumName = enumNameValue[0];
            //inxMap = static_cast<T>(enumNameValue[1]);
            if (std::is_unsigned<T>::value)
            {
                inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
            }
            else
            {
                inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
            }
        }
        retMap[inxMap++] = enumName;
    }

    return retMap;
}

Exemplo:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

int main(void) {
    TestEnumClass first, second;
    first = TestEnumClass::FOUR;
    second = TestEnumClass::TWO;

    std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)

    std::string strOne;
    strOne = ~first;
    std::cout << strOne << std::endl; // FOUR

    std::string strTwo;
    strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
    std::cout << strTwo << std::endl; // Enum-TWOTHREE-test

    std::string strThree("TestEnumClass: ");
    strThree += second;
    std::cout << strThree << std::endl; // TestEnumClass: TWO
    std::cout << "Enum count=" << *first << std::endl;
}

You can run the code here


11
Podemos ter quebras de linha dentro dessa definição de macro?
einpoklum

11
Eu adicionei a sobrecarga para *obter a contagem de enums ... Eu espero que você não se importa :-)
Peter VARGA

11
Existe alguma razão para essa implementação usar std::map(indexação O (log (n))) em vez de std::unordered_map(indexação O (1))?
River Tam

11
Além disso, acho que os métodos devem ser marcados inlinepara que você possa declarar enumerações em arquivos de cabeçalho como o normal, sem obter erros de "definição múltipla" do vinculador. (não tenho certeza se isso é realmente o / melhor solução mais limpa, embora)
Rio Tam

11
(desculpe o spam, mas não consigo editar os comentários hoje), há outros problemas com esse arquivo de cabeçalho. O mapa ( E##MapName) precisa ser movido para uma unidade de compilação que também tenha acesso à enumeração. Eu criei uma solução, mas não é muito limpa e eu precisaria obter permissão para compartilhá-la. Por enquanto, estou apenas comentando para dizer que não faz sentido marcar os métodos em linha sem os recursos adicionais necessários para dar suporte ao uso em um arquivo de cabeçalho.
River Tam

19

Em 2011, passei um fim de semana aprimorando uma solução baseada em macro e acabei nunca a usando.

Meu procedimento atual é iniciar o Vim, copiar os enumeradores em um corpo de switch vazio, iniciar uma nova macro, transformar o primeiro enumerador em uma instrução de caso, mover o cursor para o início da próxima linha, parar a macro e gerar o caso restante instruções executando a macro nos outros enumeradores.

Macros Vim são mais divertidas que macros C ++.

Exemplo da vida real:

enum class EtherType : uint16_t
{
    ARP   = 0x0806,
    IPv4  = 0x0800,
    VLAN  = 0x8100,
    IPv6  = 0x86DD
};

Vou criar isso:

std::ostream& operator<< (std::ostream& os, EtherType ethertype)
{
    switch (ethertype)
    {
        case EtherType::ARP : return os << "ARP" ;
        case EtherType::IPv4: return os << "IPv4";
        case EtherType::VLAN: return os << "VLAN";
        case EtherType::IPv6: return os << "IPv6";
        // omit default case to trigger compiler warning for missing cases
    };
    return os << static_cast<std::uint16_t>(ethertype);
}

E é assim que eu vivo.

O suporte nativo para a enumeração por enumeração seria muito melhor. Estou muito interessado em ver os resultados do grupo de trabalho de reflexão em C ++ 17.

Uma maneira alternativa de fazer isso foi postada por @sehe nos comentários .


11
Eu faço exatamente isso. Embora eu costumo usar o Surround vim e bloquear seleções ao longo do caminho
veja

@sehe Interessante. Eu deveria dar uma olhada no "surround" porque, atualmente, preciso de muitas teclas pressionadas.
StackedCrooked

Aqui está completo, sem macros (a menos que seja importante .): i.imgur.com/gY4ZhBE.gif
sehe

11
O gif animado é fofo, mas é difícil dizer quando começa e termina e até que ponto estamos. ... na verdade, risque isso, não é fofo, é perturbador. Eu digo mate.
einpoklum

Essa abordagem de seleção de blocos no vim é boa e tudo mais, mas por que não simplesmente usar algo parecido :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/?
Ruslan

14

Não sei se você vai gostar ou não, não estou muito feliz com esta solução, mas é uma abordagem amigável do C ++ 14 porque está usando variáveis ​​de modelo e abusando da especialização de modelo:

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";

int main()
{
    // Prints "AAA"
    std::cout << MyEnumName<MyEnum::AAA> << '\n';
    // Prints "Invalid MyEnum value"
    std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
    // Well... in fact it prints "Invalid MyEnum value" for any value
    // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.

    return 0;
}

O pior dessa abordagem é que é uma dor manter, mas também é uma dor manter outras abordagens semelhantes, não são?

Bons pontos sobre essa abordagem:

  • Usando tempates variáveis ​​(recurso C ++ 14)
  • Com a especialização de modelos, podemos "detectar" quando um valor inválido é usado (mas não tenho certeza se isso pode ser útil).
  • Parece arrumado.
  • A pesquisa de nome é feita em tempo de compilação.

Live example

Editar

Misterioso user673679 você está certo; a abordagem de modelo variável do C ++ 14 não lida com o caso de tempo de execução, foi minha culpa esquecê-lo :(

Mas ainda podemos usar alguns recursos modernos do C ++ e modelos de variáveis, além de truques de modelos variados para obter uma tradução em tempo de execução do valor enum para a string ... é tão incômodo quanto os outros, mas ainda vale a pena mencionar.

Vamos começar a usar um alias de modelo para diminuir o acesso a um mapa enum-to-string:

// enum_map contains pairs of enum value and value string for each enum
// this shortcut allows us to use enum_map<whatever>.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;

// This variable template will create a map for each enum type which is
// instantiated with.
template <typename ENUM>
enum_map<ENUM> enum_values{};

Em seguida, o truque de modelo variável:

template <typename ENUM>
void initialize() {}

template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
    enum_values<ENUM>.emplace(value, name);
    initialize<ENUM>(tail ...);
}

O " melhor truque " aqui é o uso do modelo de variável para o mapa que contém os valores e nomes de cada entrada de enumeração; este mapa será o mesmo em cada unidade de tradução e terá o mesmo nome em todos os lugares, então é bem direto e claro, se chamarmos a initializefunção assim:

initialize
(
    MyEnum::AAA, "AAA",
    MyEnum::BBB, "BBB",
    MyEnum::CCC, "CCC"
);

Estamos atribuindo nomes a cada MyEnumentrada e podemos ser usados ​​em tempo de execução:

std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';

Mas pode ser aprimorado com SFINAE e <<operador de sobrecarga :

template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
std::ostream &operator <<(std::ostream &o, const ENUM value)
{
    static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
    auto found = enum_values<ENUM>.find(value);

    return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
}

Com o correto operator <<agora, podemos usar o enum desta maneira:

std::cout << MyEnum::AAA << '\n';

Isso também é incômodo de manter e pode ser melhorado, mas espero que você entenda.

Live example


Isso parece bem legal (é possível simplesmente não definir a variável não especializada?). Talvez esteja faltando alguma coisa, embora não veja como ele lida com o caso de tempo de execução.
user673679

@Paula_plus_plus: Você não deveria usar apenas um std::arraymapa em vez do pesado? Só será preferível para enumerações começando em ... o que, 2 ^ 10 valores? Talvez até mais.
einpoklum

@einpoklum que seria incrível se pudéssemos garantir em tempo de execução quantos elementos um enumtem. Infelizmente, não podemos. E o objetivo do mapa é apenas associar nomes a valores, e é para isso que std::mapserve.
PaperBirdMaster

@Paula_plus_plus: você já está chamando uma initialize()função cujo número de argumentos é o número de valores enum, para saber o número de valores no tempo de compilação. É apenas o valor específico que você deve imprimir que é conhecido apenas em tempo de execução. Além disso, mesmo se você não soubesse esse número, um std :: vector seria mais rápido que um std :: map, novamente, em quase todos os casos realistas.
einpoklum

@einpoklum esse é realmente um ponto muito bom, vou pensar nisso, obrigado! A única coisa que me preocupa é que std::arraynão é um contêiner de valor-chave e, portanto, carece de métodos find; de qualquer forma eu vou pensar nisso.
PaperBirdMaster

7

Se você enumparece

enum MyEnum
{
  AAA = -8,
  BBB = '8',
  CCC = AAA + BBB
};

Você pode mover o conteúdo do enumarquivo para um novo arquivo:

AAA = -8,
BBB = '8',
CCC = AAA + BBB

E então os valores podem ser cercados por uma macro:

// default definition
#ifned ITEM(X,Y)
#define ITEM(X,Y)
#endif

// Items list
ITEM(AAA,-8)
ITEM(BBB,'8')
ITEM(CCC,AAA+BBB)

// clean up
#undef ITEM

A próxima etapa pode ser incluir os itens enumnovamente:

enum MyEnum
{
  #define ITEM(X,Y) X=Y,
  #include "enum_definition_file"
};

E, finalmente, você pode gerar funções utilitárias sobre isso enum:

std::string ToString(MyEnum value)
{
  switch( value )
  {
    #define ITEM(X,Y) case X: return #X;
    #include "enum_definition_file"
  }

  return "";
}

MyEnum FromString(std::string const& value)
{
  static std::map<std::string,MyEnum> converter
  {
    #define ITEM(X,Y) { #X, X },
    #include "enum_definition_file"
  };

  auto it = converter.find(value);
  if( it != converter.end() )
    return it->second;
  else
    throw std::runtime_error("Value is missing");
}

A solução pode ser aplicada a padrões C ++ mais antigos e não usa elementos modernos de C ++, mas pode ser usada para gerar muito código sem muito esforço e manutenção.


3
Não há necessidade de um arquivo separado. Esta é essencialmente uma macro x .
HolyBlackCat

@HolyBlackCat Se você dividir a solução em alguns arquivos, poderá reutilizar os valores de enumeração para propósitos diferentes
eferion

Estou tentando dizer que você pode fazer a mesma coisa se colocar a lista de valores em uma única macro ao lado da definição de enum em um cabeçalho.
HolyBlackCat

@HolyBlackCat sim, eu entendo você, mas eu prefiro esta solução. por outro lado, esta solução pode ser encontrada no código-fonte clang, então eu acho que é uma boa maneira de resolver o problema
eferion

Justo. Eu não deveria ter rebaixado isso, eu acho, pois ele pode realmente ter alguns usos. (Pardon a edição manequim, o sistema bloqueia o meu voto contrário.)
HolyBlackCat

6

Eu tive o mesmo problema alguns dias atrás. Como não consegui encontrar nenhuma solução C ++ sem uma mágica macro estranha, decidi escrever um gerador de código CMake para gerar instruções simples de caso de alternância.

Uso:

enum2str_generate(
  PATH          <path to place the files in>
  CLASS_NAME    <name of the class (also prefix for the files)>
  FUNC_NAME     <name of the (static) member function>
  NAMESPACE     <the class will be inside this namespace>
  INCLUDES      <LIST of files where the enums are defined>
  ENUMS         <LIST of enums to process>
  BLACKLIST     <LIST of constants to ignore>
  USE_CONSTEXPR <whether to use constexpr or not (default: off)>
  USE_C_STRINGS <whether to use c strings instead of std::string or not (default: off)>
)

A função pesquisa os arquivos de inclusão no sistema de arquivos (usa os diretórios de inclusão fornecidos com o comando include_directories), lê-os e faz algumas regex para gerar a classe e as funções.

NOTA: constexpr implica em linha em C ++, portanto, o uso da opção USE_CONSTEXPR gerará uma classe apenas de cabeçalho!

Exemplo:

./includes/ah:

enum AAA : char { A1, A2 };

typedef enum {
   VAL1          = 0,
   VAL2          = 1,
   VAL3          = 2,
   VAL_FIRST     = VAL1,    // Ignored
   VAL_LAST      = VAL3,    // Ignored
   VAL_DUPLICATE = 1,       // Ignored
   VAL_STRANGE   = VAL2 + 1 // Must be blacklisted
} BBB;

./CMakeLists.txt:

include_directories( ${PROJECT_SOURCE_DIR}/includes ...)

enum2str_generate(
   PATH       "${PROJECT_SOURCE_DIR}"
   CLASS_NAME "enum2Str"
   NAMESPACE  "abc"
   FUNC_NAME  "toStr"
   INCLUDES   "a.h" # WITHOUT directory
   ENUMS      "AAA" "BBB"
   BLACKLIST  "VAL_STRANGE")

Gera:

./enum2Str.hpp:

/*!
  * \file enum2Str.hpp
  * \warning This is an automatically generated file!
  */

#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP

#include <string>
#include <a.h>

namespace abc {

class enum2Str {
 public:
   static std::string toStr( AAA _var ) noexcept;
   static std::string toStr( BBB _var ) noexcept;
};

}

#endif // ENUM2STR_HPP

./enum2Str.cpp:

/*!
  * \file enum2Str.cpp
  * \warning This is an automatically generated file!
  */

#include "enum2Str.hpp"

namespace abc {

/*!
 * \brief Converts the enum AAA to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( AAA _var ) noexcept {
   switch ( _var ) {
      case A1: return "A1";
      case A2: return "A2";
      default: return "<UNKNOWN>";
   }
}

/*!
 * \brief Converts the enum BBB to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( BBB _var ) noexcept {
   switch ( _var ) {
      case VAL1: return "VAL1";
      case VAL2: return "VAL2";
      case VAL3: return "VAL3";
      default: return "<UNKNOWN>";
   }
}
}

Atualizar:

O script agora também suporta enumerações com escopo definido (enum class | struct) e eu o mudei para um repositório separado com alguns outros scripts que costumo usar: https://github.com/mensinda/cmakeBuildTools


Uau! Ideia muito original e inovador :-) Eu espero que você tenha a coragem de atualizar o seu gerador, a fim de fornecer um constexpre noexceptversão ;-) Eu também apenas olhou seu projeto GitHub ;-) Felicidades
olibre

11
Atualizado o gerador. As funções agora serão sempre constexpr e enum: <type> agora é suportado. Obrigado pela estrela :)
Mense

O link está quebrado ... -.-
yeoman

O link agora está corrigido.
Mense

4

Apenas gere suas enums. Escrever um gerador para esse fim é um trabalho de cerca de cinco minutos.

Código gerador em java e python, super fácil de portar para qualquer idioma que você quiser, incluindo C ++.

Também é super fácil de estender por qualquer funcionalidade que você desejar.

exemplo de entrada:

First = 5
Second
Third = 7
Fourth
Fifth=11

cabeçalho gerado:

#include <iosfwd>

enum class Hallo
{
    First = 5,
    Second = 6,
    Third = 7,
    Fourth = 8,
    Fifth = 11
};

std::ostream & operator << (std::ostream &, const Hallo&);

arquivo cpp gerado

#include <ostream>

#include "Hallo.h"

std::ostream & operator << (std::ostream &out, const Hallo&value)
{
    switch(value)
    {
    case Hallo::First:
        out << "First";
        break;
    case Hallo::Second:
        out << "Second";
        break;
    case Hallo::Third:
        out << "Third";
        break;
    case Hallo::Fourth:
        out << "Fourth";
        break;
    case Hallo::Fifth:
        out << "Fifth";
        break;
    default:
        out << "<unknown>";
    }

    return out;
}

E o gerador, de forma muito concisa como modelo para portabilidade e extensão. Este código de exemplo realmente tenta evitar a substituição de arquivos, mas ainda o utiliza por seu próprio risco.

package cppgen;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EnumGenerator
{
    static void fail(String message)
    {
        System.err.println(message);
        System.exit(1);
    }

    static void run(String[] args)
    throws Exception
    {
        Pattern pattern = Pattern.compile("\\s*(\\w+)\\s*(?:=\\s*(\\d+))?\\s*", Pattern.UNICODE_CHARACTER_CLASS);
        Charset charset = Charset.forName("UTF8");
        String tab = "    ";

        if (args.length != 3)
        {
            fail("Required arguments: <enum name> <input file> <output dir>");
        }

        String enumName = args[0];

        File inputFile = new File(args[1]);

        if (inputFile.isFile() == false)
        {
            fail("Not a file: [" + inputFile.getCanonicalPath() + "]");
        }

        File outputDir = new File(args[2]);

        if (outputDir.isDirectory() == false)
        {
            fail("Not a directory: [" + outputDir.getCanonicalPath() + "]");
        }

        File headerFile = new File(outputDir, enumName + ".h");
        File codeFile = new File(outputDir, enumName + ".cpp");

        for (File file : new File[] { headerFile, codeFile })
        {
            if (file.exists())
            {
                fail("Will not overwrite file [" + file.getCanonicalPath() + "]");
            }
        }

        int nextValue = 0;

        Map<String, Integer> fields = new LinkedHashMap<>();

        try
        (
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), charset));
        )
        {
            while (true)
            {
                String line = reader.readLine();

                if (line == null)
                {
                    break;
                }

                if (line.trim().length() == 0)
                {
                    continue;
                }

                Matcher matcher = pattern.matcher(line);

                if (matcher.matches() == false)
                {
                    fail("Syntax error: [" + line + "]");
                }

                String fieldName = matcher.group(1);

                if (fields.containsKey(fieldName))
                {
                    fail("Double fiend name: " + fieldName);
                }

                String valueString = matcher.group(2);

                if (valueString != null)
                {
                    int value = Integer.parseInt(valueString);

                    if (value < nextValue)
                    {
                        fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName);
                    }

                    nextValue = value;
                }

                fields.put(fieldName, nextValue);

                ++nextValue;
            }
        }

        try
        (
            PrintWriter headerWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(headerFile), charset));
            PrintWriter codeWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(codeFile), charset));
        )
        {
            headerWriter.println();
            headerWriter.println("#include <iosfwd>");
            headerWriter.println();
            headerWriter.println("enum class " + enumName);
            headerWriter.println('{');
            boolean first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                if (first == false)
                {
                    headerWriter.println(",");
                }

                headerWriter.print(tab + entry.getKey() + " = " + entry.getValue());

                first = false;
            }
            if (first == false)
            {
                headerWriter.println();
            }
            headerWriter.println("};");
            headerWriter.println();
            headerWriter.println("std::ostream & operator << (std::ostream &, const " + enumName + "&);");
            headerWriter.println();

            codeWriter.println();
            codeWriter.println("#include <ostream>");
            codeWriter.println();
            codeWriter.println("#include \"" + enumName + ".h\"");
            codeWriter.println();
            codeWriter.println("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)");
            codeWriter.println('{');
            codeWriter.println(tab + "switch(value)");
            codeWriter.println(tab + '{');
            first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                codeWriter.println(tab + "case " + enumName + "::" + entry.getKey() + ':');
                codeWriter.println(tab + tab + "out << \"" + entry.getKey() + "\";");
                codeWriter.println(tab + tab + "break;");

                first = false;
            }
            codeWriter.println(tab + "default:");
            codeWriter.println(tab + tab + "out << \"<unknown>\";");
            codeWriter.println(tab + '}');
            codeWriter.println();
            codeWriter.println(tab + "return out;");
            codeWriter.println('}');
            codeWriter.println();
        }
    }

    public static void main(String[] args)
    {
        try
        {
            run(args);
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
            System.exit(1);
        }
    }
}

E uma porta para o Python 3.5, porque diferente o suficiente para ser potencialmente útil

import re
import collections
import sys
import io
import os

def fail(*args):
    print(*args)
    exit(1)

pattern = re.compile(r'\s*(\w+)\s*(?:=\s*(\d+))?\s*')
tab = "    "

if len(sys.argv) != 4:
    n=0
    for arg in sys.argv:
        print("arg", n, ":", arg, " / ", sys.argv[n])
        n += 1
    fail("Required arguments: <enum name> <input file> <output dir>")

enumName = sys.argv[1]

inputFile = sys.argv[2]

if not os.path.isfile(inputFile):
    fail("Not a file: [" + os.path.abspath(inputFile) + "]")

outputDir = sys.argv[3]

if not os.path.isdir(outputDir):
    fail("Not a directory: [" + os.path.abspath(outputDir) + "]")

headerFile = os.path.join(outputDir, enumName + ".h")
codeFile = os.path.join(outputDir, enumName + ".cpp")

for file in [ headerFile, codeFile ]:
    if os.path.exists(file):
        fail("Will not overwrite file [" + os.path.abspath(file) + "]")

nextValue = 0

fields = collections.OrderedDict()

for line in open(inputFile, 'r'):
    line = line.strip()

    if len(line) == 0:
        continue

    match = pattern.match(line)

    if match == None:
        fail("Syntax error: [" + line + "]")

    fieldName = match.group(1)

    if fieldName in fields:
        fail("Double field name: " + fieldName)

    valueString = match.group(2)

    if valueString != None:
        value = int(valueString)

        if value < nextValue:
            fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName)

        nextValue = value

    fields[fieldName] = nextValue

    nextValue += 1

headerWriter = open(headerFile, 'w')
codeWriter = open(codeFile, 'w')

try:
    headerWriter.write("\n")
    headerWriter.write("#include <iosfwd>\n")
    headerWriter.write("\n")
    headerWriter.write("enum class " + enumName + "\n")
    headerWriter.write("{\n")
    first = True
    for fieldName, fieldValue in fields.items():
        if not first:
            headerWriter.write(",\n")

        headerWriter.write(tab + fieldName + " = " + str(fieldValue))

        first = False
    if not first:
        headerWriter.write("\n")
    headerWriter.write("};\n")
    headerWriter.write("\n")
    headerWriter.write("std::ostream & operator << (std::ostream &, const " + enumName + "&);\n")
    headerWriter.write("\n")

    codeWriter.write("\n")
    codeWriter.write("#include <ostream>\n")
    codeWriter.write("\n")
    codeWriter.write("#include \"" + enumName + ".h\"\n")
    codeWriter.write("\n")
    codeWriter.write("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)\n")
    codeWriter.write("{\n")
    codeWriter.write(tab + "switch(value)\n")
    codeWriter.write(tab + "{\n")
    for fieldName in fields.keys():
        codeWriter.write(tab + "case " + enumName + "::" + fieldName + ":\n")
        codeWriter.write(tab + tab + "out << \"" + fieldName + "\";\n")
        codeWriter.write(tab + tab + "break;\n")
    codeWriter.write(tab + "default:\n")
    codeWriter.write(tab + tab + "out << \"<unknown>\";\n")
    codeWriter.write(tab + "}\n")
    codeWriter.write("\n")
    codeWriter.write(tab + "return out;\n")
    codeWriter.write("}\n")
    codeWriter.write("\n")
finally:
    headerWriter.close()
    codeWriter.close()

11
Muito obrigado por compartilhar seu gerador em dois idiomas :-) Mas você tem alguma idéia de como gerar em tempo de compilação? Por exemplo, podemos imaginar traduzir seu gerador usando instruções CMake para atualizar o código gerado em C ++ quando os dados de entrada são alterados? Meu sonho é forçar o compilador C ++ a gerar enumerações na compilação usando metaprogramação ( variadic template classe constexprfunções).
precisa saber é

Otoh, caso seja muito complicado adicionar um comando cmake personalizado, você pode automatizar seu IDE ou chamar o gererator manualmente e ter a saída no controle de origem. Às vezes, é uma boa idéia ter gerado código no controle de origem de qualquer maneira, desde que não seja muito, e as pessoas entendem que não devem fazer alterações manuais, porque às vezes é interessante olhar para o histórico dos arquivos gerados quando você 're depuração algo estranho e ter a suspeita de que uma recente mudança para o gerador pode ter quebrado algo :)
yeoman

Sobre a geração de coisas em tempo de compilação, isso é muito fácil no LISP, porque a sintaxe é extremamente limpa e fácil. Isso é ajudado pelo fato de ser digitado dinamicamente, o que permite que seja conciso e legível sem muita sintaxe. O equivalente a macros LISP em C ++ precisaria de uma maneira muito complicada de descrever o AST do que você está tentando gerar. E um AST para C ++ nunca é bonito :(
yeoman 8/16

Diretamente no Make, em vez de cmake, é super fácil. Apenas gere destinos .hepp .cpp para cada arquivo .enum via find e faça com que esses destinos dependam dos referidos enum defs, para que sejam automaticamente gerados novamente quando os arquivos .enum def forem alterados. É provavelmente muito mais fácil em cmake porque é cheio de magia para este tipo de coisas, mas eu uso fazem regularmente, formiga, e Gradle, mas só tem um conhecimento limitado da Maven, cmake, e grunhido :)
yeoman

Obrigado pela sua resposta :-) Eu acho que a maioria dos desenvolvedores de C ++ apreciará se o seu gerador puder detectar enumerações diretamente no código C ++, como enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};em várias linhas :-D Você acha que pode adaptar seu gerador para detectar um enumdentro de um C ++ Arquivo? O melhor seria gerar código apenas na detecção de uma tag como /*<Generate enum to string here>*/. Em seguida, seu gerador grava no local o código gerado em C ++ correspondente (substituindo o código gerado anteriormente). ^ _ ^ Que gerador incrível não é? Cheers :-)
olibre

3

Como pelo pedido do OP, aqui um despojado versão da solução macro feio com base no impulso Preprosessor e variádicos Macros .

Ele permite uma lista simples, como sintaxe dos elementos do enumerador, além de definir valores para elementos específicos, para que

XXX_ENUM(foo,(a,b,(c,42)));

expande para

enum foo {
    a,
    b,
    c=42
};

Juntamente com as funções necessárias para produzir e fazer algumas conversões de volta. Essa macro existe aqui há muito tempo e não tenho certeza de que seja a maneira mais eficiente, ou que seja uma forma compatível, mas desde então está funcionando

O código completo pode ser visto em ação em Ideone e Coliru .

Sua feiúra gigantesca está acima; Eu teria colocado isso atrás de spoilers para proteger seus olhos, se eu soubesse, mas a redução não gosta de mim.

A biblioteca (mesclada em um único arquivo de cabeçalho)

#include <boost/preprocessor.hpp>
#include <string>
#include <unordered_map>

namespace xxx
{

template<class T>
struct enum_cast_adl_helper { };

template<class E>
E enum_cast( const std::string& s )
{
    return do_enum_cast(s,enum_cast_adl_helper<E>());
}

template<class E>
E enum_cast( const char* cs )
{
    std::string s(cs);
    return enum_cast<E>(s);
}

} // namespace xxx

#define XXX_PP_ARG_N(                             \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N

#define XXX_PP_RSEQ_N()                 \
         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,0 

#define XXX_PP_NARG_(...) XXX_PP_ARG_N(__VA_ARGS__)
#define XXX_PP_NARG(...)  XXX_PP_NARG_(__VA_ARGS__,XXX_PP_RSEQ_N())
#define XXX_TUPLE_SIZE_INTERNAL(TUPLE) XXX_PP_NARG TUPLE

#define XXX_TUPLE_CHOICE(i)                            \
  BOOST_PP_APPLY(                                      \
    BOOST_PP_TUPLE_ELEM(                               \
      25, i, (                                         \
        (0), (1), (2), (3), (4), (5), (6), (7), (8),   \
        (9), (10), (11), (12), (13), (14), (15), (16), \
        (17), (18), (19), (20), (21), (22), (23), (24) \
  ) ) )

#define BOOST_PP_BOOL_00  BOOST_PP_BOOL_0
#define BOOST_PP_BOOL_01  BOOST_PP_BOOL_1
#define BOOST_PP_BOOL_02  BOOST_PP_BOOL_2
#define BOOST_PP_BOOL_03  BOOST_PP_BOOL_3
#define BOOST_PP_BOOL_04  BOOST_PP_BOOL_4
#define BOOST_PP_BOOL_05  BOOST_PP_BOOL_5
#define BOOST_PP_BOOL_06  BOOST_PP_BOOL_6
#define BOOST_PP_BOOL_07  BOOST_PP_BOOL_7
#define BOOST_PP_BOOL_08  BOOST_PP_BOOL_8
#define BOOST_PP_BOOL_09  BOOST_PP_BOOL_9
#define BOOST_PP_BOOL_010 BOOST_PP_BOOL_10
#define BOOST_PP_BOOL_011 BOOST_PP_BOOL_11
#define BOOST_PP_BOOL_012 BOOST_PP_BOOL_12
#define BOOST_PP_BOOL_013 BOOST_PP_BOOL_13
#define BOOST_PP_BOOL_014 BOOST_PP_BOOL_14
#define BOOST_PP_BOOL_015 BOOST_PP_BOOL_15
#define BOOST_PP_BOOL_016 BOOST_PP_BOOL_16
#define BOOST_PP_BOOL_017 BOOST_PP_BOOL_17
#define BOOST_PP_BOOL_018 BOOST_PP_BOOL_18
#define BOOST_PP_BOOL_019 BOOST_PP_BOOL_19
#define BOOST_PP_BOOL_020 BOOST_PP_BOOL_20
#define BOOST_PP_BOOL_021 BOOST_PP_BOOL_21
#define BOOST_PP_BOOL_022 BOOST_PP_BOOL_22
#define BOOST_PP_BOOL_023 BOOST_PP_BOOL_23
#define BOOST_PP_BOOL_024 BOOST_PP_BOOL_24
#define BOOST_PP_BOOL_025 BOOST_PP_BOOL_25
#define BOOST_PP_BOOL_026 BOOST_PP_BOOL_26
#define BOOST_PP_BOOL_027 BOOST_PP_BOOL_27
#define BOOST_PP_BOOL_028 BOOST_PP_BOOL_28
#define BOOST_PP_BOOL_029 BOOST_PP_BOOL_29
#define BOOST_PP_BOOL_030 BOOST_PP_BOOL_30
#define BOOST_PP_BOOL_031 BOOST_PP_BOOL_31
#define BOOST_PP_BOOL_032 BOOST_PP_BOOL_32
#define BOOST_PP_BOOL_033 BOOST_PP_BOOL_33
#define BOOST_PP_BOOL_034 BOOST_PP_BOOL_34
#define BOOST_PP_BOOL_035 BOOST_PP_BOOL_35
#define BOOST_PP_BOOL_036 BOOST_PP_BOOL_36
#define BOOST_PP_BOOL_037 BOOST_PP_BOOL_37
#define BOOST_PP_BOOL_038 BOOST_PP_BOOL_38
#define BOOST_PP_BOOL_039 BOOST_PP_BOOL_39
#define BOOST_PP_BOOL_040 BOOST_PP_BOOL_40
#define BOOST_PP_BOOL_041 BOOST_PP_BOOL_41
#define BOOST_PP_BOOL_042 BOOST_PP_BOOL_42
#define BOOST_PP_BOOL_043 BOOST_PP_BOOL_43
#define BOOST_PP_BOOL_044 BOOST_PP_BOOL_44
#define BOOST_PP_BOOL_045 BOOST_PP_BOOL_45
#define BOOST_PP_BOOL_046 BOOST_PP_BOOL_46
#define BOOST_PP_BOOL_047 BOOST_PP_BOOL_47
#define BOOST_PP_BOOL_048 BOOST_PP_BOOL_48
#define BOOST_PP_BOOL_049 BOOST_PP_BOOL_49
#define BOOST_PP_BOOL_050 BOOST_PP_BOOL_50
#define BOOST_PP_BOOL_051 BOOST_PP_BOOL_51
#define BOOST_PP_BOOL_052 BOOST_PP_BOOL_52
#define BOOST_PP_BOOL_053 BOOST_PP_BOOL_53
#define BOOST_PP_BOOL_054 BOOST_PP_BOOL_54
#define BOOST_PP_BOOL_055 BOOST_PP_BOOL_55
#define BOOST_PP_BOOL_056 BOOST_PP_BOOL_56
#define BOOST_PP_BOOL_057 BOOST_PP_BOOL_57
#define BOOST_PP_BOOL_058 BOOST_PP_BOOL_58
#define BOOST_PP_BOOL_059 BOOST_PP_BOOL_59
#define BOOST_PP_BOOL_060 BOOST_PP_BOOL_60
#define BOOST_PP_BOOL_061 BOOST_PP_BOOL_61
#define BOOST_PP_BOOL_062 BOOST_PP_BOOL_62
#define BOOST_PP_BOOL_063 BOOST_PP_BOOL_63

#define BOOST_PP_DEC_00  BOOST_PP_DEC_0
#define BOOST_PP_DEC_01  BOOST_PP_DEC_1
#define BOOST_PP_DEC_02  BOOST_PP_DEC_2
#define BOOST_PP_DEC_03  BOOST_PP_DEC_3
#define BOOST_PP_DEC_04  BOOST_PP_DEC_4
#define BOOST_PP_DEC_05  BOOST_PP_DEC_5
#define BOOST_PP_DEC_06  BOOST_PP_DEC_6
#define BOOST_PP_DEC_07  BOOST_PP_DEC_7
#define BOOST_PP_DEC_08  BOOST_PP_DEC_8
#define BOOST_PP_DEC_09  BOOST_PP_DEC_9
#define BOOST_PP_DEC_010 BOOST_PP_DEC_10
#define BOOST_PP_DEC_011 BOOST_PP_DEC_11
#define BOOST_PP_DEC_012 BOOST_PP_DEC_12
#define BOOST_PP_DEC_013 BOOST_PP_DEC_13
#define BOOST_PP_DEC_014 BOOST_PP_DEC_14
#define BOOST_PP_DEC_015 BOOST_PP_DEC_15
#define BOOST_PP_DEC_016 BOOST_PP_DEC_16
#define BOOST_PP_DEC_017 BOOST_PP_DEC_17
#define BOOST_PP_DEC_018 BOOST_PP_DEC_18
#define BOOST_PP_DEC_019 BOOST_PP_DEC_19
#define BOOST_PP_DEC_020 BOOST_PP_DEC_20
#define BOOST_PP_DEC_021 BOOST_PP_DEC_21
#define BOOST_PP_DEC_022 BOOST_PP_DEC_22
#define BOOST_PP_DEC_023 BOOST_PP_DEC_23
#define BOOST_PP_DEC_024 BOOST_PP_DEC_24
#define BOOST_PP_DEC_025 BOOST_PP_DEC_25
#define BOOST_PP_DEC_026 BOOST_PP_DEC_26
#define BOOST_PP_DEC_027 BOOST_PP_DEC_27
#define BOOST_PP_DEC_028 BOOST_PP_DEC_28
#define BOOST_PP_DEC_029 BOOST_PP_DEC_29
#define BOOST_PP_DEC_030 BOOST_PP_DEC_30
#define BOOST_PP_DEC_031 BOOST_PP_DEC_31
#define BOOST_PP_DEC_032 BOOST_PP_DEC_32
#define BOOST_PP_DEC_033 BOOST_PP_DEC_33
#define BOOST_PP_DEC_034 BOOST_PP_DEC_34
#define BOOST_PP_DEC_035 BOOST_PP_DEC_35
#define BOOST_PP_DEC_036 BOOST_PP_DEC_36
#define BOOST_PP_DEC_037 BOOST_PP_DEC_37
#define BOOST_PP_DEC_038 BOOST_PP_DEC_38
#define BOOST_PP_DEC_039 BOOST_PP_DEC_39
#define BOOST_PP_DEC_040 BOOST_PP_DEC_40
#define BOOST_PP_DEC_041 BOOST_PP_DEC_41
#define BOOST_PP_DEC_042 BOOST_PP_DEC_42
#define BOOST_PP_DEC_043 BOOST_PP_DEC_43
#define BOOST_PP_DEC_044 BOOST_PP_DEC_44
#define BOOST_PP_DEC_045 BOOST_PP_DEC_45
#define BOOST_PP_DEC_046 BOOST_PP_DEC_46
#define BOOST_PP_DEC_047 BOOST_PP_DEC_47
#define BOOST_PP_DEC_048 BOOST_PP_DEC_48
#define BOOST_PP_DEC_049 BOOST_PP_DEC_49
#define BOOST_PP_DEC_050 BOOST_PP_DEC_50
#define BOOST_PP_DEC_051 BOOST_PP_DEC_51
#define BOOST_PP_DEC_052 BOOST_PP_DEC_52
#define BOOST_PP_DEC_053 BOOST_PP_DEC_53
#define BOOST_PP_DEC_054 BOOST_PP_DEC_54
#define BOOST_PP_DEC_055 BOOST_PP_DEC_55
#define BOOST_PP_DEC_056 BOOST_PP_DEC_56
#define BOOST_PP_DEC_057 BOOST_PP_DEC_57
#define BOOST_PP_DEC_058 BOOST_PP_DEC_58
#define BOOST_PP_DEC_059 BOOST_PP_DEC_59
#define BOOST_PP_DEC_060 BOOST_PP_DEC_60
#define BOOST_PP_DEC_061 BOOST_PP_DEC_61
#define BOOST_PP_DEC_062 BOOST_PP_DEC_62
#define BOOST_PP_DEC_063 BOOST_PP_DEC_63

#define XXX_TO_NUMx(x) 0 ## x
#define XXX_TO_NUM(x) BOOST_PP_ADD(0,XXX_TO_NUMx(x))
#define XXX_STRINGIZEX(x) # x
#define XXX_VSTRINGIZE_SINGLE(a,b,x) XXX_STRINGIZE(x)
#define XXX_VSTRINGIZE_TUPLE(tpl) XXX_TUPLE_FOR_EACH(XXX_VSTRINGIZE_SINGLE,,tpl)
#define XXX_TUPLE_SIZE(TUPLE) XXX_TO_NUM(XXX_TUPLE_CHOICE(XXX_TUPLE_SIZE_INTERNAL(TUPLE)))
#define XXX_TUPLE_FOR_EACH(MACRO,DATA,TUPLE) BOOST_PP_LIST_FOR_EACH(MACRO,DATA,BOOST_PP_TUPLE_TO_LIST(XXX_TUPLE_SIZE(TUPLE),TUPLE))
#define XXX_STRINGIZE(x) XXX_STRINGIZEX(x)
#define XXX_VSTRINGIZE(...) XXX_VSTRINGIZE_TUPLE((__VA_ARGS__))
#define XXX_CAST_TO_VOID_ELEMENT(r,data,elem) (void)(elem);
#define XXX_CAST_TO_VOID_INTERNAL(TUPLE) XXX_TUPLE_FOR_EACH(XXX_CAST_TO_VOID_ELEMENT,,TUPLE)    
#define XXX_CAST_TO_VOID(...) XXX_CAST_TO_VOID_INTERNAL((__VA_ARGS__))
#define XXX_ENUM_EXTRACT_SP(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en) = BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),1,en)
#define XXX_ENUM_ELEMENT(r,data,elem) BOOST_PP_IF( XXX_TUPLE_SIZE(elem), XXX_ENUM_EXTRACT_SP(elem), elem) ,
#define XXX_ENUM_EXTRACT_ELEMENT(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en)
#define XXX_ENUM_CASE_ELEMENT(en) BOOST_PP_IF( XXX_TUPLE_SIZE(en), XXX_ENUM_EXTRACT_ELEMENT(en), en )
#define XXX_ENUM_CASE(r,data,elem) case data :: XXX_ENUM_CASE_ELEMENT(elem) : return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem));
#define XXX_ENUM_IFELSE(r,data,elem) else if( en == data :: XXX_ENUM_CASE_ELEMENT(elem)) { return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); }
#define XXX_ENUM_CASTLIST(r,data,elem) { XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_QUALIFIED_CASTLIST(r,data,elem) { #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },

#define XXX_ENUM_INTERNAL(TYPE,NAME,TUPLE)                       \
enum TYPE                                                        \
{                                                                \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_ELEMENT,,TUPLE)                   \
   BOOST_PP_CAT(last_enum_,NAME)                                 \
};                                                               \
                                                                 \
inline                                                           \
const char* to_string( NAME en )                                 \
{                                                                \
   if(false)                                                     \
   {                                                             \
   }                                                             \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_IFELSE,NAME,TUPLE)                \
   else if( en == NAME :: BOOST_PP_CAT(last_enum_,NAME) )        \
   {                                                             \
     return XXX_VSTRINGIZE(NAME,::,BOOST_PP_CAT(last_enum_,NAME));  \
   }                                                             \
   else                                                          \
   {                                                             \
     return "Invalid enum value specified for " # NAME;          \
   }                                                             \
}                                                                \
                                                                 \
inline                                                           \
std::ostream& operator<<( std::ostream& os, const NAME& en )     \
{                                                                \
   os << to_string(en);                                          \
   return os;                                                    \
}                                                                \
                                                                 \
inline                                                           \
NAME do_enum_cast( const std::string& s, const ::xxx::enum_cast_adl_helper<NAME>& ) \
{                                                                \
  static const std::unordered_map<std::string,NAME> map =        \
  {                                                              \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_CASTLIST,NAME,TUPLE)             \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_QUALIFIED_CASTLIST,NAME,TUPLE)   \
  };                                                             \
                                                                 \
  auto cit = map.find(s);                                        \
  if( cit == map.end() )                                         \
  {                                                              \
    throw std::runtime_error("Invalid value to cast to enum");   \
  }                                                              \
  return cit->second;                                            \
}

#define XXX_ENUM(NAME,TUPLE) XXX_ENUM_INTERNAL(NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS(NAME,TUPLE) XXX_ENUM_INTERNAL(class NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(class NAME : TYPE,NAME,TUPLE)
#define XXX_ENUM_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(NAME : TYPE,NAME,TUPLE)

Uso

#include "xxx_enum.h"  // the above lib
#include <iostream>

XXX_ENUM(foo,(a,b,(c,42)));

int main()
{
  std::cout << "foo::a = "            << foo::a            <<'\n';
  std::cout << "(int)foo::c = "       << (int)foo::c       <<'\n';
  std::cout << "to_string(foo::b) = " << to_string(foo::b) <<'\n';
  std::cout << "xxx::enum_cast<foo>(\"b\") = " << xxx::enum_cast<foo>("b") <<'\n';
}

Compilação (copiar colar cabeçalho dentro main.cpp)

> g++ --version | sed 1q
g++ (GCC) 4.9.2

> g++ -std=c++14 -pedantic -Wall -Wextra main.cpp
main.cpp:268:31: warning: extra ';' [-Wpedantic]
     XXX_ENUM(foo,(a,b,(c,42)));
                               ^

Resultado

foo::a = foo::a
(int)foo::c = 42
to_string(foo::b) = foo::b
xxx::enum_cast<foo>("b") = foo::b

5
Esse bloco de código é uma jornada louca pelas incríveis paisagens da metaprogramação de magia negra. Na verdade, senti-me aliviado ao chegar main- Lar, doce lar!
Quentin

Basta adicionar um link ao coliru para verificar a saída (existem alguns avisos, clique no link dentro da sua resposta). Eu também me dividi em Lib / Usage. O material namespace xxxpode ser movido para o local do cabeçalho? Você pode dizer na introdução seu uso boost/preprocessor.hppe, portanto, a resposta é moderna e compatível com C ++ . Corrija os avisos e limpe um pouco o código fonte para obter melhor qualidade.
olibre

@olibre: É copypastad de eu acho que 5 cabeçalhos diferentes em nossa biblioteca. O enum_cast é de outra parte mais geral, mas pensei em adicioná-lo também para ver para que serve o do_enum_cast na macro. Os avisos são apenas do meu main<tab>do vim, incluindo argumentos que eu não uso. Eu não acho que esse código possa ser realmente limpo, é apenas para mostrar o que pode ser feito e o que não deve ser;) e se eu o alterar aqui, não será mais o código que eu uso na produção ... é uma daquelas coisas frágeis que, uma vez que funcione, é melhor você nunca tocar, pois pode entrar em colapso de maneiras que ninguém poderia prever.
PlasmaHH

Tudo bem, plasma, vejo que isso pode ser visto como uma prova de conceito . Mas há muita sobrecarga de macro para ser votada. No entanto, obrigado por compartilhar. Cheers
olibre 4/15

Oi Plasma. Realizei uma limpeza profunda do código fonte + concluída pela compilação e execução da saída. Por favor, verifique minha edição . Espero que esteja tudo bem para você. A resposta é mais valiosa? No entanto, a sobrecarga da macro ainda é horrível! Tenha um bom dia :-) Cheers
olibre 4/15

2

A solução a seguir é baseada em um std::array<std::string,N>para um determinado enum.

Para enuma std::stringconversão, podemos apenas converter o enum size_te procurar a string da matriz. A operação é O (1) e não requer alocação de heap.

#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/stringize.hpp>

#include <string>
#include <array>
#include <iostream>

#define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem)

// ENUM
// ============================================================================
#define ENUM(X, SEQ) \
struct X {   \
    enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \
    static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \
        return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \
    } \
    static std::string to_string(Enum e) { \
        auto a = array_of_strings(); \
        return a[static_cast<size_t>(e)]; \
    } \
}

Para std::stringa enumconversão, teríamos que fazer uma pesquisa linear sobre a matriz e converter o índice da matriz em enum.

Experimente aqui com exemplos de uso: http://coliru.stacked-crooked.com/a/e4212f93bee65076

Edit: Retrabalhei minha solução para que o Enum personalizado possa ser usado dentro de uma classe.


Obrigado pela sua resposta interessante. Retrabalhe sua proposta para usar sua macro em uma classe. Consulte coliru.stacked-crooked.com/a/00d362eba836d04b Além disso, tente usar constexpre noexeptpalavras - chave quando possível. Cheers :-)
olibre

A pergunta não especificou este requisito.
FKaria

Pergunta atualizada (veja o exemplo). Dois outros requisitos: (1) suporte ao tipo de enum e (2) os valores podem ser diferentes da sequência 0, 1, 2 ...
olibre 4/16

Eu reformulei minha solução para que ela pudesse ser usada dentro de uma classe. Ainda não descobri como diferenciar os valores de 0,1,2.
FKaria 20/05

Oi FKaria. Muito obrigado pelo seu retrabalho. Fiz algumas alterações para oferecer suporte a várias enums da mesma classe e também para o enum class X : Typeformato. Revise minha contribuição: coliru.stacked-crooked.com/a/b02db9190d3491a3 O que você acha das minhas alterações? Você tem alguma idéia para oferecer suporte a valores definidos no enum? Exemplo enum E{A=3, B=6, C=A-B};Cheers
olibre

2

Esta essência fornece um mapeamento simples com base em modelos variáveis ​​C ++.

Esta é uma versão simplificada para C ++ 17 do mapa baseado em tipo da gist :

#include <cstring> // http://stackoverflow.com/q/24520781

template<typename KeyValue, typename ... RestOfKeyValues>
struct map {
  static constexpr typename KeyValue::key_t get(const char* val) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)  // C++17 if constexpr
      return KeyValue::key; // Returns last element
    else {
      static_assert(KeyValue::val != nullptr,
                  "Only last element may have null name");
      return strcmp(val, KeyValue::val()) 
            ? map<RestOfKeyValues...>::get(val) : KeyValue::key;
    }
  }
  static constexpr const char* get(typename KeyValue::key_t key) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)
      return (KeyValue::val != nullptr) && (key == KeyValue::key)
            ? KeyValue::val() : "";
    else
      return (key == KeyValue::key) 
            ? KeyValue::val() : map<RestOfKeyValues...>::get(key);
  }
};

template<typename Enum, typename ... KeyValues>
class names {
  typedef map<KeyValues...> Map;
public:
  static constexpr Enum get(const char* nam) noexcept {
    return Map::get(nam);
  }
  static constexpr const char* get(Enum key) noexcept {
    return Map::get(key);
  }
};

Um exemplo de uso:

enum class fasion {
    fancy,
    classic,
    sporty,
    emo,
    __last__ = emo,
    __unknown__ = -1
};

#define NAME(s) static inline constexpr const char* s() noexcept {return #s;}
namespace name {
    NAME(fancy)
    NAME(classic)
    NAME(sporty)
    NAME(emo)
}

template<auto K, const char* (*V)()>  // C++17 template<auto>
struct _ {
    typedef decltype(K) key_t;
    typedef decltype(V) name_t;
    static constexpr key_t  key = K; // enum id value
    static constexpr name_t val = V; // enum id name
};

typedef names<fasion,
    _<fasion::fancy, name::fancy>,
    _<fasion::classic, name::classic>,
    _<fasion::sporty, name::sporty>,
    _<fasion::emo, name::emo>,
    _<fasion::__unknown__, nullptr>
> fasion_names;

O map<KeyValues...>pode ser usado em ambas as direções:

  • fasion_names::get(fasion::emo)
  • fasion_names::get("emo")

Este exemplo está disponível em godbolt.org

int main ()
{
  constexpr auto str = fasion_names::get(fasion::emo);
  constexpr auto fsn = fasion_names::get(str);
  return (int) fsn;
}

Resultado de gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

11
Maneira de meta-programação muito interessante. Eu tentei simplificar um pouco a resposta para ser autônomo (sem dependência do link Gist). Para ser conciso e compreensível, finalmente editei bastante sua resposta. Você ainda concorda com minhas alterações? Cheers ;-)
olibre

2

Também fiquei frustrado com esse problema por um longo tempo, juntamente com o problema de converter um tipo em string de maneira adequada. No entanto, para o último problema, fiquei surpreso com a solução explicada em É possível imprimir o tipo de uma variável no C ++ padrão? , usando a ideia de Posso obter nomes de tipos C ++ de uma maneira consistente? . Usando esta técnica, uma função análoga pode ser construída para obter um valor enum como string:

#include <iostream>
using namespace std;

class static_string
{
    const char* const p_;
    const std::size_t sz_;

public:
    typedef const char* const_iterator;

    template <std::size_t N>
    constexpr static_string(const char(&a)[N]) noexcept
        : p_(a)
        , sz_(N - 1)
    {}

    constexpr static_string(const char* p, std::size_t N) noexcept
        : p_(p)
        , sz_(N)
    {}

    constexpr const char* data() const noexcept { return p_; }
    constexpr std::size_t size() const noexcept { return sz_; }

    constexpr const_iterator begin() const noexcept { return p_; }
    constexpr const_iterator end()   const noexcept { return p_ + sz_; }

    constexpr char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};

inline std::ostream& operator<<(std::ostream& os, static_string const& s)
{
    return os.write(s.data(), s.size());
}

/// \brief Get the name of a type
template <class T>
static_string typeName()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 30, p.size() - 30 - 1);
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 37, p.size() - 37 - 7);
#endif

}

namespace details
{
    template <class Enum>
    struct EnumWrapper
    {
        template < Enum enu >
        static static_string name()
        {
#ifdef __clang__
            static_string p = __PRETTY_FUNCTION__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 73 + enumType.size(), p.size() - 73 - enumType.size() - 1);
#elif defined(_MSC_VER)
            static_string p = __FUNCSIG__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7);
#endif
        }
    };
}

/// \brief Get the name of an enum value
template <typename Enum, Enum enu>
static_string enumName()
{
    return details::EnumWrapper<Enum>::template name<enu>();
}

enum class Color
{
    Blue = 0,
    Yellow = 1
};


int main() 
{
    std::cout << "_" << typeName<Color>() << "_"  << std::endl;
    std::cout << "_" << enumName<Color, Color::Blue>() << "_"  << std::endl;
    return 0;
}

O código acima foi testado apenas no Clang (consulte https://ideone.com/je5Quv ) e no VS2015, mas deve ser adaptável a outros compiladores, mexendo um pouco com as constantes inteiras. Obviamente, ele ainda usa macros ocultas, mas pelo menos uma não precisa de acesso à implementação do enum.


Isso falha com g ++ 6.3.0 e C ++ 14.
einpoklum

Interessante porque o enum pode ser declarado normalmente sem precisar envolvê-lo em uma macro. Embora eu não goste das dependências do compilador e das constantes mágicas.
Zett42 26/08/19

2

Peguei a ideia do @antron e a implementei de maneira diferente: gerando uma verdadeira classe enum .

Esta implementação atende a todos os requisitos listados na pergunta original, mas atualmente possui apenas uma limitação real : assume que os valores da enumeração não são fornecidos ou, se fornecidos, devem começar com 0 e subir sequencialmente sem lacunas.

Essa não é uma limitação intrínseca - simplesmente porque eu não uso valores ad-hoc enum. Se isso for necessário, pode-se substituir a pesquisa de vetores pela implementação tradicional de switch / case.

A solução usa algum c ++ 17 para variáveis ​​em linha, mas isso pode ser facilmente evitado se necessário. Ele também usa boost: trim por causa da simplicidade.

Mais importante ainda, são necessárias apenas 30 linhas de código e nenhuma macros de magia negra. O código está abaixo. Ele deve ser colocado no cabeçalho e incluído em vários módulos de compilação.

Ele pode ser usado da mesma maneira sugerida anteriormente neste tópico:

ENUM(Channel, int, Red, Green = 1, Blue)
std::out << "My name is " << Channel::Green;
//prints My name is Green

Por favor, deixe-me saber se isso é útil e como pode ser melhorado ainda mais.


#include <boost/algorithm/string.hpp>   
struct EnumSupportBase {
  static std::vector<std::string> split(const std::string s, char delim) {
    std::stringstream ss(s);
    std::string item;
    std::vector<std::string> tokens;
    while (std::getline(ss, item, delim)) {
        auto pos = item.find_first_of ('=');
        if (pos != std::string::npos)
            item.erase (pos);
        boost::trim (item);
        tokens.push_back(item);
    }
    return tokens;
  }
};
#define ENUM(EnumName, Underlying, ...) \
    enum class EnumName : Underlying { __VA_ARGS__, _count }; \
    struct EnumName ## Support : EnumSupportBase { \
        static inline std::vector<std::string> _token_names = split(#__VA_ARGS__, ','); \
        static constexpr const char* get_name(EnumName enum_value) { \
            int index = (int)enum_value; \
            if (index >= (int)EnumName::_count || index < 0) \
               return "???"; \
            else \
               return _token_names[index].c_str(); \
        } \
    }; \
    inline std::ostream& operator<<(std::ostream& os, const EnumName & es) { \
        return os << EnumName##Support::get_name(es); \
    } 

2

Desde que você consiga escrever um .h/.cpppar separado para cada enum consultável, esta solução funcionará com quase a mesma sintaxe e recursos que um enum c ++ regular:

// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if

enum MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = AAA + BBB
};

O .cpparquivo possui três linhas de clichê:

// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>

Exemplo de uso:

for (MyEnum value : EnumTraits<MyEnum>::GetValues())
    std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;

Código

Esta solução requer 2 arquivos de origem:

// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>

#define ETRAITS
#define EDECL(x) x

template <class ENUM>
class EnumTraits
{
public:
    static const std::vector<ENUM>& GetValues()
    {
        return values;
    }

    static ENUM GetValue(const char* name)
    {
        auto match = valueMap.find(name);
        return (match == valueMap.end() ? ENUM() : match->second);
    }

    static const char* GetName(ENUM value)
    {
        auto match = nameMap.find(value);
        return (match == nameMap.end() ? nullptr : match->second);
    }

public:
    EnumTraits() = delete;

    using vector_type = std::vector<ENUM>;
    using name_map_type = std::unordered_map<ENUM, const char*>;
    using value_map_type = std::unordered_map<std::string, ENUM>;

private:
    static const vector_type values;
    static const name_map_type nameMap;
    static const value_map_type valueMap;
};

struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }

...e

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;

#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

Explicação

Esta implementação explora o fato de que a lista entre elementos de uma definição de enum também pode ser usada como uma lista inicializada para a inicialização de membros da classe.

Quando ETRAITSé avaliado no contexto de EnumTraits.inl, ele se expande para uma definição de membro estático para oEnumTraits<> classe.

A EDECLmacro transforma cada membro da enumeração em valores de lista do inicializador que posteriormente são passados ​​ao construtor do membro para preencher as informações da enumeração.

A EnumInitGuardclasse foi projetada para consumir os valores do inicializador de enum e depois recolher - deixando uma lista pura de dados de enum.

Benefícios

  • c++sintaxe
  • Funciona de forma idêntica para ambos enume enum class(* quase)
  • Funciona para enumtipos com qualquer tipo subjacente numérico
  • Funciona para enumtipos com valores inicializadores automáticos, explícitos e fragmentados
  • Trabalhos para renomeação em massa (vinculação intellisense preservada)
  • Apenas 5 símbolos do pré-processador (3 globais)

* Por outro lado enums, inicializadores em enum classtipos que referenciam outros valores da mesma enum devem ter esses valores totalmente qualificados

Desembolsos

  • Requer um .h/.cpppar separado para cada consultaenum
  • Depende de complicado macroe includemágico
  • Pequenos erros de sintaxe explodem em erros muito maiores
  • Definindo classou namespaceenums com escopo não é trivial
  • Inicialização sem tempo de compilação

Comentários

O Intellisense irá reclamar um pouco sobre o acesso de membros privados ao abrir EnumTraits.inl, mas como as macros expandidas estão realmente definindo os membros da classe, isso não é realmente um problema.

o #ifndef ENUM_INCLUDE_MULTI bloco na parte superior do arquivo de cabeçalho é um pequeno aborrecimento que provavelmente pode ser reduzido a uma macro ou algo assim, mas é pequeno o suficiente para ser usado no tamanho atual.

A declaração de uma enumeração no escopo do espaço para nome exige que a enum seja declarada para frente primeiro dentro do escopo do espaço para nome e, em seguida, definida no espaço para nome global. Além disso, qualquer inicializador de enumeração que use valores da mesma enumeração deve ter esses valores totalmente qualificados.

namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}

2

Não tenho certeza se essa abordagem já foi abordada em uma das outras respostas (na verdade é, veja abaixo). Encontrei o problema várias vezes e não encontrei uma solução que não usasse macros ofuscadas ou bibliotecas de terceiros. Por isso, decidi escrever minha própria versão macro ofuscada.

O que eu quero ativar é o equivalente a

enum class test1 { ONE, TWO = 13, SIX };

std::string toString(const test1& e) { ... }

int main() {
    test1 x;
    std::cout << toString(x) << "\n";
    std::cout << toString(test1::TWO) << "\n";
    std::cout << static_cast<std::underlying_type<test1>::type>(test1::TWO) << "\n";
    //std::cout << toString(123);// invalid
}

que deve imprimir

ONE
TWO
13

Eu não sou um fã de macros. No entanto, a menos que o c ++ suporte nativamente a conversão de enumerações em strings, é necessário usar algum tipo de geração de código e / ou macros (e duvido que isso aconteça muito em breve). Estou usando uma macro X :

// x_enum.h
#include <string>
#include <map>
#include <type_traits>
#define x_begin enum class x_name {
#define x_val(X) X
#define x_value(X,Y) X = Y
#define x_end };
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end

#define x_begin inline std::string toString(const x_name& e) { \
                static std::map<x_name,std::string> names = { 
#define x_val(X)      { x_name::X , #X }
#define x_value(X,Y)  { x_name::X , #X }
#define x_end }; return names[e]; }
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#undef x_name
#undef x_enum_def

A maioria é definir e indefinir símbolos que o usuário passará como parâmetro para o marco X por meio de uma inclusão. O uso é assim

#define x_name test1
#define x_enum_def x_begin x_val(ONE) , \
                           x_value(TWO,13) , \
                           x_val(SIX) \
                   x_end
#include "x_enum.h"

Demonstração ao vivo

Observe que eu não incluí ainda a escolha do tipo subjacente. Eu não precisava dele até agora, mas deve ser simples modificar o código para permitir isso.

Somente depois de escrever isso, percebi que é bastante semelhante à resposta eferions . Talvez eu tenha lido antes e talvez tenha sido a principal fonte de inspiração. Eu sempre falhei em entender as macros X até escrever minhas próprias;).


1

Soluções usando enum dentro de classe / estrutura (padrões de estrutura com membros públicos) e operadores sobrecarregados:

struct Color
{
    enum Enum { RED, GREEN, BLUE };
    Enum e;

    Color() {}
    Color(Enum e) : e(e) {}

    Color operator=(Enum o) { e = o; return *this; }
    Color operator=(Color o) { e = o.e; return *this; }
    bool operator==(Enum o) { return e == o; }
    bool operator==(Color o) { return e == o.e; }
    operator Enum() const { return e; }

    std::string toString() const
    {
        switch (e)
        {
        case Color::RED:
            return "red";
        case Color::GREEN:
            return "green";
        case Color::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
};

Do lado de fora, parece quase exatamente como um enum de classe:

Color red;
red = Color::RED;
Color blue = Color::BLUE;

cout << red.toString() << " " << Color::GREEN << " " << blue << endl;

Isso exibirá "vermelho 1 2". Você poderia sobrecarregar << para transformar a saída azul em uma string (embora isso possa causar ambiguidade, portanto não é possível), mas não funcionaria com Color :: GREEN, pois não se converte automaticamente em Color.

O objetivo de ter uma conversão implícita em Enum (que converte implicitamente em int ou tipo dado) é ser capaz de:

Color color;
switch (color) ...

Isso funciona, mas também significa que esse trabalho também:

int i = color;

Com uma classe enum, não seria compilada. Você deve ter cuidado se sobrecarregar duas funções que usam enum e um número inteiro ou remover a conversão implícita ...

Outra solução envolveria o uso de uma classe enum real e de membros estáticos:

struct Color
{
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    //same as previous...
};

Possivelmente, ocupa mais espaço e é mais demorado, mas causa um erro de compilação para conversões int implícitas. Eu usaria este por causa disso!

Certamente há uma sobrecarga nisso, mas acho que é mais simples e parece melhor do que outro código que eu já vi. Também há potencial para adicionar funcionalidades, que podem ter escopo definido dentro da classe.

Edit : isso funciona e a maioria pode ser compilada antes da execução:

class Color
{
public:
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    constexpr Color() : e(Enum::RED) {}
    constexpr Color(Enum e) : e(e) {}

    constexpr bool operator==(Enum o) const { return e == o; }
    constexpr bool operator==(Color o) const { return e == o.e; }
    constexpr operator Enum() const { return e; }

    Color& operator=(Enum o) { const_cast<Enum>(this->e) = o; return *this; }
    Color& operator=(Color o) { const_cast<Enum>(this->e) = o.e; return *this; }

    std::string toString() const
    {
        switch (e)
        {
        case Enum::RED:
            return "red";
        case Enum::GREEN:
            return "green";
        case Enum::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
private:
    const Enum e;
};

Isso é muito interessante :-) No entanto, sua versão atual implica que você deve escrever manualmente as coisas case Enum::RED: return "red";. A pergunta é sobre como automatizar esse material pelo compilador (no momento da compilação). A idéia da pergunta é alterar ou adicionar apenas valores de enumeração sem precisar atualizar o material toString(). Você vê? Graças
olibre

1

Solução muito simples com uma grande restrição: você não pode atribuir valores personalizados a enumvalores, mas com o regex certo, você pode. você também pode adicionar um mapa para convertê-los de volta aos enumvalores sem muito mais esforço:

#include <vector>
#include <string>
#include <regex>
#include <iterator>

std::vector<std::string> split(const std::string& s, 
                               const std::regex& delim = std::regex(",\\s*"))
{
    using namespace std;
    vector<string> cont;
    copy(regex_token_iterator<string::const_iterator>(s.begin(), s.end(), delim, -1), 
         regex_token_iterator<string::const_iterator>(),
         back_inserter(cont));
    return cont;
}

#define EnumType(Type, ...)     enum class Type { __VA_ARGS__ }

#define EnumStrings(Type, ...)  static const std::vector<std::string> \
                                Type##Strings = split(#__VA_ARGS__);

#define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \
                                EnumStrings(Type, __VA_ARGS__)

Exemplo de uso:

EnumToString(MyEnum, Red, Green, Blue);

Obrigado Malem pela sua ideia inovadora. Eu editei sua resposta para melhorar a legibilidade. Espero que você goste das minhas alterações. Continue melhorando sua resposta: (1) estenda a seção "Exemplo de uso" com algo como auto name = MyEnumStrings["Red"];- (2) Por que você usa enum class? - (3) Você apoia enum class MyEnum : char { Red, Green, Blue };? - (4) Explique a função split()- (5) Você precisa de parâmetro const std::regex& delim? - (6) E quanto à geração MyEnumStringsno momento da compilação? => Você pode usar constexpr? ... Cheers :-)
olibre

Eu realmente gosto dessa abordagem. Muito curto e fácil de entender.
Anton Holmberg

1

EDIT: confira abaixo uma versão mais recente

Como mencionado acima, o N4113 é a solução final para esse problema, mas teremos que esperar mais de um ano para vê-lo sair.

Enquanto isso, se você quiser esse recurso, precisará recorrer a modelos "simples" e alguma mágica do pré-processador.

Enumerador

template<typename T>
class Enum final
{
    const char* m_name;
    const T m_value;
    static T m_counter;

public:
    Enum(const char* str, T init = m_counter) : m_name(str), m_value(init) {m_counter = (init + 1);}

    const T value() const {return m_value;}
    const char* name() const {return m_name;}
};

template<typename T>
T Enum<T>::m_counter = 0;

#define ENUM_TYPE(x)      using Enum = Enum<x>;
#define ENUM_DECL(x,...)  x(#x,##__VA_ARGS__)
#define ENUM(...)         const Enum ENUM_DECL(__VA_ARGS__);

Uso

#include <iostream>

//the initialization order should be correct in all scenarios
namespace Level
{
    ENUM_TYPE(std::uint8)
    ENUM(OFF)
    ENUM(SEVERE)
    ENUM(WARNING)
    ENUM(INFO, 10)
    ENUM(DEBUG)
    ENUM(ALL)
}

namespace Example
{
    ENUM_TYPE(long)
    ENUM(A)
    ENUM(B)
    ENUM(C, 20)
    ENUM(D)
    ENUM(E)
    ENUM(F)
}

int main(int argc, char** argv)
{
    Level::Enum lvl = Level::WARNING;
    Example::Enum ex = Example::C;
    std::cout << lvl.value() << std::endl; //2
    std::cout << ex.value() << std::endl; //20
}

Explicação simples

Enum<T>::m_counteré definido como 0 dentro de cada declaração de namespace.
( Alguém poderia me indicar onde esse comportamento é mencionado no padrão? )
A mágica do pré-processador automatiza a declaração dos enumeradores.

Desvantagens

  • Não é um enumtipo verdadeiro , portanto, não é possível promover int
  • Não pode ser usado em casos de switch

Solução alternativa

Este sacrifica a numeração das linhas (na verdade não), mas pode ser usado em casos de comutação .

#define ENUM_TYPE(x) using type = Enum<x>
#define ENUM(x)      constexpr type x{__LINE__,#x}

template<typename T>
struct Enum final
{
    const T value;
    const char* name;

    constexpr operator const T() const noexcept {return value;}
    constexpr const char* operator&() const noexcept {return name;}
};

Errata

#line 0entra -pedanticem conflito com o GCC e clang.

Gambiarra

Comece em #line 1e subtraia 1 de __LINE__.
Ou, não use -pedantic.
E enquanto estamos nisso, evite o VC ++ a todo custo, sempre foi uma piada de um compilador.

Uso

#include <iostream>

namespace Level
{
    ENUM_TYPE(short);
    #line 0
    ENUM(OFF);
    ENUM(SEVERE);
    ENUM(WARNING);
    #line 10
    ENUM(INFO);
    ENUM(DEBUG);
    ENUM(ALL);
    #line <next line number> //restore the line numbering
};

int main(int argc, char** argv)
{
    std::cout << Level::OFF << std::endl;   // 0
    std::cout << &Level::OFF << std::endl;  // OFF

    std::cout << Level::INFO << std::endl;  // 10
    std::cout << &Level::INFO << std::endl; // INFO

    switch(/* any integer or integer-convertible type */)
    {
    case Level::OFF:
        //...
        break;

    case Level::SEVERE:
        //...
        break;

    //...
    }

    return 0;
}

Implementação e uso na vida real

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Referência rápida

#line lineno - cppreference.com


0

Eu escrevi uma biblioteca para resolver esse problema, tudo acontece no tempo de compilação, exceto para receber a mensagem.

Uso:

Use macro DEF_MSGpara definir um par de macro e mensagem:

DEF_MSG(CODE_OK,   "OK!")
DEF_MSG(CODE_FAIL, "Fail!")

CODE_OKé a macro a ser usada e "OK!"é a mensagem correspondente.

Use get_message()ou apenas gm()para receber a mensagem:

get_message(CODE_FAIL);  // will return "Fail!"
gm(CODE_FAIL);           // works exactly the same as above

Use MSG_NUMpara descobrir quantas macros foram definidas. Isso aumentará automaticamente, você não precisará fazer nada.

Mensagens predefinidas:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Projeto: libcodemsg


A biblioteca não cria dados extras. Tudo acontece no tempo de compilação. Em message_def.h, gera um enumchamado MSG_CODE; in message_def.c, gera uma variável contém todas as strings static const char* _g_messages[].

Nesse caso, a biblioteca é limitada para criar enumapenas uma . Isso é ideal para valores de retorno, por exemplo:

MSG_CODE foo(void) {
    return MSG_OK; // or something else
}

MSG_CODE ret = foo();

if (MSG_OK != ret) {
    printf("%s\n", gm(ret););
}

Outra coisa que eu gosto desse design é que você pode gerenciar definições de mensagens em arquivos diferentes.


Eu encontrei a solução para esta pergunta parece muito melhor.


Oi Madwyn. Obrigado pela sua ideia. Mas como isso funciona? Qual é a sobrecarga? (zero overhead ou cria dados extras?). Sua proposta parece bem, mas infelizmente uma declaração DEF_MSGdeve ser usada / atualizada / mantida para cada enumvalor: - / E é isso que idealmente gostaríamos de parar de fazer ... Cheers
olibre

Obrigado pela resposta, @olibre. Por favor, verifique a resposta atualizada. Não vejo sobrecarga aqui, exceto que uma chamada de função é necessária para acessar as strings. DEF_MSGfaz o enumemparelhado com a mensagem, embora tenha algumas limitações.
Madwyn

Obrigado pela explicação em anexo na sua resposta :-) Sua biblioteca está bem, mas não pode ser usada para várias enumerações: - / E o suporte do enum class(C ++ 11) ? Você pode usar constexprpara limitar _g_messagesem tempo de execução. Ofereça suporte a vários enumtipos (evitando _g_messages) usando a metaprogramação (tipo de transmissão {tipo de enum, valor de enum}) ou talvez variáveis ​​de modelo (C ++ 14) . Eu acho que sua biblioteca (ainda não) se encaixa nos requisitos C ++ 11/14/17. O que você acha? Cheers ;-)
olibre 23/11

11
Obrigado pelo acompanhamento. Eu aprendi algo novo hoje! A classe enum e as variáveis ​​de modelo parecem boas. Eu acho que minha resposta foi um pouco "fora de tópico", pois era com sabor de C.
Madwyn

0
#define ENUM_MAKE(TYPE, ...) \
        enum class TYPE {__VA_ARGS__};\
        struct Helper_ ## TYPE { \
            static const String& toName(TYPE type) {\
                int index = static_cast<int>(type);\
                return splitStringVec()[index];}\
            static const TYPE toType(const String& name){\
                static std::unordered_map<String,TYPE> typeNameMap;\
                if( typeNameMap.empty() )\
                {\
                    const StringVector& ssVec = splitStringVec();\
                    for (size_t i = 0; i < ssVec.size(); ++i)\
                        typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\
                }\
                return typeNameMap[name];}\
            static const StringVector& splitStringVec() {\
                static StringVector typeNameVector;\
                if(typeNameVector.empty()) \
                {\
                    typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\
                    for (auto& name : typeNameVector)\
                    {\
                        name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \
                        name = String(#TYPE) + "::" + name;\
                    }\
                }\
                return typeNameVector;\
            }\
        };


using String = std::string;
using StringVector = std::vector<String>;

   StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
    {
        StringVector ret;
        // Pre-allocate some space for performance
        ret.reserve(maxSplits ? maxSplits+1 : 10);    // 10 is guessed capacity for most case

        unsigned int numSplits = 0;

        // Use STL methods 
        size_t start, pos;
        start = 0;
        do 
        {
            pos = str.find_first_of(delims, start);
            if (pos == start)
            {
                // Do nothing
                start = pos + 1;
            }
            else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
            {
                // Copy the rest of the string
                ret.push_back( str.substr(start) );
                break;
            }
            else
            {
                // Copy up to delimiter
                ret.push_back( str.substr(start, pos - start) );

                if(preserveDelims)
                {
                    // Sometimes there could be more than one delimiter in a row.
                    // Loop until we don't find any more delims
                    size_t delimStart = pos, delimPos;
                    delimPos = str.find_first_not_of(delims, delimStart);
                    if (delimPos == String::npos)
                    {
                        // Copy the rest of the string
                        ret.push_back( str.substr(delimStart) );
                    }
                    else
                    {
                        ret.push_back( str.substr(delimStart, delimPos - delimStart) );
                    }
                }

                start = pos + 1;
            }
            // parse up to next real data
            start = str.find_first_not_of(delims, start);
            ++numSplits;

        } while (pos != String::npos);



        return ret;
    }

exemplo

ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)


    MY_TEST s1 = MY_TEST::MY_1;
    MY_TEST s2 = MY_TEST::MY_2;
    MY_TEST s3 = MY_TEST::MY_3;

    String z1 = Helper_MY_TEST::toName(s1);
    String z2 = Helper_MY_TEST::toName(s2);
    String z3 = Helper_MY_TEST::toName(s3);

    MY_TEST q1 = Helper_MY_TEST::toType(z1);
    MY_TEST q2 = Helper_MY_TEST::toType(z2);
    MY_TEST q3 = Helper_MY_TEST::toType(z3);

a macro ENUM_MAKE gera automaticamente 'classe enum' e classe auxiliar com 'função de reflexão enum'.

Para reduzir erros, tudo de uma vez é definido com apenas um ENUM_MAKE.

A vantagem desse código é criada automaticamente para reflexão e um exame mais detalhado do código macro, código fácil de entender. 'enum to string', 'string to enum', o desempenho é o algoritmo O (1).

As desvantagens são quando, pela primeira vez, a classe auxiliar para o vetor de seqüência de caracteres da relação de enumeração e o mapa é inicializada. mas se você quiser, também será pré-inicializado. -


Embora esse código possa responder à pergunta, seria melhor explicar como ele resolve o problema sem apresentar outros e por que usá-lo. As respostas somente de código não são úteis a longo prazo.
JAL

Ei, desculpe, eu não falo inglês muito bem.
desperado_98

a macro ENUM_MAKE gera automaticamente 'classe enum' e classe auxiliar com 'função de reflexão enum'. / Para reduzir erros, de uma vez Tudo é definido com apenas um ENUM_MAKE. A vantagem desse código é criada automaticamente para reflexão e um exame mais detalhado do código macro, código fácil de entender. O desempenho de 'enum to string', 'string to enum' é o algoritmo O (1). As desvantagens são quando, pela primeira vez, é iniciada a classe auxiliar para o vetor e o mapa de seqüência de caracteres da relação de enumeração. mas se você quiser, também será pré-inicializado.
desesperado_98

Oi desesperado_98. Obrigado pela sua contribuição. Edite sua resposta e insira nele o conteúdo do seu comentário. O compilador pode calcular seu exemplo em tempo de compilação, se você usar alguns truques de metaprogramação e constexpr. Quero dizer as funções toName()e toType()podem ser avaliadas durante a compilação e não durante a execução (tempo de execução). Adote o estilo C ++ 11/14/17 em sua resposta. Cheers ;-)
olibre 23/11

Além disso: Sua macro é compatível enum class MyEnum : short { A, B, C };?
olibre

0

minha solução é sem uso de macro.

vantagens:

  • você vê exatamente o que você faz
  • o acesso é com mapas de hash, tão bom para muitas enumerações valiosas
  • não é necessário considerar ordem ou valores não consecutivos
  • tradução de enum para string e tradução de string para enum, enquanto o valor de enum adicionado deve ser adicionado em apenas um local adicional

desvantagens:

  • você precisa replicar todos os valores de enumerações como texto
  • acesso no mapa de hash deve considerar maiúsculas de minúsculas
  • manutenção se a adição de valores for dolorosa - é necessário adicionar enum e traduzir diretamente o mapa

então ... até o dia em que o C ++ implemente a funcionalidade C # Enum.Parse, ficarei preso a isso:

            #include <unordered_map>

            enum class Language
            { unknown, 
                Chinese, 
                English, 
                French, 
                German
                // etc etc
            };

            class Enumerations
            {
            public:
                static void fnInit(void);

                static std::unordered_map <std::wstring, Language> m_Language;
                static std::unordered_map <Language, std::wstring> m_invLanguage;

            private:
                static void fnClear();
                static void fnSetValues(void);
                static void fnInvertValues(void);

                static bool m_init_done;
            };

            std::unordered_map <std::wstring, Language> Enumerations::m_Language = std::unordered_map <std::wstring, Language>();
            std::unordered_map <Language, std::wstring> Enumerations::m_invLanguage = std::unordered_map <Language, std::wstring>();

            void Enumerations::fnInit()
            {
                fnClear();
                fnSetValues();
                fnInvertValues();
            }

            void Enumerations::fnClear()
            {
                m_Language.clear();
                m_invLanguage.clear();
            }

            void Enumerations::fnSetValues(void)
            {   
                m_Language[L"unknown"] = Language::unknown;
                m_Language[L"Chinese"] = Language::Chinese;
                m_Language[L"English"] = Language::English;
                m_Language[L"French"] = Language::French;
                m_Language[L"German"] = Language::German;
                // and more etc etc
            }

            void Enumerations::fnInvertValues(void)
            {
                for (auto it = m_Language.begin(); it != m_Language.end(); it++)
                {
                    m_invLanguage[it->second] = it->first;
                }
            }

            // usage -
            //Language aLanguage = Language::English;
            //wstring sLanguage = Enumerations::m_invLanguage[aLanguage];

            //wstring sLanguage = L"French" ;
            //Language aLanguage = Enumerations::m_Language[sLanguage];

0

Bem, mais uma opção. Um caso de uso típico é onde você precisa de constantes para os verbos HTTP, além de usar seus valores de versão de sequência.

O exemplo:

int main () {

  VERB a = VERB::GET;
  VERB b = VERB::GET;
  VERB c = VERB::POST;
  VERB d = VERB::PUT;
  VERB e = VERB::DELETE;


  std::cout << a.toString() << std::endl;

  std::cout << a << std::endl;

  if ( a == VERB::GET ) {
    std::cout << "yes" << std::endl;
  }

  if ( a == b ) {
    std::cout << "yes" << std::endl;
  }

  if ( a != c ) {
    std::cout << "no" << std::endl;
  }

}

A classe VERBO:

// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {

private:

  // private constants
  enum Verb {GET_=0, POST_, PUT_, DELETE_};

  // private string values
  static const std::string theStrings[];

  // private value
  const Verb value;
  const std::string text;

  // private constructor
  VERB (Verb v) :
  value(v), text (theStrings[v])
  {
    // std::cout << " constructor \n";
  }

public:

  operator const char * ()  const { return text.c_str(); }

  operator const std::string ()  const { return text; }

  const std::string toString () const { return text; }

  bool operator == (const VERB & other) const { return (*this).value == other.value; }

  bool operator != (const VERB & other) const { return ! ( (*this) == other); }

  // ---

  static const VERB GET;
  static const VERB POST;
  static const VERB PUT;
  static const VERB DELETE;

};

const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};

const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file

11
Para reduzir o uso da memória, você pode substituir o membro const std::string textapenas theStrings[v]. No entanto, a questão é sobre as características de C ++ 11 / C ++ 14 / C ++ 17 / C ++ 20 para evitar ter de escrever tal classe à mão: - /
olibre

0

Minha resposta está aqui.

Você pode obter nomes de valores de enumeração e esses índices simultaneamente como deque de string.

Este método precisa apenas de copiar, colar e editar.

O resultado obtido precisa da conversão de tamanho de size_t para o tipo de classe enum quando você precisa do valor do tipo de classe enum, mas acho que é uma maneira muito portátil e poderosa de tratar a classe enum.

enum class myenum
{
  one = 0,
  two,
  three,
};

deque<string> ssplit(const string &_src, boost::regex &_re)
{
  boost::sregex_token_iterator it(_src.begin(), _src.end(), _re, -1);
  boost::sregex_token_iterator e;
  deque<string> tokens;
  while (it != e)
    tokens.push_back(*it++);
  return std::move(tokens);
}

int main()
{
  regex re(",");
  deque<string> tokens = ssplit("one,two,three", re);
  for (auto &t : tokens) cout << t << endl;
    getchar();
  return 0;
}

0

Você pode usar uma biblioteca de reflexão, como Ponder :

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

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

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

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

0

(Análogo de https://stackoverflow.com/a/54967187/2338477 , ligeiramente modificado).

Aqui está minha própria solução, com o mínimo de definição de mágica e suporte para atribuições individuais de enum.

Aqui está o arquivo de cabeçalho:

#pragma once
#include <string>
#include <map>
#include <regex>

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

//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
    static std::map<std::string, int> enum2int;
    static std::map<int, std::string> int2enum;

    static void EnsureEnumMapReady( const char* enumsInfo )
    {
        if (*enumsInfo == 0 || enum2int.size() != 0 )
            return;

        // Should be called once per each enumeration.
        std::string senumsInfo(enumsInfo);
        std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
        std::smatch sm;
        int value = 0;

        for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
        {
            string enumName = sm[1].str();
            string enumValue = sm[2].str();

            if (enumValue.length() != 0)
                value = atoi(enumValue.c_str());

            enum2int[enumName] = value;
            int2enum[value] = enumName;
        }
    }
};

template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;

template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;


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




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // 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...");
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& int2enum = EnumReflect<T>::int2enum;
    auto it = int2enum.find(t);

    if (it == int2enum.end())
        return "";

    return it->second;
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& enum2int = EnumReflect<T>::enum2int;
    auto it = enum2int.find(enumName);

    if (it == enum2int.end())
        return false;

    t = (T) it->second;
    return true;
}

E aqui está um exemplo de aplicativo de teste:

DECLARE_ENUM(TestEnum,
    ValueOne,
    ValueTwo,
    ValueThree = 5,
    ValueFour = 7
);

DECLARE_ENUM(TestEnum2,
    ValueOne2 = -1,
    ValueTwo2,
    ValueThree2 = -4,
    ValueFour2
);

void main(void)
{
    string sName1 = EnumToString(ValueOne);
    string sName2 = EnumToString(ValueTwo);
    string sName3 = EnumToString(ValueThree);
    string sName4 = EnumToString(ValueFour);

    TestEnum t1, t2, t3, t4, t5 = ValueOne;
    bool b1 = StringToEnum(sName1.c_str(), t1);
    bool b2 = StringToEnum(sName2.c_str(), t2);
    bool b3 = StringToEnum(sName3.c_str(), t3);
    bool b4 = StringToEnum(sName4.c_str(), t4);
    bool b5 = StringToEnum("Unknown", t5);

    string sName2_1 = EnumToString(ValueOne2);
    string sName2_2 = EnumToString(ValueTwo2);
    string sName2_3 = EnumToString(ValueThree2);
    string sName2_4 = EnumToString(ValueFour2);

    TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
    bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
    bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
    bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
    bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
    bool b2_5 = StringToEnum("Unknown", t2_5);

A versão atualizada do mesmo arquivo de cabeçalho será mantida aqui:

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


-5

Que tal uma simples sobrecarga de streaming? Você ainda precisa manter o mapeamento se não quiser fazer alguma macro mágica, mas acho que é mais limpa que a sua solução original.

#include <cstdint>  // for std::uint_fast8_t
#include <array>
#include <string>
#include <iostream>

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

std::ostream& operator<<(std::ostream& str, MyEnum type)
{
    switch(type)
    {
    case MyEnum::AAA: str << "AAA"; break;
    case MyEnum::BBB: str << "BBB"; break;
    case MyEnum::CCC: str << "CCC"; break;
    default: break;
    }
    return str;
}

int main()
{
   std::cout << MyEnum::AAA <<'\n';
}

5
1) ele cria ainda mais a duplicação 2) o obriga a usar correntes
Karoly Horvath

6
-1 Desculpe @dau_sama, mas o objetivo de todos esses enum para encadear perguntas recorrentes é evitar a manutenção do mapeamento de enum / string. Se você acha que sua resposta não se encaixa no objetivo, considere excluir a resposta. Boa sorte na sua próxima resposta;) Cheers
olibre

-9

O jeito mais fácil?
Use Ada: Enumeration'Image( Value )faz exatamente o que você deseja. Se você realmente precisa de C ++, tente exportar a função:

Function To_String( Input : Enumeration ) return Interfaces.C.Strings.chars_ptr is
    ( Interfaces.C.Strings.New_String( Enumeration'Image(Input) ) )
    with Export, Convention => C;

4
Como isso responde à pergunta? A pergunta afirma claramente a conversão de um enum para string no C ++ moderno.
Michael Choi

11
@MichaelChoi - Sim, mas também existe a questão de usar a ferramenta adequada para o trabalho: apenas porque o C ++ está completo e, portanto, pode resolver todos os problemas solucionáveis, NÃO significa que a solução é: rápida, sustentável ou eficiente. Usar um idioma que tenha a funcionalidade adequada / desejada e exportá-lo é uma solução válida.
Shark8

3
Na primeira frase da pergunta "esta pergunta é sobre o uso dos novos recursos do C ++". então "[ainda não encontrei] maneira elegante usando os novos recursos do C ++ 11, C ++ 14 ou C ++ 17". O autor estava claramente procurando uma solução em C ++; você deu uma solução no Ada e não respondeu à pergunta. Você está sugerindo incorporar uma dependência totalmente diferente para resolver algo que provavelmente não estava no escopo das perguntas.
Michael Choi
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.