wyliczanie ciągów we współczesnym C ++ 11 / C ++ 14 / C ++ 17 i przyszłym C ++ 20


354

W przeciwieństwie do wszystkich innych podobnych pytań, to pytanie dotyczy korzystania z nowych funkcji C ++.

Po przeczytaniu wielu odpowiedzi nie znalazłem jeszcze:

Przykład

Przykład jest często lepszy niż długie wyjaśnienie.
Możesz skompilować i uruchomić ten fragment kodu na Coliru .
( Dostępny jest także inny poprzedni przykład )

#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';
}

Ograniczenia

  • Proszę, bezwartościowe powielanie innych odpowiedzi lub podstawowego linku .
  • Unikaj nadmiernej odpowiedzi opartej na makrach lub staraj się #definejak najmniej zmniejszać koszty ogólne.
  • Proszę nie ręcznego enum-> stringmapowania.

Miło jest mieć

  • Obsługuje enumwartości zaczynające się od liczby różnej od zera
  • Obsługuje enumwartości ujemne
  • Obsługuje pofragmentowane enumwartości
  • Wsparcie class enum(C ++ 11)
  • Wsparcie class enum : <type>mające dowolne dozwolone <type>(C ++ 11)
  • Konwersje w czasie kompilacji (nie w czasie wykonywania) na ciąg znaków
    lub przynajmniej szybkie wykonanie w czasie wykonywania (np. std::mapNie jest świetnym pomysłem ...)
  • constexpr (C ++ 11, następnie zrelaksowany w C ++ 14/17/20)
  • noexcept (C ++ 11)
  • Przyjazny fragment kodu C ++ 17 / C ++ 20

Jednym z możliwych pomysł może być przy użyciu kompilatora C ++ zdolności do generowania kodu C ++ w czasie kompilacji za pomocą meta-programowanie oparte na sztuczki variadic template classi constexprfunkcje ...


4
(być może tematycznie) spójrz na tego bloga związanego z Qt. woboq.com/blog/reflection-in-cpp-and-qt-moc.html . Opisuje możliwość zastąpienia mocy Qt (kompilator metaobiektu) za pomocą odbicia C ++ (proponowany standard).
ibre5041

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

1
Osobiście rozwiązałem ten problem, wdrażając małą bibliotekę narzędziową preprocesora, która pozwala mi na przeglądanie różnych argumentów makr i wykonywanie funkcji na nich wszystkich. Wartości enum przekazuję jako argumenty makr i automatycznie generuję wyliczenie i tablicę łańcuchów przez preprocesor. Prawdopodobnie możesz to zrobić również przy użyciu Preprocesora wzmocnienia.
Vittorio Romeo

2
czy wszystko musi być rozwiązane w C ++? Tak łatwo jest automatycznie wygenerować kod do reprezentacji ciągu, zaledwie kilka wierszy kodu.
Karoly Horvath

2
„Proszę nie podawać, jeśli to możliwe, odpowiedzi w oparciu o makro C”, chyba że jesteś gotów czekać na C ++ 17, nie ma prawie nic użytecznego i nie jest tak źle deklarować swoje wyliczenia, DEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))chyba że musisz zachować generowanie makr ... i imho umieszczanie takich przypadków w języku jest tylko innym rodzajem hacka zamiast silniejszej hybrydy szablon / makro. Nie powinniśmy dodawać wszystkich użytecznych przypadków użycia makr do języka, aby móc powiedzieć, że makra nie mają już sensu.
PlasmaHH

Odpowiedzi:


42

Biblioteka tylko w nagłówku Magic Enum zapewnia odbicie statyczne dla wyliczeń (do łańcucha, od łańcucha, iteracji) dla 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
};

Aby uzyskać więcej przykładów, sprawdź repozytorium domowe https://github.com/Neargye/magic_enum .

Gdzie jest wada?

Ta biblioteka używa hacka specyficznego dla kompilatora (na podstawie __PRETTY_FUNCTION__/ __FUNCSIG__), który działa na Clang> = 5, MSVC> = 15.3 i GCC> = 9.

Wartość wyliczenia musi mieścić się w zakresie [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • Domyślnie MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.

  • Jeśli domyślnie potrzebujesz innego zakresu dla wszystkich typów wyliczeń, ponownie zdefiniuj makro MAGIC_ENUM_RANGE_MINi MAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MINmusi być mniejsza lub równa 0i musi być większa niż INT16_MIN.

  • MAGIC_ENUM_RANGE_MAXmusi być większy niż 0i musi być mniejszy niż INT16_MAX.

  • Jeśli potrzebujesz innego zakresu dla określonego typu wyliczenia, dodaj specjalizację enum_range dla wymaganego typu wyliczenia.

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

Dlaczego ograniczenia zasięgu? Czy ma to na celu ograniczenie pewnego rodzaju głębokości rekurencji, czy może z powodu pewnego rodzaju wyszukiwania liniowego w czasie kompilacji?
Emile Cormier

To jest niesamowite. Dziękuję Ci! Prawdopodobnie jest nawet skuteczny, jeśli kompilator jest wystarczająco inteligentny, aby ocenić constexpr std :: array tylko raz. Bardzo bardzo dobrze.
iestyn

87

(Podejście biblioteki better_enums )

Istnieje sposób na wykonanie enum do łańcucha w obecnym C ++, który wygląda następująco:

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

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

Stosowanie:

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

Wszystkie operacje mogą być wykonane constexpr. Możesz także zaimplementować propozycję refleksji C ++ 17 wspomnianą w odpowiedzi przez @ecatmur.

  • Jest tylko jedno makro. Uważam, że jest to minimum możliwe, ponieważ łańcuchowanie preprocesora ( #) jest jedynym sposobem na konwersję tokena na łańcuch w obecnym języku C ++.
  • Makro jest dość dyskretne - ciągłe deklaracje, w tym inicjatory, są wklejane do wbudowanej deklaracji wyliczeniowej. Oznacza to, że mają taką samą składnię i znaczenie, jak we wbudowanym wyliczeniu.
  • Powtórzenie jest wyeliminowane.
  • Implementacja jest najbardziej naturalna i przydatna w co najmniej C ++ 11, ze względu na constexpr. Może być również przystosowany do pracy z C ++ 98 + __VA_ARGS__. Jest to zdecydowanie nowoczesny C ++.

Definicja makra jest nieco związana, więc odpowiadam na to na kilka sposobów.

  • Większość tej odpowiedzi to implementacja, która moim zdaniem jest odpowiednia dla ograniczeń miejsca na StackOverflow.
  • Jest też artykuł CodeProject opisujący podstawy implementacji w długim samouczku. [ Czy powinienem to tutaj przenieść? Myślę, że to za dużo jak na odpowiedź SO ].
  • Istnieje w pełni funkcjonalna biblioteka „Better Enums”, która implementuje makro w jednym pliku nagłówkowym. Implementuje także zapytania o właściwości typu N4428 , aktualną wersję propozycji odbicia C ++ 17 N4113. Tak więc, przynajmniej dla wyliczeń zadeklarowanych przez to makro, możesz teraz mieć proponowane odbicie wyliczenia C ++ 17, w C ++ 11 / C ++ 14.

Rozszerzenie tej odpowiedzi na funkcje biblioteki jest proste - nie pominięto tutaj niczego „ważnego”. Jest to jednak dość nużące i istnieją obawy dotyczące przenośności kompilatora.

Oświadczenie : Jestem autorem zarówno artykułu CodeProject, jak i biblioteki.

Możesz wypróbować kod z tej odpowiedzi , bibliotekę i implementację N4428 na żywo online w Wandbox. Dokumentacja biblioteki zawiera także przegląd tego, jak używać jej jako N4428 , co wyjaśnia część wyliczeniową tej propozycji.


Wyjaśnienie

Poniższy kod implementuje konwersje między wierszami i ciągami znaków. Można go jednak rozszerzyć również na inne czynności, takie jak iteracja. Ta odpowiedź zawiera wyliczenie w struct. Zamiast tego możesz również generować cechy structobok wyliczenia.

Strategia polega na wygenerowaniu czegoś takiego:

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 */ }
};

Problemy są następujące:

  1. Skończymy z czymś w rodzaju {Red = 1, Green, Blue}inicjalizatora tablicy wartości. To nie jest poprawny C ++, ponieważ Rednie jest możliwym do przypisania wyrażeniem. Problem został rozwiązany przez odlewanie co stałe, typu T, który ma zadanie operatora, ale spada przyporządkowania: {(T)Red = 1, (T)Green, (T)Blue}.
  2. Podobnie skończy się {"Red = 1", "Green", "Blue"}jako inicjalizator tablicy nazw. Będziemy musieli odciąć " = 1". Nie znam świetnego sposobu na zrobienie tego w czasie kompilacji, więc odłożymy to na czas wykonywania. W rezultacie _to_stringnie będzie constexpr, ale _from_stringnadal może być constexpr, ponieważ możemy traktować białe znaki i znaki równości jako terminatory w porównaniu z ciągami niepoprawionymi.
  3. Oba powyższe wymagają makra „mapującego”, które może zastosować kolejne makro do każdego elementu w __VA_ARGS__. To jest dość standardowe. Ta odpowiedź zawiera prostą wersję, która może obsłużyć do 8 elementów.
  4. Jeśli makro ma być naprawdę samoistne, musi zadeklarować brak danych statycznych, które wymagają osobnej definicji. W praktyce oznacza to, że tablice wymagają specjalnego traktowania. Istnieją dwa możliwe rozwiązania: constexpr(lub po prostu const) tablice w zakresie przestrzeni nazw lub tablice zwykłe w constexprniestatycznych funkcjach wbudowanych. Kod w tej odpowiedzi dotyczy C ++ 11 i przyjmuje poprzednie podejście. Artykuł CodeProject dotyczy C ++ 98 i bierze to drugie.

Kod

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

i

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

Program powyżej jest drukowany Red, jak można się spodziewać. Istnieje pewien stopień bezpieczeństwa typu, ponieważ nie można utworzyć wyliczenia bez jego zainicjowania, a usunięcie jednego z przypadków z switchspowoduje wygenerowanie ostrzeżenia z kompilatora (w zależności od kompilatora i flag). Zauważ też, że "Red"podczas kompilacji został przekonwertowany na wyliczenie.


Hej @mrhthepie, przepraszam, że twoja edycja została odrzucona. Właśnie zobaczyłem e-maila na ten temat. Zamierzam włączyć to do odpowiedzi - dzięki za poprawkę!
antron

to jest świetne. Czy to też zadziała, jeśli chcę wyliczenie bitów? Jak chcę wyliczyć BitFlagi, każdy z nich jest 1Uprzesunięty o pewną kwotę?
user3240688,

1
Wydaje się, że _trimmed_names()w kodzie, który tu opublikowałeś, jest wyciek pamięci ( new char[length + 1]ale nie ustawiłeś wartości initializedtrue). czy coś mi brakuje? Nie widzę tego samego problemu w twoim kodzie github.
user3240688,

1
Jest ustawiony na true, ale poza ifgałęzią (wyciek pamięci pierwotnie wykryty przez @mrhthepie). Powinien przenieść go do środka ... Edycja. Dzięki za dokładne przyjrzenie się zarówno temu, jak i kodowi GH.
antron

1
to_stringmoże zwrócić a string_viewz C ++ 17, który nie wymaga zakończenia zerowego, i stać się constexpr.
Yakk - Adam Nevraumont

74

W przypadku C ++ 17 C ++ 20 będziesz zainteresowany pracą Reflection Study Group (SG7). Istnieje równoległa seria artykułów obejmujących sformułowania ( P0194 ) oraz uzasadnienie, projekt i ewolucję ( P0385 ). (Linki rozstrzygają do najnowszego artykułu w każdej serii).

Począwszy od P0194r2 (15.10.2016), składnia używałaby proponowanego reflexprsłowa kluczowego:

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

Na przykład (zaadaptowano z gałęzi clang Matusa Choclika ):

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

Refleksja statyczna nie przeszła do C ++ 17 (raczej do prawdopodobnie ostatecznego szkicu zaprezentowanego podczas spotkania standardów w listopadzie 2016 r. W Issaquah), ale istnieje pewność, że przejdzie do C ++ 20; z raportu z podróży Sutta :

W szczególności grupa analityczna Reflection przeanalizowała najnowszą połączoną propozycję statycznego odbicia i stwierdziła, że ​​jest gotowa wejść na główne grupy Evolution na naszym następnym spotkaniu, aby zacząć rozważać ujednoliconą propozycję statycznego odbicia dla TS lub następnego standardu.


2
@antron przepraszam, twoja edycja została odrzucona; Zatwierdziłbym to, gdybym to zobaczył na czas. Nie widziałem N4428, więc dziękuję za oddanie głowy.
ecatmur

3
Nie ma problemu, dziękuję za jego włączenie. Zastanawiam się, dlaczego został odrzucony. Widzę przyczynę „nie czyni dokładniejszej”, ale jest wyraźnie bardziej dokładna na dzień dzisiejszy.
antron

1
Dzięki :-) Podzieliłem ostatni przykład, aby uniknąć poziomego paska przewijania. Jaka szkoda, że ​​wartość MyEnum::AAAnie może być przekazana jako drugi argument std::meta::get_enumerators_m: - /
olibre

1
Fakt, że takie proste koncepcyjnie zadanie wymaga 3 poziomów zagnieżdżonych argumentów szablonów, jest bardzo nadinżynieryjny. Jestem pewien, że istnieją konkretne techniczne powody. Ale to nie znaczy, że efekt końcowy jest przyjazny dla użytkownika. Uwielbiam C ++ i kod ma dla mnie sens. Ale 90% innych programistów, z którymi codziennie pracuję, unika C ++ z powodu takiego kodu. Jestem rozczarowany, że nie widziałem prostszych, bardziej wbudowanych rozwiązań.
void.pointer

2
Wydaje się, że obecne szacunki dotyczące włączenia nadchodzącego Reflection TS do standardu to C ++ 23 : herbutter.com/2018/04/02/…
Tim Rae

25

Jest to podobne do Yuri Finkelsteina; ale nie wymaga wzmocnienia. Korzystam z mapy, więc możesz przypisać dowolną wartość do wyliczeń, w dowolnej kolejności.

Deklaracja klasy enum jako:

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

Poniższy kod automatycznie utworzy klasę enum i przeciążenie:

  • „+” „+ =” dla std :: string
  • „<<” dla strumieni
  • „~” tylko do konwersji na ciąg znaków (zrobi to każdy operator jednoargumentowy, ale ja osobiście nie lubię tego ze względu na przejrzystość)
  • „*”, aby uzyskać liczbę wyliczeń

Doładowanie nie jest wymagane, wszystkie wymagane funkcje są zapewnione.

Kod:

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

Przykład:

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


1
Czy możemy mieć podziały linii w tej definicji makra?
einpoklum,

1
Dodałem przeciążenie, *aby uzyskać liczbę wyliczeń ... Mam nadzieję, że nie masz nic przeciwko :-)
Peter VARGA

1
Czy jest jakiś powód, dla którego ta implementacja używa std::map(indeksowanie O (log (n))) zamiast std::unordered_map(indeksowanie O (1))?
Rzeka Tam

1
myślę też, że metody powinny być oznaczone, inlineaby można było zadeklarować wyliczenia w plikach nagłówkowych jak zwykle bez uzyskiwania błędów „wielokrotnej definicji” z linkera. (nie jestem pewien, czy to rzeczywiście najczystsze / najlepsze rozwiązanie)
River Tam

1
(przepraszam za spam, ale nie mogę dziś edytować komentarzy) istnieją inne problemy z tym, że znajduje się w pliku nagłówkowym. Map ( E##MapName) należy przenieść do jednostki kompilacji, która również ma dostęp do wyliczenia. Stworzyłem rozwiązanie, ale nie jest bardzo czyste i musiałbym uzyskać pozwolenie, aby je udostępnić. Na razie komentuję tylko, żeby powiedzieć, że nie ma sensu oznaczać metod bezpośrednio bez dodatkowych funkcji niezbędnych do obsługi użycia w pliku nagłówkowym.
River Tam

19

W 2011 roku spędziłem weekend na dostrajaniu rozwiązania opartego na makrach i nigdy go nie użyłem .

Moja obecna procedura polega na uruchomieniu Vima, skopiowaniu enumeratorów w pustym ciele przełącznika, uruchomieniu nowego makra, przekształceniu pierwszego enumeratora w instrukcję case, przesunięciu kursora na początek następnego wiersza, zatrzymaniu makra i wygenerowaniu pozostałej wielkości liter instrukcje, uruchamiając makro w innych modułach wyliczających.

Makra Vima są bardziej zabawne niż makra C ++.

Przykład z życia:

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

Stworzę to:

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

I tak sobie radzę.

Natywne wsparcie dla strunowania wyliczeniowego byłoby jednak znacznie lepsze. Jestem bardzo zainteresowany wynikami grupy roboczej refleksji w C ++ 17.

Alternatywny sposób na to zostało opublikowane przez @sehe w komentarzach .


1
Robię dokładnie to. Chociaż zwykle używam Surround vim i wybieram bloki po drodze
patrz

@sehe Interesting. Powinienem rzucić okiem na „surround”, ponieważ obecnie potrzebuję sposobu na wiele naciśnięć klawiszy.
StackedCrooked

Tutaj jest to w pełni Górach żadnych makr (chyba .liczy): i.imgur.com/gY4ZhBE.gif
sehe

1
Animowany gif jest słodki, ale trudno powiedzieć, kiedy zaczyna się i kończy, i jak daleko jesteśmy. ... właściwie, podrap to, to nie jest słodkie, to rozprasza. Mówię zabij to.
einpoklum,

Takie podejście do selekcji bloków w vimie jest fajne, ale dlaczego nie po prostu użyć czegoś takiego :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/?
Ruslan,

14

Nie wiem, czy ci się to spodoba, czy nie, nie jestem zadowolony z tego rozwiązania, ale jest to podejście przyjazne dla C ++ 14, ponieważ używa zmiennych szablonu i nadużywa specjalizacji szablonu:

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;
}

Najgorsze w tym podejściu jest to, że jest to ból do utrzymania, ale jest to również ból do utrzymania niektórych innych podobnych podejść, prawda?

Zalety tego podejścia:

  • Korzystanie ze zmiennych temperatur (funkcja C ++ 14)
  • Dzięki specjalizacji szablonów możemy „wykryć” użycie niepoprawnej wartości (ale nie jestem pewien, czy może to być w ogóle przydatne).
  • Wygląda schludnie.
  • Wyszukiwanie nazw odbywa się w czasie kompilacji.

Live example

Edytować

Tajemniczy użytkownik 673679 masz rację; podejście do szablonu zmiennych C ++ 14 nie obsługuje przypadku środowiska wykonawczego, zapomniałem o tym:

Ale nadal możemy korzystać z niektórych nowoczesnych funkcji C ++ i zmiennych szablonów oraz rozmaitych szablonów, aby uzyskać tłumaczenie środowiska wykonawczego z wartości wyliczeniowej na ciąg znaków ... jest to tak samo uciążliwe jak inne, ale nadal warto o tym wspomnieć.

Zacznijmy od użycia aliasu szablonu, aby skrócić dostęp do mapy wyliczania do łańcucha:

// 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{};

Następnie sztuczka z szablonami variadic:

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 ...);
}

Najlepszą sztuczką ” tutaj jest użycie szablonu zmiennej dla mapy, która zawiera wartości i nazwy każdego wpisu wyliczeniowego; ta mapa będzie taka sama w każdej jednostce tłumaczeniowej i wszędzie będzie miała tę samą nazwę, więc jest całkiem prosta i uporządkowana, jeśli wywołamy taką initializefunkcję:

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

Przypisujemy nazwy do każdego MyEnumwpisu i mogą być używane w środowisku wykonawczym:

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

Ale można to poprawić za pomocą SFINAE i <<operatora przeciążenia :

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

Przy poprawnym operator <<teraz możemy użyć enum w ten sposób:

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

Jest to również uciążliwe w utrzymaniu i można je poprawić, ale mam nadzieję, że wpadniesz na pomysł.

Live example


Wygląda to całkiem fajnie (czy można po prostu nie zdefiniować niespecjalizowanej zmiennej?). Być może coś mi brakuje, ponieważ nie widzę, jak w ogóle obsługuje to środowisko uruchomieniowe.
user673679,

@Paula_plus_plus: Czy nie powinieneś po prostu użyć std::arraynieporęcznej mapy? Stanie się bardziej preferowane dla wyliczeń zaczynających się od ... co, 2 ^ 10 wartości? Może nawet więcej.
einpoklum,

@einpoklum, które byłyby niesamowite, gdybyśmy mogli zapewnić w czasie wykonywania, ile elementów enumma. Niestety nie możemy. A cały punkt na mapie polega na powiązaniu nazw z wartościami, co std::mapjest dobre.
PaperBirdMaster

@Paula_plus_plus: Już wywołujesz initialize()funkcję, której liczba argumentów jest liczbą wartości wyliczanych, więc znasz liczbę wartości w czasie kompilacji. Tylko konkretna wartość, o jaką poproszono Cię o wydruk, jest znana tylko w czasie wykonywania. Ponadto, nawet jeśli nie znasz tej liczby, std :: vector byłby szybszy niż std :: map, znowu, w prawie wszystkich realistycznych przypadkach.
einpoklum

@einpoklum to naprawdę bardzo dobry punkt, pomyślę o tym, dzięki! Jedyne, co mnie martwi, to to, że std::arraynie jest kontenerem klucz-wartość, a zatem brakuje metod wyszukiwania; tak czy inaczej pomyślę.
PaperBirdMaster

7

Jeśli enumtak wygląda

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

Możesz przenieść zawartość enumdo nowego pliku:

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

Następnie wartości mogą być otoczone przez makro:

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

Następnym krokiem może być enumponowne włączenie elementów :

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

I na koniec możesz wygenerować funkcje narzędziowe na ten temat 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");
}

Rozwiązanie może być zastosowane do starszych standardów C ++ i nie wykorzystuje nowoczesnych elementów C ++, ale może być użyte do generowania dużej ilości kodu bez nadmiernego wysiłku i konserwacji.


3
Nie ma potrzeby oddzielnego pliku. Jest to zasadniczo makro-makro .
HolyBlackCat

@HolyBlackCat, jeśli podzielisz rozwiązanie na niektóre pliki, możesz ponownie użyć wartości wyliczeniowych do różnych celów
eferion

Próbuję powiedzieć, że możesz zrobić to samo, jeśli umieścisz listę wartości w jednym makrze obok definicji wyliczenia w nagłówku.
HolyBlackCat

@HolyBlackCat tak rozumiem cię, ale wolę to rozwiązanie. z drugiej strony to rozwiązanie można znaleźć w kodzie źródłowym clang, więc myślę, że to dobry sposób na rozwiązanie problemu
eferion

Słusznie. Chyba nie powinienem był głosować za tym, ponieważ może mieć pewne zastosowania. (Przepraszam za edycję manekina, system blokuje mój głos inaczej.)
HolyBlackCat

6

Miałem ten sam problem kilka dni temu. Nie mogłem znaleźć żadnego rozwiązania C ++ bez jakiejś dziwnej magii makr, więc postanowiłem napisać generator kodu CMake, aby wygenerować proste instrukcje wielkości liter.

Stosowanie:

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

Funkcja przeszukuje pliki włączeń w systemie plików (używa katalogów dołączania dostarczonych z poleceniem include_directories), odczytuje je i wykonuje wyrażenia regularne w celu wygenerowania klasy i funkcji.

UWAGA: constexpr oznacza wbudowane w C ++, więc użycie opcji USE_CONSTEXPR wygeneruje klasę tylko nagłówka!

Przykład:

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

Generuje:

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

Aktualizacja:

Skrypt obsługuje teraz także wyliczenia o zasięgu (klasa enum | struct) i przeniosłem go do osobnego repozytorium z kilkoma innymi skryptami, których często używam: https://github.com/mensinda/cmakeBuildTools


łał! Bardzo oryginalny i innowacyjny pomysł :-) Mam nadzieję, że masz odwagę zaktualizować swój generator, aby zapewnić wersję constexpri noexcept;-) Właśnie
obejrzałem

1
Zaktualizowano generator. Funkcje będą teraz zawsze constexpr i enum: obsługiwany jest teraz <type>. Dzięki za gwiazdę :)
Mense

Link jest zerwany ... -.-
yeoman

Link jest teraz naprawiony.
Mense

4

Po prostu wygeneruj swoje wyliczenia. Napisanie generatora w tym celu zajmuje około pięciu minut.

Kod generatora w Javie i Pythonie, bardzo łatwy do przeniesienia na dowolny język, który lubisz, w tym C ++.

Również bardzo łatwe do rozszerzenia o dowolną funkcjonalność.

przykładowe dane wejściowe:

First = 5
Second
Third = 7
Fourth
Fifth=11

wygenerowany nagłówek:

#include <iosfwd>

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

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

wygenerowany plik CPP

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

I generator, w bardzo zwięzłej formie jako szablon do przenoszenia i rozszerzania. Ten przykładowy kod naprawdę próbuje uniknąć nadpisywania plików, ale nadal używa go na własne ryzyko.

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

I port do Pythona 3.5, ponieważ jest na tyle inny, że może być potencjalnie pomocny

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()

1
Dziękuję bardzo za udostępnienie generatora w dwóch językach :-) Ale czy masz jakiś pomysł, jak generować w czasie kompilacji? Czy na przykład możemy sobie wyobrazić tłumaczenie generatora za pomocą instrukcji CMake w celu odświeżenia kodu wygenerowanego w C ++ po zmianie danych wejściowych? Moim marzeniem jest zmusić kompilator C ++ do generowania wyliczeń podczas kompilacji za pomocą metaprogramowania ( variadic template classi constexprfunkcji).
olibre

Oto, w przypadku, gdy jest zbyt kłopotliwe, aby dodać niestandardowe polecenie cmake, możesz zautomatyzować swoje IDE lub ręcznie wywołać gererator i mieć kontrolę nad danymi wyjściowymi. Czasami dobrym pomysłem jest wygenerowanie kodu w kontroli źródła, pod warunkiem, że nie jest to zbyt wiele, a ludzie rozumieją, że nie powinni dokonywać ręcznych zmian, ponieważ czasami interesujące jest spojrzenie na historię wygenerowanych plików, gdy debuguję coś dziwnego i podejrzewam, że ostatnia zmiana w generatorze mogła coś
zepsuć

Generowanie rzeczy w czasie kompilacji jest tak łatwe w LISP, ponieważ składnia jest niezwykle czysta i łatwa. Pomaga temu fakt, że jest on dynamicznie wpisywany, dzięki czemu jest zwięzły i czytelny bez dużej składni. Odpowiednik makr LISP w C ++ wymagałby bardzo skomplikowanego sposobu opisania AST tego, co próbujesz wygenerować. A AST dla C ++ nigdy nie jest ładny :(
yeoman

Bezpośrednio w Make zamiast cmake, jest to bardzo łatwe btw. Po prostu wygeneruj cele .h i .cpp dla każdego pliku .enum za pomocą funkcji znajdź, a te cele zależą od wymienionych definicji enum, więc są one automatycznie generowane ponownie po zmianie plików .enum def. Prawdopodobnie jest o wiele łatwiejszy w cmake, ponieważ jest pełen magii do tego rodzaju rzeczy, ale regularnie używam Marka, mrówka i gradle, ale mam tylko ograniczoną wiedzę o Maven, cmake i chrząknięciu :)
yeoman

Dziękuję za odpowiedź :-) Myślę, że większość programistów C ++ doceni, czy Twój generator może wykrywać wyliczenia bezpośrednio w kodzie C ++, jak enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};lub w kilku wierszach :-D Czy uważasz, że możesz dostosować swój generator, aby wykryć enumwewnątrz C ++ plik? Najlepszym rozwiązaniem może być wygenerowanie kodu tylko po wykryciu takiego tagu /*<Generate enum to string here>*/. Następnie twój generator zapisuje w miejscu odpowiedni kod wygenerowany w C ++ (zastępując poprzednio wygenerowany kod). ^ _ ^ Co to za niesamowity generator? Pozdrawiam :-)
olibre

3

Zgodnie z prośbą OP, tutaj jest uproszczona wersja brzydkiego rozwiązania makro opartego na Boost Preprosessor i Variadic Macros .

Pozwala na prostą listę, taką jak składnia elementów modułu wyliczającego wraz z ustawieniem wartości dla określonych elementów, aby

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

rozwija się do

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

Wraz z niezbędnymi funkcjami do wyjścia i konwersji z powrotem. To makro istnieje już od wieków i nie jestem całkowicie pewien, czy jest to najbardziej efektywny sposób, czy też zgodny sposób, ale od tego czasu działa

Kompletny kod można zobaczyć w akcji zarówno w Ideone, jak i Coliru .

Jego gigantyczna brzydota jest powyżej; Umieściłbym to za spojlerami, aby chronić twoje oczy, gdybym wiedział, jak, ale markdown mnie nie lubi.

Biblioteka (scalona w jednym pliku nagłówka)

#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)

Stosowanie

#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';
}

Kompilacja (skopiuj nagłówek wklej 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)));
                               ^

Wynik

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

5
Ten blok kodu to szalona podróż przez niesamowite krajobrazy metaprogramowania czarnej magii. Po dotarciu do maindomu poczułem ulgę - Dom, słodki dom!
Quentin,

Właśnie dodałem link do coliru, aby sprawdzić dane wyjściowe (są pewne ostrzeżenia, kliknij link w odpowiedzi). Podzieliłem też na Lib / Usage. Czy rzeczy namespace xxxmożna przenieść do miejsca nagłówka? Możesz wprowadzić we wstępie swoje zastosowanie, boost/preprocessor.hppdlatego odpowiedź jest zgodna z nowoczesnym C ++ . Napraw ostrzeżenia i wyczyść nieco kod źródłowy, aby uzyskać lepszą jakość.
olibre

@olibre: Jest to copypastad z, jak sądzę, 5 różnych nagłówków w naszej bibliotece. Enum_cast pochodzi z innej, bardziej ogólnej części, ale pomyślałem, że mogę go również dodać, aby zobaczyć, do czego służy do_enum_cast w makrze. Ostrzeżenia pochodzą od mojego main<tab>vima, w tym argumentów, których nie używam. Nie sądzę, że ten kod da się naprawdę wyczyścić, to po prostu pokazać, co można zrobić, a nie powinien;) i jeśli go tu zmienię, to nie jest to kod, którego używam już w produkcji ... to jedna z tych delikatnych rzeczy że kiedy już zadziała, lepiej nigdy nie dotykaj, ponieważ może się zawalić w sposób, którego nikt nie był w stanie przewidzieć.
PlasmaHH

Dobra plazma, widzę, że można to uznać za dowód koncepcji . Ale jest zbyt wiele narzutów makro, aby można było je przegłosować. Niemniej jednak dziękuję za udostępnienie. Na zdrowie
olibre

Cześć plazma. Przeprowadziłem głębokie czyszczenie kodu źródłowego + zakończyłem kompilacją i uruchomiłem wyjście. Proszę sprawdzić moją edycję . Mam nadzieję, że to dla ciebie OK. Czy odpowiedź jest cenniejsza? Jednak narzuty makro są nadal okropne! Miłego dnia :-) Pozdrawiam
olibre

2

Poniższe rozwiązanie jest oparte na std::array<std::string,N>danym wyliczeniu.

Dla enumdo std::stringkonwersji możemy po prostu rzucić do wyliczenia size_ti odnośnika ciąg z tablicy. Operacja to O (1) i nie wymaga alokacji sterty.

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

Dla std::stringdo enumkonwersji musielibyśmy zrobić przeszukiwanie liniowe nad tablicy i oddać indeks tablicy do enum.

Wypróbuj tutaj z przykładami użycia: http://coliru.stacked-crooked.com/a/e4212f93bee65076

Edycja: Przerobiłem moje rozwiązanie, aby niestandardowego Enum można było używać w klasie.


Dziękuję za interesującą odpowiedź. Zmodyfikuj swoją propozycję, aby użyć makra w klasie. Zobacz coliru.stacked-crooked.com/a/00d362eba836d04b Ponadto starają się wykorzystywać constexpri noexeptsłowa kluczowe, jeśli to możliwe. Pozdrawiam :-)
olibre

Pytanie nie określało tego wymogu.
FKaria,

Pytanie zaktualizowane (patrz przykład). Dwa inne wymagania: (1) typ obsługi wyliczenia i (2) wartości mogą różnić się od sekwencji 0, 1, 2 ...
olibre

Przerobiłem moje rozwiązanie, aby można je było wykorzystać w klasie. Nie zastanawiałem się jednak, jak odróżnić wartości od 0,1,2.
FKaria,

Cześć FKaria. Dziękuję bardzo za twoją przeróbkę. Wprowadziłem kilka zmian w celu obsługi kilku wyliczeń w tej samej klasie, a także w celu obsługi enum class X : Typeformatu. Sprawdź mój wkład: coliru.stacked-crooked.com/a/b02db9190d3491a3 Co sądzisz o moich zmianach? Czy masz pomysł na poparcie wartości ustawionych w wyliczeniu? Przykład enum E{A=3, B=6, C=A-B};Pozdrowienia
olibre

2

Ta istota zapewnia proste mapowanie oparte na szablonach variadic C ++.

Jest to uproszczona w C ++ wersja mapy opartej na typach z 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);
  }
};

Przykładowe użycie:

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;

map<KeyValues...>Można stosować w obu kierunkach:

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

Ten przykład jest dostępny na godbolt.org

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

Wynik z gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

1
Bardzo interesujący sposób metaprogramowania. Próbowałem nieco uprościć odpowiedź, aby była autonomiczna (bez zależności od linku Gist). Aby być zwięzłym i zrozumiałym, w końcu zredagowałem twoją odpowiedź. Czy nadal zgadzasz się z moimi zmianami? Pozdrawiam ;-)
olibre

2

Ten problem frustruje mnie od dawna, podobnie jak problem z konwersją typu na ciąg znaków w odpowiedni sposób. Jednak w przypadku ostatniego problemu byłem zaskoczony rozwiązaniem wyjaśnionym w Czy można wydrukować typ zmiennej w standardowym C ++? , korzystając z pomysłu z Czy mogę uzyskać nazwy typu C ++ w sposób constexpr? . Korzystając z tej techniki, można skonstruować analogiczną funkcję do uzyskania wartości wyliczeniowej jako łańcucha:

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

Powyższy kod został przetestowany tylko na Clang (patrz https://ideone.com/je5Quv ) i VS2015, ale powinien być dostosowywany do innych kompilatorów poprzez błądzenie się ze stałymi liczbami całkowitymi. Oczywiście nadal używa makr pod maską, ale przynajmniej jeden nie potrzebuje dostępu do implementacji wyliczania.


Nie udaje się to w przypadku g ++ 6.3.0 i C ++ 14.
einpoklum

Interesujące, ponieważ wyliczenie można zadeklarować normalnie bez konieczności zawijania go w makrze. Chociaż nie lubię zależności kompilatora i stałych magicznych.
zett42

2

Wziąłem pomysł z @antron i zaimplementowałem go inaczej: generowanie prawdziwej klasy enum .

Ta implementacja spełnia wszystkie wymagania wymienione w pierwotnym pytaniu, ale obecnie ma tylko jedno rzeczywiste ograniczenie : zakłada, że ​​wartości wyliczenia albo nie są podane, albo, jeśli są podane, muszą zaczynać się od 0 i iść w górę bez przerw.

To nie jest wewnętrzne ograniczenie - po prostu nie używam wartości wyliczenia ad-hoc. Jeśli jest to potrzebne, można zastąpić wyszukiwanie wektorowe tradycyjną implementacją przełącznika / skrzynki.

Rozwiązanie wykorzystuje trochę c ++ 17 dla zmiennych wbudowanych, ale w razie potrzeby można tego łatwo uniknąć. Wykorzystuje również boost: trim ze względu na prostotę.

Co najważniejsze, zajmuje tylko 30 linii kodu i żadnych makr czarnej magii. Kod znajduje się poniżej. Jest przeznaczony do umieszczenia w nagłówku i dołączenia do wielu modułów kompilacji.

Można go używać w taki sam sposób, jak zasugerowano wcześniej w tym wątku:

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

Proszę, daj mi znać, czy jest to przydatne i jak można je dalej ulepszyć.


#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

Dopóki nie masz .h/.cppnic przeciwko pisaniu osobnej pary dla każdego wyliczalnego zapytania, to rozwiązanie działa z prawie taką samą składnią i możliwościami jak zwykłe wyliczenie c ++:

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

.cppPlik jest 3 linie boilerplate:

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

Przykładowe użycie:

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

Kod

To rozwiązanie wymaga 2 plików źródłowych:

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

...i

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

Wyjaśnienie

Ta implementacja wykorzystuje fakt, że lista stężeń elementów definicji wyliczenia może być również wykorzystana jako lista inicjatorów stężeń do inicjowania elementów klasy.

Gdy ETRAITSjest oceniany w kontekście EnumTraits.inl, rozwija się do statycznej definicji elementu dlaEnumTraits<> klasy.

W EDECLmakro przekształca każdy członek enum język initializer lista wartości, które następnie przejdzie do konstruktora zarejestrował się w celu wypełnienia informacji enum.

EnumInitGuardKlasa jest przeznaczony do konsumpcji enum initializer wartości, a następnie zwinąć - pozostawiając czystą listę danych enum.

Korzyści

  • c++podobna do składni
  • Działa identycznie dla obu enumi enum class(* prawie)
  • Działa dla enumtypów z dowolnym typem liczbowym
  • Działa dla enumtypów z automatycznymi, jawnymi i pofragmentowanymi wartościami inicjalizującymi
  • Działa w przypadku masowej zmiany nazwy (zachowane łączenie intellisense)
  • Tylko 5 symboli preprocesora (3 globalne)

* W przeciwieństwie do enumsinicjatorów w enum classtypach, które odwołują się do innych wartości z tego samego wyliczenia, muszą mieć te wartości w pełni kwalifikowane

Wady

  • Wymaga osobnej .h/.cpppary dla każdego zapytaniaenum
  • Zależy od zawiłości macroi includemagii
  • Drobne błędy składniowe eksplodują w znacznie większe błędy
  • Definiowanie classlub określanie namespacezakresów nie jest łatwe
  • Brak inicjalizacji czasu kompilacji

Komentarze

Intellisense będzie narzekać trochę na dostęp prywatnych członków podczas otwierania EnumTraits.inl, ale ponieważ rozszerzone makra w rzeczywistości definiują członków klasy, nie jest to tak naprawdę problemem.

#ifndef ENUM_INCLUDE_MULTIBloku na początku pliku nagłówka jest kłopot moll, które mogłyby prawdopodobnie skurczony dół do makro lub czegoś, ale jest na tyle mała, aby żyć w swojej obecnej wielkości.

Zadeklarowanie wyliczenia o zakresie nazw wymaga, aby wyliczenie najpierw zostało zadeklarowane w ramach zakresu przestrzeni nazw, a następnie zdefiniowane w globalnej przestrzeni nazw. Ponadto wszelkie inicjatory wyliczeń wykorzystujące wartości tego samego wyliczenia muszą mieć te wartości w pełni kwalifikowane.

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

Nie jestem pewien, czy to podejście jest już uwzględnione w jednej z pozostałych odpowiedzi (tak naprawdę jest, patrz poniżej). Problem napotkałem wiele razy i nie znalazłem rozwiązania, które nie używałoby zaciemnionych makr ani bibliotek stron trzecich. Dlatego postanowiłem napisać własną zaciemnioną wersję makro.

To, co chcę włączyć, to odpowiednik

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
}

który powinien wydrukować

ONE
TWO
13

Nie jestem fanem makr. Jednakże, chyba że c ++ natywnie obsługuje konwersję wyliczeń na ciągi, należy użyć jakiegoś generowania kodu i / lub makr (i wątpię, aby stało się to zbyt wcześnie). Używam makra 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

Większość z nich to definiowanie i niezdefiniowanie symboli, które użytkownik przekaże jako parametr do X-marco poprzez dołączenie. Użycie jest takie

#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"

Demo na żywo

Zauważ, że nie uwzględniłem jeszcze wyboru typu podstawowego. Do tej pory nie potrzebowałem tego, ale modyfikacja kodu w celu włączenia tego powinna być prosta.

Dopiero po napisaniu tego zdałem sobie sprawę, że jest to raczej podobne do odpowiedzi eferions . Może przeczytałem to wcześniej i może było to główne źródło inspiracji. Zawsze nie rozumiałem makr X, dopóki nie napisałem własnych;).


1

Rozwiązania wykorzystujące wyliczanie w klasie / struct (domyślne struktury w elementach publicznych) i przeciążone operatory:

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";
        }
    }
};

Z zewnątrz wygląda prawie dokładnie jak wyliczanka klasowa:

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

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

Spowoduje to wyświetlenie „czerwonego 1 2”. Możesz przeciążać <<, aby niebieski wyjściowy napis był ciągiem (chociaż może to powodować niejednoznaczność, więc niemożliwe), ale nie działałoby to z Color :: GREEN, ponieważ nie konwertuje się automatycznie na Color.

Celem niejawnej konwersji na Enum (która domyślnie konwertuje na dane int lub type) jest możliwość:

Color color;
switch (color) ...

To działa, ale oznacza to również, że to działa:

int i = color;

Z klasą enum nie skompiluje się. Należy zachować ostrożność, jeśli przeciążymy dwie funkcje, przyjmując wyliczenie i liczbę całkowitą, lub usuwamy niejawną konwersję ...

Inne rozwiązanie wymagałoby użycia rzeczywistej klasy wyliczeniowej i elementów statycznych:

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

    //same as previous...
};

Prawdopodobnie zajmuje więcej miejsca i jest dłużej wykonywany, ale powoduje błąd kompilacji dla niejawnych konwersji int. Użyłbym tego z tego powodu!

Z pewnością jest to jednak narzut, ale myślę, że jest to po prostu prostsze i wygląda lepiej niż inny kod, który widziałem. Istnieje również możliwość dodania funkcjonalności, którą można by objąć zakresem w obrębie klasy.

Edycja : działa i większość można skompilować przed wykonaniem:

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;
};

To bardzo interesujące :-) Jednak twoja obecna wersja wymaga ręcznego pisania case Enum::RED: return "red";. Pytanie dotyczy zautomatyzowania tych rzeczy przez kompilator (w czasie kompilacji). Ideą tego pytania jest zmiana lub dodanie wartości wyliczeniowych bez konieczności aktualizacji toString(). Czy ty widzisz? Dzięki
olibre

1

Bardzo proste rozwiązanie z jednym dużym ograniczeniem: nie możesz przypisać wartości niestandardowych do enumwartości, ale przy odpowiednim wyrażeniu regularnym możesz. możesz również dodać mapę, aby przełożyć je z powrotem na enumwartości bez większego wysiłku:

#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__)

Przykład użycia:

EnumToString(MyEnum, Red, Green, Blue);

Dziękuję Malem za twój innowacyjny pomysł. Zredagowałem twoją odpowiedź, aby poprawić czytelność. Mam nadzieję, że podoba Ci się moje zmiany. Kontynuuj ulepszanie swojej odpowiedzi: (1) poszerz sekcję „Przykład użycia” o coś w stylu auto name = MyEnumStrings["Red"];- (2) Dlaczego używasz enum class? - (3) Czy wspierasz enum class MyEnum : char { Red, Green, Blue };? - (4) Funkcja wyjaśniania split()- (5) Potrzebujesz parametru const std::regex& delim? - (6) A co z generowaniem MyEnumStringsw czasie kompilacji? => Czy możesz użyć constexpr? ... Pozdrawiam :-)
olibre

Naprawdę podoba mi się to podejście. Naprawdę krótki i łatwy do zrozumienia.
Anton Holmberg

1

EDYCJA: sprawdź poniżej, czy jest nowsza wersja

Jak wspomniano powyżej, N4113 jest ostatecznym rozwiązaniem tej kwestii, ale będziemy musieli poczekać ponad rok, aby się ukazać .

Tymczasem, jeśli chcesz taką funkcję, musisz skorzystać z „prostych” szablonów i trochę magii preprocesora.

Moduł wyliczający

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__);

Stosowanie

#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
}

Proste wyjaśnienie

Enum<T>::m_counterjest ustawiony na 0 w każdej deklaracji przestrzeni nazw.
( Czy ktoś mógłby mi wskazać, gdzie ^^ to zachowanie ^^ jest wspomniane w standardzie? )
Magia preprocesora automatyzuje deklarację liczników.

Niedogodności

  • To nie jest prawdziwy enumtyp, dlatego nie można go promować na int
  • Nie można używać w skrzynkach przełączników

Alternatywne rozwiązanie

Ten poświęca numerację linii (nie tak naprawdę), ale może być stosowany w obudowach przełączników .

#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 0 koliduje z -pedantic GCC i clang.

Obejście

Zacznij od #line 1i odejmij 1 od __LINE__.
Lub nie używaj -pedantic.
I chociaż jesteśmy przy tym, unikaj VC ++ za wszelką cenę, to zawsze był żart kompilatora.

Stosowanie

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

Rzeczywiste wdrożenie i użytkowanie

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Krótki przegląd

#line lineno - cppreference.com


0

Napisałem bibliotekę do rozwiązania tego problemu, wszystko dzieje się w czasie kompilacji, z wyjątkiem otrzymania wiadomości.

Stosowanie:

Użyj makra, DEF_MSGaby zdefiniować makro i parę komunikatów:

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

CODE_OKto makro, którego należy użyć, i "OK!"odpowiedni komunikat.

Użyj get_message()lub po prostu, gm()aby otrzymać wiadomość:

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

Użyj, MSG_NUMaby dowiedzieć się, ile makr zostało zdefiniowanych. To automatycznie zwiększy, nie musisz nic robić.

Predefiniowane wiadomości:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Projekt: libcodemsg


Biblioteka nie tworzy dodatkowych danych. Wszystko dzieje się w czasie kompilacji. W message_def.hgeneruje enumwywołanie MSG_CODE; w message_def.c, generuje zmienną, która przechowuje wszystkie ciągi w static const char* _g_messages[].

W takim przypadku biblioteka jest ograniczona do utworzenia enumtylko jednej . Jest to idealne rozwiązanie dla zwracanych wartości, na przykład:

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

MSG_CODE ret = foo();

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

Inną rzeczą, która podoba mi się w tym projekcie jest to, że możesz zarządzać definicjami wiadomości w różnych plikach.


Odkryłem, że rozwiązanie tego pytania wygląda o wiele lepiej.


Cześć Madwyn. Dziękuję za twój pomysł. Ale jak to działa? Co to jest narzut? (zero narzutu czy tworzy dodatkowe dane?). Twoja propozycja wydaje się w porządku, ale niestety jedno oświadczenie DEF_MSGmusi zostać użyte / zaktualizowane / utrzymane dla każdej enumwartości: - / I to właśnie idealnie chcielibyśmy przestać robić ... Pozdrowienia
olibre

Dziękuję za odpowiedź, @olibre. Sprawdź zaktualizowaną odpowiedź. Nie widzę tutaj narzutów, z wyjątkiem tego, że do uzyskania dostępu do ciągów potrzebne jest wywołanie funkcji. DEF_MSGsprawia, że ​​jest enumściśle powiązany z wiadomością, chociaż ma pewne ograniczenia.
Madwyn

Dziękujemy za dołączone wyjaśnienie w odpowiedzi :-) Twoja biblioteka jest w porządku, ale nie można jej używać do wielu wyliczeń: - / A co ze wsparciem enum class(C ++ 11) ? Możesz użyć, constexpraby ograniczyć _g_messagesw czasie wykonywania. Obsługa wielu enumtypów (unikanie _g_messages) przy użyciu metaprogramowania (przenoszenie typu {typ-enum, wartość-enum}) lub może zmienne szablonów (C ++ 14) . Myślę, że twoja biblioteka nie spełnia (jeszcze?) Wymagań C ++ 11/14/17. Co myślisz? Pozdrawiam ;-)
olibre

1
Dzięki za kontynuację. Nauczyłem się dzisiaj czegoś nowego! Klasa enum i zmienne szablonów wyglądają dobrze. Myślę, że moja odpowiedź była trochę „nie na temat”, ponieważ miała smak 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;
    }

przykład

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);

automatycznie ENUM_MAKE makro generuje „klasę enum” i klasę pomocnika z „funkcją odbicia enum”.

Aby zmniejszyć liczbę błędów, od razu wszystko jest definiowane tylko jednym ENUM_MAKE.

Zaleta tego kodu jest tworzona automatycznie w celu refleksji i dokładnego przyjrzenia się kodowi makr, łatwemu do zrozumienia kodowi. „enum to string”, „string to enum” performance oba są algorytmem O (1).

Wady polegają na tym, że przy pierwszym użyciu inicjowana jest klasa pomocnicza wektora ciągowego i odwzorowania enum. ale jeśli chcesz, zostaniesz również wstępnie zainicjowany. -


Chociaż ten kod może odpowiedzieć na pytanie, lepiej wyjaśnić, w jaki sposób rozwiązuje problem bez przedstawiania innych i dlaczego go używać. Odpowiedzi zawierające tylko kod nie są przydatne na dłuższą metę.
JAL,

hej, przepraszam, nie mówię zbyt dobrze po angielsku.
desperado_98,

automatycznie ENUM_MAKE makro generuje „klasę enum” i klasę pomocnika z „funkcją odbicia enum”. / Aby zmniejszyć liczbę błędów, od razu wszystko jest definiowane tylko jednym ENUM_MAKE. Zaleta tego kodu jest tworzona automatycznie w celu refleksji i dokładnego przyjrzenia się kodowi makr, łatwemu do zrozumienia kodowi. „enum to string”, „string to enum” performance oba są algorytmem O (1). Wady polegają na tym, że przy pierwszym użyciu inicjowana jest klasa pomocnicza wektora ciągowego i odwzorowania enum. ale jeśli chcesz, zostaniesz również wstępnie zainicjowany.
desperado_98,

Cześć desperado_98. Dziękuję za twój wkład. Edytuj swoją odpowiedź i wstaw do niej treść komentarza. Kompilator może obliczyć twój przykład w czasie kompilacji, jeśli użyjesz sztuczek z meta-programowania i constexpr. Mam na myśli funkcje toName()i toType()można je oceniać podczas kompilacji, a nie podczas wykonywania (w czasie wykonywania). Proszę przyjąć styl C ++ 11/14/17 w swojej odpowiedzi. Pozdrawiam ;-)
olibre

Co więcej: Czy twoje makro jest kompatybilne enum class MyEnum : short { A, B, C };?
olibre

0

moje rozwiązanie nie korzysta z makr.

Zalety:

  • widzisz dokładnie to, co robisz
  • dostęp jest z mapami skrótu, więc jest dobry dla wielu cennych wyliczeń
  • nie trzeba brać pod uwagę kolejności ani niesekwencyjnych wartości
  • zarówno wyliczanie na ciąg, jak i tłumaczenie na wyliczanie, natomiast wartość dodaną wyliczania należy dodawać tylko w jednym dodatkowym miejscu

niedogodności:

  • musisz zreplikować wszystkie wartości wyliczeń jako tekst
  • dostęp w mapie skrótów musi uwzględniać wielkość liter
  • konserwacja, jeśli dodawanie wartości jest bolesne - należy dodać zarówno mapę wyliczeniową, jak i bezpośrednią translację

więc ... do dnia, w którym C ++ wdroży funkcjonalność C # Enum.Parse, utknę z tym:

            #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

Cóż, jeszcze jedna opcja. Typowy przypadek użycia polega na tym, że potrzebujesz stałych czasowników HTTP, a także używania wartości wersji ciągu.

Przykład:

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;
  }

}

Klasa VERB:

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

1
Aby zmniejszyć zużycie pamięci, możesz zastąpić członka const std::string texttylko przez theStrings[v]. Pytanie dotyczy jednak funkcji z C ++ 11 / C ++ 14 / C ++ 17 / C ++ 20, aby uniknąć konieczności ręcznego pisania takich klas: - /
olibre

0

Moja odpowiedź jest tutaj.

Możesz uzyskać nazwy wartości wyliczonych i te indeksy jednocześnie jako deque ciągu znaków.

Ta metoda wymaga tylko niewielkiej ilości kopiowania, wklejania i edycji.

Uzyskany wynik wymaga rzutowania typu z size_t na typ klasy enum, gdy potrzebujesz wartości typu klasy enum, ale myślę, że jest to bardzo przenośny i potężny sposób traktowania klasy 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

Możesz użyć biblioteki refleksji, takiej jak 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

(Analogicznie z https://stackoverflow.com/a/54967187/2338477 , nieco zmodyfikowany).

Oto moje własne rozwiązanie z minimalną magią definiowania i wsparciem dla poszczególnych zadań enum.

Oto plik nagłówka:

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

A oto przykładowa aplikacja testowa:

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);

Zaktualizowana wersja tego samego pliku nagłówka zostanie zachowana tutaj:

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


-5

Co z prostym przeciążeniem strumieniowym? Nadal musisz zachować mapowanie, jeśli nie chcesz wykonywać magii makro, ale uważam, że jest czystsze niż twoje oryginalne rozwiązanie.

#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) tworzy jeszcze większą duplikację 2) zmusza cię do korzystania ze strumieni
Karoly Horvath

6
-1 Przepraszam @dau_sama, ale celem tych wszystkich wyliczeń do ciągów powtarzających się pytań jest uniknięcie utrzymywania odwzorowania wyliczania / ciągów. Jeśli uważasz, że twoja odpowiedź nie pasuje do celu, rozważ usunięcie odpowiedzi. Powodzenia przy kolejnej odpowiedzi;) Pozdrawiam
olibre

-9

Najłatwiejszy sposób?
Użyj Ada: Enumeration'Image( Value )robi dokładnie to, co chcesz. Jeśli naprawdę potrzebujesz C ++, możesz spróbować wyeksportować funkcję:

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
Jak to w ogóle odpowiada na pytanie? Pytanie wyraźnie stwierdza konwersję wyliczenia na ciąg znaków we współczesnym języku C ++.
Michael Choi,

1
@MichaelChoi - Tak, ale jest też problem z użyciem odpowiedniego narzędzia do zadania: tylko dlatego, że C ++ jest gotowy do ukończenia i dlatego może rozwiązać wszystkie możliwe do rozwiązania problemy, NIE oznacza, że ​​rozwiązanie jest: szybkie, łatwe do utrzymania lub wydajne. Używanie języka o odpowiedniej / pożądanej funkcjonalności i eksportowanie go jest prawidłowym rozwiązaniem.
Shark8

3
W pierwszym zdaniu pytania „to pytanie dotyczy korzystania z nowych funkcji C ++”. potem „[jeszcze nie znalazłem] eleganckiego sposobu przy użyciu nowych funkcji C ++ 11, C ++ 14 lub C ++ 17”. Autor wyraźnie szukał rozwiązania C ++; dałeś rozwiązanie w Adzie, więc nie odpowiedziałeś na pytanie. Sugerujesz włączenie zupełnie innej zależności, aby rozwiązać coś, co prawdopodobnie nie było objęte zakresem pytań.
Michael Choi
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.