Aufzählung zum String in modernem C ++ 11 / C ++ 14 / C ++ 17 und zukünftigem C ++ 20


354

Im Gegensatz zu allen anderen ähnlichen Fragen geht es bei dieser Frage um die Verwendung der neuen C ++ - Funktionen.

Nachdem ich viele Antworten gelesen hatte, fand ich noch keine:

Beispiel

Ein Beispiel ist oft besser als eine lange Erklärung.
Sie können dieses Snippet auf Coliru kompilieren und ausführen .
( Ein weiteres früheres Beispiel ist ebenfalls verfügbar)

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

Einschränkungen

  • Bitte keine wertlose Vervielfältigung anderer Antworten oder grundlegender Links .
  • Vermeiden Sie es, auf Makros basierende Antworten aufzublähen, oder versuchen Sie, den #defineOverhead so gering wie möglich zu halten.
  • Bitte kein Handbuch enum-> stringMapping.

Schön zu haben

  • Unterstützungswerte enumab einer anderen Zahl als Null
  • Unterstützen Sie negative enumWerte
  • Unterstützt fragmentierte enumWerte
  • Unterstützung class enum(C ++ 11)
  • Unterstützung class enum : <type>mit allen erlaubten <type>(C ++ 11)
  • Konvertierungen zur Kompilierungszeit (nicht zur Laufzeit) in eine Zeichenfolge
    oder zumindest schnelle Ausführung zur Laufzeit (z. B. std::mapkeine gute Idee ...)
  • constexpr (C ++ 11, dann entspannt in C ++ 14/17/20)
  • noexcept (C ++ 11)
  • C ++ 17 / C ++ 20 freundliches Snippet

Eine mögliche Idee könnte darin bestehen, die C ++ - Compilerfunktionen zu verwenden, um C ++ - Code zur Kompilierungszeit mithilfe von Meta-Programmier-Tricks zu generieren, die auf variadic template classund constexprFunktionen basieren ...


4
(Vielleicht vom Thema) Schauen Sie sich diesen Qt-bezogenen Blog an. Woboq.com/blog/reflection-in-cpp-and-qt-moc.html . Beschreibt eine Möglichkeit, Qts moc (Meta-Objekt-Compiler) durch C ++ - Reflektion (vorgeschlagener Standard) zu ersetzen.
ibre5041

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

1
Ich persönlich habe dieses Problem gelöst, indem ich eine kleine Präprozessor-Dienstprogrammbibliothek implementiert habe, mit der ich verschiedene Makroargumente durchlaufen und eine Funktion über alle ausführen kann. Ich übergebe die Aufzählungswerte als Makroargumente und generiere die Aufzählung und das String-Array automatisch über den Präprozessor. Sie können dies wahrscheinlich auch mit dem Boost-Präprozessor tun.
Vittorio Romeo

2
muss alles mit C ++ gelöst werden? Es ist so einfach, automatisch Code für die Zeichenfolgendarstellung zu generieren, nur ein paar Codezeilen.
Karoly Horvath

2
"Bitte geben Sie nach Möglichkeit keine C-Makro-basierten Antworten an" Nun , es sei denn, Sie sind bereit, auf C ++ 17 zu warten, es gibt kaum etwas Verwendbares , und es ist nicht so schlimm, Ihre Aufzählungen zu deklarieren , es sei DEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))denn, Sie müssen die pflegen Das Generieren von Makros ... und das Einfügen solcher Fälle in die Sprache ist nur eine andere Art von Hack anstelle eines leistungsstärkeren Template / Makro-Hybrids. Wir sollten nicht all diese nützlichen Verwendungszwecke von Makros in die Sprache einfügen, nur um sagen zu können, dass Makros keine Verwendung mehr haben.
PlasmaHH

Antworten:


43

Die Nur-Header-Bibliothek von Magic Enum bietet statische Reflexion für Aufzählungen (zu Zeichenfolge, von Zeichenfolge, Iteration) für 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
};

Weitere Beispiele finden Sie im Home-Repository unter https://github.com/Neargye/magic_enum .

Wo ist der Nachteil?

Diese Bibliothek verwendet einen compilerspezifischen Hack (basierend auf __PRETTY_FUNCTION__/ __FUNCSIG__), der mit Clang> = 5, MSVC> = 15.3 und GCC> = 9 funktioniert.

Der Aufzählungswert muss im Bereich liegen [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • Standardmäßig MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.

  • Wenn Sie standardmäßig einen anderen Bereich für alle Aufzählungstypen benötigen, definieren Sie das Makro MAGIC_ENUM_RANGE_MINund neu MAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MINmuss kleiner oder gleich sein als 0und muss größer sein als INT16_MIN.

  • MAGIC_ENUM_RANGE_MAXmuss größer sein als 0und muss kleiner sein als INT16_MAX.

  • Wenn Sie einen anderen Bereich für einen bestimmten Aufzählungstyp benötigen, fügen Sie die Spezialisierung enum_range für den erforderlichen Aufzählungstyp hinzu.

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

Warum die Reichweitengrenzen? Ist es eine Art Rekursionstiefe zu begrenzen oder eine Art lineare Suche zur Kompilierungszeit?
Emile Cormier

Das ist großartig. Vielen Dank! Es ist wahrscheinlich sogar effizient, wenn der Compiler intelligent genug ist, um das constexpr std :: array nur einmal auszuwerten. Sehr sehr nett.
Iestyn

87

(Der Ansatz der Better_enums- Bibliothek)

Es gibt eine Möglichkeit, in aktuellem C ++ eine Aufzählung von Zeichenfolgen durchzuführen, die folgendermaßen aussieht:

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

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

Verwendungszweck:

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

Alle Operationen können durchgeführt werden constexpr. Sie können auch den in der Antwort von @ecatmur erwähnten C ++ 17-Reflexionsvorschlag implementieren.

  • Es gibt nur ein Makro. Ich glaube, dies ist das Minimum, da die Präprozessor-Stringisierung ( #) die einzige Möglichkeit ist, ein Token in das aktuelle C ++ in einen String zu konvertieren.
  • Das Makro ist ziemlich unauffällig - die konstanten Deklarationen, einschließlich der Initialisierer, werden in eine integrierte Enum-Deklaration eingefügt. Dies bedeutet, dass sie dieselbe Syntax und Bedeutung haben wie in einer integrierten Enumeration.
  • Wiederholung entfällt.
  • Die Implementierung ist aufgrund von mindestens C ++ 11 am natürlichsten und nützlichsten constexpr. Es kann auch für C ++ 98 + verwendet werden __VA_ARGS__. Es ist definitiv modernes C ++.

Die Definition des Makros ist etwas kompliziert, daher beantworte ich dies auf verschiedene Arten.

  • Der Großteil dieser Antwort ist eine Implementierung, die meiner Meinung nach für die Platzbeschränkungen in StackOverflow geeignet ist.
  • Es gibt auch einen CodeProject-Artikel , der die Grundlagen der Implementierung in einem ausführlichen Lernprogramm beschreibt. [ Soll ich es hierher bringen? Ich denke, es ist zu viel für eine SO-Antwort .
  • Es gibt eine Bibliothek mit vollem Funktionsumfang "Better Enums" , die das Makro in einer einzelnen Header-Datei implementiert. Außerdem werden N4428 Type Property Queries implementiert , die aktuelle Version des C ++ 17-Reflexionsvorschlags N4113. Zumindest für Enums, die über dieses Makro deklariert wurden, können Sie jetzt die vorgeschlagene C ++ 17-Enum-Reflektion in C ++ 11 / C ++ 14 verwenden.

Es ist einfach, diese Antwort auf die Funktionen der Bibliothek auszudehnen - hier wird nichts "Wichtiges" ausgelassen. Es ist jedoch ziemlich langwierig und es gibt Bedenken hinsichtlich der Portabilität des Compilers.

Haftungsausschluss : Ich bin Autor sowohl des CodeProject-Artikels als auch der Bibliothek.

Sie können den Code in dieser Antwort , die Bibliothek und die Implementierung von N4428 live online in Wandbox ausprobieren. Die Bibliotheksdokumentation enthält auch eine Übersicht über die Verwendung als N4428 , in der der Aufzählungsteil dieses Vorschlags erläutert wird.


Erläuterung

Der folgende Code implementiert Konvertierungen zwischen Aufzählungen und Zeichenfolgen. Es kann jedoch auch auf andere Aufgaben erweitert werden, z. B. auf die Iteration. Diese Antwort umschließt eine Aufzählung in a struct. Sie können structstattdessen auch Merkmale neben einer Aufzählung generieren .

Die Strategie besteht darin, so etwas zu generieren:

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

Die Probleme sind:

  1. Wir werden so etwas wie {Red = 1, Green, Blue}den Initialisierer für das Wertearray haben. Dies ist kein gültiges C ++, da Redes sich nicht um einen zuweisbaren Ausdruck handelt. Dies wird gelöst, indem jede Konstante in einen Typ umgewandelt wird T, der einen Zuweisungsoperator hat, die Zuweisung jedoch löscht : {(T)Red = 1, (T)Green, (T)Blue}.
  2. In ähnlicher Weise werden wir {"Red = 1", "Green", "Blue"}als Initialisierer für das Namensarray enden . Wir müssen das abschneiden " = 1". Mir ist keine gute Möglichkeit bekannt, dies zur Kompilierungszeit zu tun, daher werden wir dies auf die Laufzeit verschieben. Infolgedessen _to_stringwird es nicht sein constexpr, _from_stringkann es aber immer noch sein constexpr, da wir Leerzeichen und Gleichheitszeichen als Terminatoren behandeln können, wenn wir sie mit nicht zugeschnittenen Zeichenfolgen vergleichen.
  3. Beide oben genannten benötigen ein "Mapping" -Makro, das auf jedes Element in ein anderes Makro anwenden kann __VA_ARGS__. Das ist ziemlich normal. Diese Antwort enthält eine einfache Version, die bis zu 8 Elemente verarbeiten kann.
  4. Wenn das Makro wirklich in sich geschlossen sein soll, muss es keine statischen Daten deklarieren, für die eine separate Definition erforderlich ist. In der Praxis bedeutet dies, dass Arrays eine spezielle Behandlung benötigen. Es gibt zwei mögliche Lösungen: constexpr(oder nur const) Arrays im Namespace-Bereich oder reguläre Arrays in nicht constexprstatischen Inline-Funktionen. Der Code in dieser Antwort ist für C ++ 11 und folgt dem früheren Ansatz. Der CodeProject-Artikel ist für C ++ 98 und übernimmt letzteres.

Code

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

und

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

Das obige Programm Redwird wie erwartet gedruckt . Es gibt ein gewisses Maß an Typensicherheit, da Sie eine Aufzählung nicht erstellen können, ohne sie zu initialisieren. Wenn Sie einen der Fälle aus dem löschen, switchwird eine Warnung vom Compiler ausgegeben (abhängig von Ihrem Compiler und Ihren Flags). Beachten Sie auch, dass dies "Red"beim Kompilieren in eine Aufzählung konvertiert wurde.


Heya @mrhthepie, sorry, dass deine Bearbeitung abgelehnt wurde. Ich habe gerade die E-Mail darüber gesehen. Ich werde es in die Antwort aufnehmen - danke für den Bugfix!
Antron

das ist toll. Würde das auch funktionieren, wenn ich eine Aufzählung von Bits möchte? Wie ich eine Aufzählung von BitFlags möchte, wird jede um einen 1Ubestimmten Betrag verschoben?
user3240688

1
_trimmed_names()In dem Code, den Sie hier gepostet haben, scheint ein Speicherverlust aufgetreten zu sein ( new char[length + 1]aber Sie setzen nicht initializedauf true). vermisse ich etwas Ich sehe nicht das gleiche Problem in Ihrem Github-Code.
user3240688

1
Es ist auf true, aber außerhalb des ifZweigs eingestellt (Speicherverlust ursprünglich von @mrhthepie abgefangen). Sollte es nach innen verschieben ... Bearbeiten. Vielen Dank für den genauen Blick auf diesen und den GH-Code.
Antron

1
to_stringkönnte a string_viewvon C ++ 17 zurückgeben, für das keine Nullterminierung erforderlich ist, und constexpr werden.
Yakk - Adam Nevraumont

74

Für C ++ 17 C ++ 20 interessieren Sie sich für die Arbeit der Reflection Study Group (SG7). Es gibt eine parallele Reihe von Artikeln, die sich mit Wortlaut ( P0194 ) sowie Begründung, Design und Entwicklung ( P0385 ) befassen . (Links werden zum neuesten Artikel in jeder Reihe aufgelöst.)

Ab P0194r2 (15.10.2016) würde die Syntax das vorgeschlagene reflexprSchlüsselwort verwenden:

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

Zum Beispiel (adaptiert aus Matus Chocliks Reflex-Zweig ):

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

Die statische Reflexion hat es nicht in C ++ 17 geschafft (eher in den wahrscheinlich endgültigen Entwurf, der auf dem Standardtreffen im November 2016 in Issaquah vorgestellt wurde), aber es besteht die Zuversicht, dass es in C ++ 20 gelingen wird. aus Herb Sutters Reisebericht :

Insbesondere überprüfte die Reflection- Studiengruppe den neuesten zusammengeführten Vorschlag für statische Reflexion und fand ihn bereit, bei unserem nächsten Treffen in die wichtigsten Evolutionsgruppen einzutreten, um den einheitlichen Vorschlag für statische Reflexion für einen TS oder für den nächsten Standard zu prüfen.


2
@antron Entschuldigung, Ihre Bearbeitung wurde abgelehnt. Ich hätte es genehmigt, wenn ich es rechtzeitig gesehen hätte. Ich hatte N4428 nicht gesehen, also danke, dass du die Köpfe aufgegeben hast.
Ecatmur

3
Kein Problem, danke für die Aufnahme. Ich frage mich, warum es abgelehnt wurde. Ich sehe den Grund "macht es nicht genauer", aber es ist deutlich genauer für den heutigen Tag.
Antron

1
Danke :-) Ich habe das letzte Beispiel geteilt, um die horizontale Bildlaufleiste zu vermeiden. Wie schade, dass der Wert MyEnum::AAAnicht als zweites Argument übergeben werden kann std::meta::get_enumerators_m: - /
olibre

1
Die Tatsache, dass eine solche konzeptionell einfache Aufgabe drei Ebenen verschachtelter Vorlagenargumente erfordert, ist stark überarbeitet. Ich bin sicher, es gibt bestimmte technische Gründe dafür. Das heißt aber nicht, dass das Endergebnis benutzerfreundlich ist. Ich liebe C ++ und der Code macht für mich Sinn. Aber 90% der anderen Programmierer, mit denen ich täglich zusammenarbeite, meiden C ++ aufgrund von Code wie diesem. Ich bin enttäuscht, dass ich keine einfacheren, integrierten Lösungen gesehen habe.
void.pointer

2
Es scheint, dass die aktuelle Schätzung für die Aufnahme des kommenden Reflection TS in den Standard C ++ 23 ist : herbsutter.com/2018/04/02/…
Tim Rae

25

Dies ist ähnlich wie bei Yuri Finkelstein; erfordert aber keinen Boost. Ich verwende eine Karte, damit Sie den Aufzählungen in beliebiger Reihenfolge einen beliebigen Wert zuweisen können.

Erklärung der Aufzählungsklasse als:

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

Der folgende Code erstellt automatisch die Aufzählungsklasse und die Überladung:

  • '+' '+ =' für std :: string
  • '<<' für Streams
  • '~' nur um in einen String zu konvertieren (Jeder unäre Operator wird es tun, aber ich persönlich mag es aus Gründen der Klarheit nicht)
  • '*', um die Anzahl der Aufzählungen zu ermitteln

Kein Boost erforderlich, alle erforderlichen Funktionen vorhanden.

Code:

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

Beispiel:

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
Können wir innerhalb dieser Makrodefinition Zeilenumbrüche haben?
Einpoklum

1
Ich habe die Überladung hinzugefügt *, um die Anzahl der Aufzählungen zu ermitteln ... Ich hoffe, es macht Ihnen nichts aus :-)
Peter VARGA

1
Gibt es einen Grund, warum diese Implementierung std::map(O (log (n)) Indizierung) anstelle von std::unordered_map(O (1) Indizierung) verwendet?
River Tam

1
Ich denke auch, dass die Methoden markiert sein sollten, inlinedamit Sie wie normal Aufzählungen in Header-Dateien deklarieren können, ohne vom Linker "mehrfache Definition von" Fehlern zu erhalten. (nicht sicher, ob das tatsächlich die sauberste / beste Lösung ist)
River Tam

1
(Entschuldigung für Spam, aber ich kann Kommentare heute scheinbar nicht bearbeiten) Es gibt andere Probleme damit, dass dies in einer Header-Datei enthalten ist. Die map ( E##MapName) muss in eine Kompilierungseinheit verschoben werden, die ebenfalls Zugriff auf die Aufzählung hat. Ich habe eine Lösung erstellt, aber sie ist nicht sehr sauber und ich muss die Erlaubnis einholen, um sie zu teilen. Im Moment möchte ich nur sagen, dass es keinen Sinn macht, die Methoden inline zu markieren, ohne die zusätzlichen Funktionen, die zur Unterstützung der Verwendung in einer Header-Datei erforderlich sind.
River Tam

19

Im Jahr 2011 verbrachte ich ein Wochenende damit, eine makrobasierte Lösung zu optimieren, und habe sie schließlich nie verwendet.

Meine aktuelle Prozedur besteht darin, Vim zu starten, die Enumeratoren in einen leeren Schalterkörper zu kopieren, ein neues Makro zu starten, den ersten Enumerator in eine case-Anweisung umzuwandeln, den Cursor an den Anfang der nächsten Zeile zu bewegen, das Makro anzuhalten und den verbleibenden Fall zu generieren Anweisungen durch Ausführen des Makros auf den anderen Enumeratoren.

Vim-Makros machen mehr Spaß als C ++ - Makros.

Beispiel aus der Praxis:

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

Ich werde dies erstellen:

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

Und so komme ich zurecht.

Die native Unterstützung für die Enum-Stringifizierung wäre jedoch viel besser. Ich bin sehr interessiert an den Ergebnissen der Reflexionsarbeitsgruppe in C ++ 17.

Eine alternative Möglichkeit wurde von @sehe in den Kommentaren gepostet .


1
Ich mache genau das. Obwohl ich normalerweise Surround vim und Blockauswahl auf dem Weg
benutze

@sehe Interessant. Ich sollte mir "Surround" ansehen, da ich derzeit viel zu viele Tastenanschläge benötige.
StackedCrooked

Hier ist es in vollem Umfang, keine Makros (es sei denn, es .zählt): i.imgur.com/gY4ZhBE.gif
sehe

1
Das animierte GIF ist süß, aber es ist schwer zu sagen, wann es beginnt und endet und wie weit wir drin sind. ... eigentlich kratz das, es ist nicht süß, es lenkt ab. Ich sage töte es.
Einpoklum

Dieser Blockauswahlansatz in vim ist nett und alles, aber warum nicht einfach so etwas verwenden :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/?
Ruslan

14

Ich weiß nicht, ob Ihnen das gefallen wird oder nicht. Ich bin mit dieser Lösung nicht ganz zufrieden, aber es ist ein C ++ 14-freundlicher Ansatz, da Vorlagenvariablen verwendet werden und die Spezialisierung von Vorlagen missbraucht wird:

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

Das Schlimmste an diesem Ansatz ist, dass es ein Schmerz ist, ihn aufrechtzuerhalten, aber es ist auch ein Schmerz, einige andere ähnliche Ansätze aufrechtzuerhalten, nicht wahr?

Gute Punkte zu diesem Ansatz:

  • Verwenden variabler Tempate (C ++ 14-Funktion)
  • Mit der Vorlagenspezialisierung können wir "erkennen", wenn ein ungültiger Wert verwendet wird (aber ich bin nicht sicher, ob dies überhaupt nützlich sein könnte).
  • Es sieht ordentlich aus.
  • Die Namenssuche erfolgt zur Kompilierungszeit.

Live example

Bearbeiten

Misterious user673679 du hast recht; Der C ++ 14-Ansatz für variable Vorlagen behandelt den Laufzeitfall nicht. Es war meine Schuld, ihn zu vergessen :(

Aber wir können immer noch einige moderne C ++ - Funktionen und variable Vorlagen sowie Tricks mit variablen Vorlagen verwenden, um eine Laufzeitübersetzung vom Enum-Wert zum String zu erzielen ... es ist genauso lästig wie die anderen, aber dennoch erwähnenswert.

Beginnen wir mit der Verwendung eines Vorlagenalias, um den Zugriff auf eine Enum-to-String-Zuordnung zu verkürzen:

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

Dann der Trick mit den variablen Vorlagen:

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

Der " beste Trick " ist hier die Verwendung einer Variablenvorlage für die Karte, die die Werte und Namen jedes Aufzählungseintrags enthält. Diese Karte ist in jeder Übersetzungseinheit gleich und hat überall den gleichen Namen. Sie ist also ziemlich einfach und ordentlich, wenn wir die initializeFunktion wie folgt aufrufen :

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

Wir weisen jedem MyEnumEintrag Namen zu und können zur Laufzeit verwendet werden:

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

Kann aber mit SFINAE und Überlastungsoperator verbessert <<werden:

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

Mit dem richtigen operator <<Jetzt können wir die Aufzählung folgendermaßen verwenden:

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

Dies ist auch lästig zu pflegen und kann verbessert werden, aber ich hoffe, Sie bekommen die Idee.

Live example


Das sieht ganz ordentlich aus (ist es möglich, die nicht spezialisierte Variable einfach nicht zu definieren?). Vielleicht fehlt mir etwas, da ich nicht sehe, wie es den Laufzeitfall überhaupt behandelt.
user673679

@Paula_plus_plus: Solltest du nicht einfach eine std::arrayanstelle der unhandlichen Karte verwenden? Es wird nur für Aufzählungen vorzuziehen sein, die bei ... was, 2 ^ 10 Werten beginnen? Vielleicht sogar noch mehr.
Einpoklum

@einpoklum das wäre erstaunlich, wenn wir zur Laufzeit sicherstellen könnten, wie viele Elemente ein enumhaben. Leider können wir nicht. Und der springende Punkt der Karte ist nur, Namen mit Werten zu verknüpfen, wofür std::mapes gut ist.
PaperBirdMaster

@Paula_plus_plus: Sie rufen bereits eine initialize()Funktion auf, deren Anzahl der Argumente der Anzahl der Aufzählungswerte entspricht, sodass Sie die Anzahl der Werte zur Kompilierungszeit kennen. Nur der spezifische Wert, den Sie drucken sollen, ist nur zur Laufzeit bekannt. Selbst wenn Sie diese Zahl nicht kennen würden, wäre ein std :: vector in fast allen realistischen Fällen schneller als eine std :: map.
Einpoklum

@einpoklum das ist in der Tat ein sehr guter Punkt, ich werde darüber nachdenken, danke! Das einzige, was mich beunruhigt, ist, dass std::arrayes sich nicht um einen Schlüsselwertcontainer handelt und daher keine Suchmethoden vorhanden sind. Wie auch immer, ich werde darüber nachdenken.
PaperBirdMaster

7

Wenn du enumso aussiehst

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

Sie können den Inhalt von enumin eine neue Datei verschieben:

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

Und dann können die Werte von einem Makro umgeben sein:

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

Der nächste Schritt kann darin bestehen, die Elemente enumerneut aufzunehmen:

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

Und schließlich können Sie dazu Dienstprogrammfunktionen generieren 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");
}

Die Lösung kann auf ältere C ++ - Standards angewendet werden und verwendet keine modernen C ++ - Elemente. Sie kann jedoch verwendet werden, um ohne großen Aufwand und Wartung viel Code zu generieren.


3
Es ist keine separate Datei erforderlich. Dies ist im Wesentlichen ein x-Makro .
HolyBlackCat

@HolyBlackCat Wenn Sie die Lösung in einige Dateien aufteilen, können Sie die Aufzählungswerte für verschiedene Zwecke
wiederverwenden

Ich versuche Ihnen zu sagen, dass Sie dasselbe tun können, wenn Sie die Werteliste neben der Aufzählungsdefinition in einem Header in ein einzelnes Makro einfügen.
HolyBlackCat

@HolyBlackCat Ja, ich verstehe dich, aber ich bevorzuge diese Lösung. Auf der anderen Seite kann diese Lösung im Clang-Quellcode gefunden werden, also denke ich, dass es ein guter Weg ist, um das Problem zu lösen
eferion

Meinetwegen. Ich denke, ich hätte das nicht ablehnen sollen, da es tatsächlich einige Verwendungszwecke haben kann. (Entschuldigen Sie die Dummy-Bearbeitung, das System sperrt meine Stimme ansonsten.)
HolyBlackCat

6

Ich hatte vor ein paar Tagen das gleiche Problem. Ich konnte keine C ++ - Lösung ohne seltsame Makromagie finden und beschloss, einen CMake-Codegenerator zu schreiben , um einfache Switch-Case-Anweisungen zu generieren.

Verwendungszweck:

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

Die Funktion durchsucht die Include-Dateien im Dateisystem (verwendet die mit dem Befehl include_directories gelieferten Include-Verzeichnisse), liest sie und führt einen regulären Ausdruck durch, um die Klasse und die Funktion (en) zu generieren.

HINWEIS: constexpr impliziert Inline in C ++. Wenn Sie also die Option USE_CONSTEXPR verwenden, wird nur eine Header-Klasse generiert!

Beispiel:

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

Erzeugt:

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

Aktualisieren:

Das Skript unterstützt jetzt auch Aufzählungen mit Gültigkeitsbereich (enum class | struct) und ich habe es mit einigen anderen Skripten, die ich häufig verwende, in ein separates Repo verschoben: https://github.com/mensinda/cmakeBuildTools


Beeindruckend! Sehr originelle und innovative Idee :-) Ich hoffe, Sie haben den Mut, Ihren Generator zu aktualisieren, um eine constexprund noexceptVersion bereitzustellen ;-) Ich habe auch gerade Ihr GitHub-Projekt
angestarrt ;-)

1
Der Generator wurde aktualisiert. Die Funktionen sind jetzt immer constexpr und enum: <type> wird jetzt unterstützt. Danke für den Stern :)
Mense

Die Verbindung ist unterbrochen ... -.-
yeoman

Der Link ist jetzt behoben.
Mense

4

Generieren Sie einfach Ihre Aufzählungen. Das Schreiben eines Generators für diesen Zweck dauert ungefähr fünf Minuten.

Generatorcode in Java und Python, super einfach in jede Sprache zu portieren, die Sie mögen, einschließlich C ++.

Auch super einfach um jede gewünschte Funktionalität zu erweitern.

Beispieleingabe:

First = 5
Second
Third = 7
Fourth
Fifth=11

generierter Header:

#include <iosfwd>

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

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

generierte cpp-Datei

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

Und der Generator in einer sehr knappen Form als Vorlage für Portierung und Erweiterung. Dieser Beispielcode versucht wirklich, das Überschreiben von Dateien zu vermeiden, verwendet ihn jedoch auf eigenes Risiko.

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

Und eine Portierung auf Python 3.5, weil sie unterschiedlich genug ist, um möglicherweise hilfreich zu sein

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
Vielen Dank, dass Sie Ihren Generator in zwei Sprachen geteilt haben :-) Aber haben Sie eine Idee, wie Sie zur Kompilierungszeit generieren können? Können wir uns zum Beispiel vorstellen, Ihren Generator mit CMake-Anweisungen zu übersetzen, um den von C ++ generierten Code zu aktualisieren, wenn Eingabedaten geändert werden? Mein Traum ist es, den C ++ - Compiler zu zwingen, beim Kompilieren mithilfe von Metaprogrammierung ( variadic template classund constexprFunktionen) Aufzählungen zu generieren .
Olibre

Otoh, falls es zu umständlich ist, einen benutzerdefinierten cmake-Befehl hinzuzufügen, können Sie Ihre IDE automatisieren oder den Gererator manuell aufrufen und die Ausgabe in der Quellcodeverwaltung haben. Es ist manchmal eine gute Idee, Code in der Quellcodeverwaltung zu generieren, solange es nicht zu viel ist, und die Leute verstehen, dass sie keine manuellen Änderungen vornehmen sollen, da es manchmal interessant ist, den Verlauf der generierten Dateien zu betrachten, wenn Sie Ich debugge etwas Seltsames und habe den Verdacht, dass eine kürzliche Änderung am Generator etwas kaputt gemacht hat :)
Yeoman

Das Generieren von Dingen zur Kompilierungszeit ist in LISP so einfach, weil die Syntax so extrem sauber und einfach ist. Dies wird durch die Tatsache unterstützt, dass es dynamisch typisiert ist, wodurch es ohne viel Syntax knapp und lesbar ist. Das Äquivalent von LISP-Makros in C ++ würde eine sehr komplizierte Methode erfordern, um den AST dessen zu beschreiben, was Sie generieren möchten. Und ein AST für C ++ ist nie schön :(
yeoman

Direkt in Make statt cmake ist es übrigens super einfach. Generieren Sie einfach .h- und .cpp-Ziele für jede .enum-Datei über find und lassen Sie diese Ziele von den Enum-Defs abhängen, sodass sie automatisch neu generiert werden, sobald sich die .enum-Def-Dateien ändern. In cmake ist es wahrscheinlich viel einfacher, weil es für diese Art von Dingen voller Magie ist, aber ich benutze regelmäßig Make, Ant und Gradle, habe aber nur begrenzte Kenntnisse über Maven, cmake und Grunzen :)
Yeoman

Vielen Dank für Ihre Antwort :-) Ich denke, die meisten C ++ - Entwickler werden es zu schätzen wissen, wenn Ihr Generator Aufzählungen direkt in C ++ - Code wie enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};oder in mehreren Zeilen erkennen kann :-D Denken Sie, Sie können Ihren Generator anpassen, um eine enumin C ++ zu erkennen Datei? Das Beste könnte sein, Code nur bei Erkennung eines Tags wie zu generieren /*<Generate enum to string here>*/. Dann schreibt Ihr Generator direkt den entsprechenden C ++ - generierten Code (ersetzt den zuvor generierten Code). ^ _ ^ Was für ein großartiger Generator, nicht wahr? Prost :-)
Olibre

3

Auf Anfrage des OP hier eine abgespeckte Version der hässlichen Makrolösung basierend auf Boost Preprosessor und Variadic Macros .

Es ermöglicht eine einfache Liste wie die Syntax der Enumerator-Elemente sowie das Festlegen von Werten für bestimmte Elemente, so dass

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

erweitert sich auf

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

Zusammen mit den notwendigen Funktionen zum Ausgeben und Zurückkonvertieren. Dieses Makro gibt es hier schon seit Ewigkeiten, und ich bin mir nicht ganz sicher, ob es der effizienteste oder konforme Weg ist, aber es funktioniert seitdem

Der vollständige Code ist sowohl bei Ideone als auch bei Coliru in Aktion zu sehen .

Seine gigantische Hässlichkeit ist oben; Ich hätte es hinter Spoiler gestellt, um deine Augen zu schützen, wenn ich gewusst hätte wie, aber Markdown mag mich nicht.

Die Bibliothek (zusammengeführt in einer einzigen Header-Datei)

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

Verwendungszweck

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

Kompilierung (Kopieren Einfügen Header innerhalb 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)));
                               ^

Ausgabe

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

5
Dieser Codeblock ist eine verrückte Reise durch die erstaunlichen Landschaften der Metaprogrammierung schwarzer Magie. Ich fühlte mich tatsächlich erleichtert, als ich erreichte main- Zuhause, süßes Zuhause!
Quentin

Fügen Sie einfach einen Link zu coliru hinzu, um die Ausgabe zu überprüfen (es gibt einige Warnungen, klicken Sie auf den Link in Ihrer Antwort). Ich habe mich auch in Lib / Usage aufgeteilt. Kann das Zeug namespace xxxan die Kopfzeile verschoben werden? Sie können im Intro Ihre Verwendung angeben boost/preprocessor.hppund daher ist die Antwort modern C ++ -kompatibel . Bitte korrigieren Sie die Warnungen und bereinigen Sie den Quellcode ein wenig, um eine bessere Qualität zu erzielen.
Olibre

@olibre: Es ist Copypastad von ich denke 5 verschiedene Header in unserer Bibliothek. Der enum_cast stammt aus einem anderen allgemeineren Teil, aber ich dachte, ich sollte ihn auch hinzufügen, um zu sehen, wozu der do_enum_cast im Makro dient. Die Warnungen stammen nur von main<tab>vim, einschließlich der Argumente, die ich nicht verwende. Ich glaube nicht, dass dieser Code wirklich bereinigt werden kann, er soll nur zeigen, was getan werden kann und was nicht;) und wenn ich ihn hier ändere, ist es nicht mehr der Code, den ich in der Produktion verwende ... es ist eines dieser fragilen Dinge Wenn es einmal funktioniert, berühren Sie es besser nie, da es auf eine Weise zusammenbrechen könnte, die niemand vorhersagen kann.
PlasmaHH

Alles klar Plasma, ich sehe das als Proof Of Concept . Aber es gibt zu viel Makro-Overhead, um hochgestimmt zu werden. Trotzdem danke fürs Teilen. Prost
olibre

Hallo Plasma. Ich habe eine gründliche Quellcodebereinigung + durchgeführt, die durch Kompilieren und Ausführen der Ausgabe abgeschlossen wurde. Bitte überprüfen Sie meine Bearbeitung . Ich hoffe das ist OK für dich. Ist die Antwort wertvoller? Der Makro-Overhead ist jedoch immer noch schrecklich! Einen schönen Tag noch :-) Cheers
Olibre

2

Die folgende Lösung basiert auf a std::array<std::string,N> für eine bestimmte Aufzählung.

Für enumdie std::stringKonvertierung können wir einfach die Aufzählung in size_tdas Array umwandeln und den String aus dem Array nachschlagen. Die Operation ist O (1) und erfordert keine Heap-Zuordnung.

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

Für std::stringdie enumKonvertierung müssten wir eine lineare Suche über das Array durchführen und den Array-Index in umwandeln enum.

Versuchen Sie es hier mit Verwendungsbeispielen: http://coliru.stacked-crooked.com/a/e4212f93bee65076

Bearbeiten: Meine Lösung wurde überarbeitet, damit die benutzerdefinierte Aufzählung in einer Klasse verwendet werden kann.


Vielen Dank für Ihre interessante Antwort. Bitte überarbeiten Sie Ihren Vorschlag, um Ihr Makro innerhalb einer Klasse zu verwenden. Siehe coliru.stacked-crooked.com/a/00d362eba836d04b Außerdem versuchen zu verwenden , constexprund noexeptSchlüsselwörter , wenn möglich. Prost :-)
Olibre

Die Frage hat diese Voraussetzung nicht angegeben.
FKaria

Frage aktualisiert (siehe Beispiel). Zwei weitere Anforderungen: (1) Unterstützungsart der Aufzählung und (2) Werte können von der Sequenz 0, 1, 2 ...
abweichen

Ich habe meine Lösung dahingehend überarbeitet, dass sie innerhalb einer Klasse verwendet werden kann. Ich habe nicht herausgefunden, wie ich die Werte von 0,1,2 unterscheiden kann.
FKaria

Hallo FKaria. Vielen Dank für Ihre Überarbeitung. Ich habe einige Änderungen vorgenommen, um mehrere Aufzählungen innerhalb derselben Klasse zu unterstützen und auch das enum class X : TypeFormat zu unterstützen . Bitte überprüfen Sie meinen Beitrag: coliru.stacked-crooked.com/a/b02db9190d3491a3 Was halten Sie von meinen Änderungen? Haben Sie eine Idee, in enum festgelegte Werte zu unterstützen? Beispiel enum E{A=3, B=6, C=A-B};Cheers
Olibre

2

Das Wesentliche bietet eine einfache Zuordnung basierend auf variablen C ++ - Vorlagen.

Dies ist eine C ++ 17-vereinfachte Version der typbasierten Map aus dem Kern :

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

Ein Anwendungsbeispiel:

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;

Das map<KeyValues...>kann in beide Richtungen verwendet werden:

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

Dieses Beispiel ist auf godbolt.org verfügbar

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

Das Ergebnis von gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

1
Sehr interessante Meta-Programmiermethode. Ich habe versucht, die Antwort ein wenig zu vereinfachen, um autonom zu sein (ohne Abhängigkeit vom Gist-Link). Um kurz und verständlich zu sein, habe ich Ihre Antwort endlich viel bearbeitet. Stimmen Sie meinen Änderungen noch zu? Prost ;-)
Olibre

2

Dieses Problem hat mich auch schon lange frustriert, ebenso wie das Problem, dass ein Typ ordnungsgemäß in einen String konvertiert wird. Beim letzten Problem war ich jedoch überrascht von der Lösung, die unter Ist es möglich, den Typ einer Variablen in Standard-C ++ zu drucken? , Mit der Idee von Can I erhalten C ++ Typnamen in einer constexpr Art und Weise? . Mit dieser Technik kann eine analoge Funktion zum Abrufen eines Aufzählungswerts als Zeichenfolge erstellt werden:

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

Der obige Code wurde nur auf Clang (siehe https://ideone.com/je5Quv ) und VS2015 getestet , sollte jedoch an andere Compiler angepasst werden können, indem ein wenig an den Ganzzahlkonstanten herumgespielt wird. Natürlich werden immer noch Makros unter der Haube verwendet, aber mindestens eines benötigt keinen Zugriff auf die Enum-Implementierung.


Dies schlägt mit g ++ 6.3.0 und C ++ 14 fehl.
Einpoklum

Interessant, weil die Aufzählung normal deklariert werden kann, ohne sie in ein Makro einschließen zu müssen. Obwohl ich die Compiler-Abhängigkeiten und magischen Konstanten nicht mag.
Zett42

2

Ich habe die Idee von @antron übernommen und anders implementiert: eine echte Enum-Klasse generieren .

Diese Implementierung erfüllt alle in der ursprünglichen Frage aufgeführten Anforderungen, weist jedoch derzeit nur eine echte Einschränkung auf : Es wird davon ausgegangen, dass die Aufzählungswerte entweder nicht angegeben sind oder, falls angegeben, mit 0 beginnen und nacheinander ohne Lücken steigen müssen.

Dies ist keine intrinsische Einschränkung - einfach, dass ich keine Ad-hoc-Enum-Werte verwende. Wenn dies erforderlich ist, kann die Vektorsuche durch eine herkömmliche Switch / Case-Implementierung ersetzt werden.

Die Lösung verwendet etwas c ++ 17 für Inline-Variablen, dies kann jedoch bei Bedarf leicht vermieden werden. Es wird auch Boost: Trim verwendetAufgrund der Einfachheit wird .

Am wichtigsten ist, dass nur 30 Codezeilen und keine Makros für schwarze Magie erforderlich sind. Der Code ist unten. Es soll in den Header eingefügt und in mehreren Kompilierungsmodulen enthalten sein.

Es kann auf die gleiche Weise verwendet werden, wie es zuvor in diesem Thread vorgeschlagen wurde:

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

Bitte lassen Sie mich wissen, ob dies nützlich ist und wie es weiter verbessert werden kann.


#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

Solange Sie mit dem Schreiben eines separaten .h/.cppPaares für jede abfragbare Aufzählung einverstanden sind, funktioniert diese Lösung mit nahezu derselben Syntax und denselben Funktionen wie eine normale C ++ - Aufzählung:

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

Die .cppDatei besteht aus 3 Zeilen Boilerplate:

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

Anwendungsbeispiel:

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

Code

Diese Lösung erfordert 2 Quelldateien:

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

...und

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

Erläuterung

Diese Implementierung nutzt die Tatsache aus, dass die geschweifte Liste von Elementen einer Aufzählungsdefinition auch als geschweifte Initialisierungsliste für die Initialisierung von Klassenmitgliedern verwendet werden kann.

Wenn ETRAITSes im Kontext von ausgewertet wird EnumTraits.inl, wird es zu einer statischen Elementdefinition für das erweitertEnumTraits<> Klasse erweitert.

Das EDECLMakro wandelt jedes Aufzählungselement in Initialisierungslistenwerte um, die anschließend an den Elementkonstruktor übergeben werden, um die Aufzählungsinformationen zu füllen.

Die EnumInitGuardKlasse ist so konzipiert, dass sie die Enum-Initialisierungswerte verwendet und dann reduziert wird, sodass eine reine Liste von Enum-Daten verbleibt.

Leistungen

  • c++-ähnliche Syntax
  • Funktioniert für beide enumund enum class(* fast) identisch
  • Funktioniert für enumTypen mit einem beliebigen numerischen zugrunde liegenden Typ
  • Funktioniert für enumTypen mit automatischen, expliziten und fragmentierten Initialisierungswerten
  • Funktioniert für die Massenumbenennung (Intellisense-Verknüpfung erhalten)
  • Nur 5 Präprozessorsymbole (3 global)

* Im Gegensatz dazu enumsmüssen Initialisierer in enum classTypen, die auf andere Werte aus derselben Aufzählung verweisen, diese Werte vollständig qualifiziert haben

Nachteile

  • Benötigt ein separates .h/.cppPaar für jede abfragbareenum
  • Kommt auf verschlungene macround includemagische an
  • Kleinere Syntaxfehler explodieren in viel größere Fehler
  • Das Definieren classoder namespaceAuflisten von Enums ist nicht trivial
  • Keine Initialisierung der Kompilierungszeit

Bemerkungen

Intellisense wird sich beim Öffnen ein wenig über den Zugriff privater Mitglieder beschweren EnumTraits.inl, aber da die erweiterten Makros tatsächlich Klassenmitglieder definieren, ist dies eigentlich kein Problem.

Das #ifndef ENUM_INCLUDE_MULTI Block oben in der Header-Datei ist ein kleiner Ärger, der wahrscheinlich in ein Makro oder etwas anderes verkleinert werden könnte, aber klein genug, um mit seiner aktuellen Größe zu leben.

Um eine Enumeration mit Namespace-Gültigkeitsbereich zu deklarieren, muss die Enumeration zunächst in ihrem Namespace-Bereich vorwärts deklariert und dann im globalen Namespace definiert werden. Darüber hinaus müssen für Enum-Initialisierer, die Werte derselben Enum verwenden, diese Werte vollständig qualifiziert sein.

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

Ich bin mir nicht sicher, ob dieser Ansatz bereits in einer der anderen Antworten behandelt wird (tatsächlich, siehe unten). Ich bin oft auf das Problem gestoßen und habe keine Lösung gefunden, bei der keine verschleierten Makros oder Bibliotheken von Drittanbietern verwendet wurden. Daher habe ich beschlossen, meine eigene verschleierte Makroversion zu schreiben.

Was ich aktivieren möchte, ist das Äquivalent von

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
}

was drucken sollte

ONE
TWO
13

Ich bin kein Fan von Makros. Sofern c ++ die Konvertierung von Enums in Strings nicht nativ unterstützt, muss eine Art Codegenerierung und / oder Makros verwendet werden (und ich bezweifle, dass dies zu früh passieren wird). Ich benutze ein X-Makro :

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

Das meiste davon definiert und dedefiniert Symbole, die der Benutzer als Parameter über ein Include an den X-marco übergibt. Die Verwendung ist wie folgt

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

Live-Demo

Beachten Sie, dass ich den zugrunde liegenden Typ noch nicht ausgewählt habe. Ich brauchte es bisher nicht, aber es sollte einfach sein, es in Code zu ändern, um dies zu ermöglichen.

Erst nachdem ich dies geschrieben hatte, wurde mir klar, dass es der Antwort von eferions ziemlich ähnlich ist . Vielleicht habe ich es schon einmal gelesen und vielleicht war es die Hauptinspirationsquelle. Ich habe X-Makros immer nicht verstanden, bis ich meine eigenen geschrieben habe;).


1

Lösungen, die Enum innerhalb von class / struct verwenden (Struktur standardmäßig mit öffentlichen Mitgliedern) und überladene Operatoren:

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

Von außen sieht es fast genauso aus wie eine Klassenaufzählung:

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

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

Dies gibt "rot 1 2" aus. Sie könnten möglicherweise << überladen, um die blaue Ausgabe zu einer Zeichenfolge zu machen (obwohl dies zu Mehrdeutigkeiten führen kann, die nicht möglich sind), aber mit Color :: GREEN funktioniert dies nicht, da es nicht automatisch in Farbe konvertiert wird.

Der Zweck einer impliziten Konvertierung in Enum (die implizit in int oder einen angegebenen Typ konvertiert) besteht darin, Folgendes zu tun:

Color color;
switch (color) ...

Das funktioniert, aber es bedeutet auch, dass dies auch funktioniert:

int i = color;

Mit einer Enum-Klasse würde es nicht kompiliert. Sie sollten vorsichtig sein, wenn Sie zwei Funktionen mit der Aufzählung und einer Ganzzahl überladen oder die implizite Konvertierung entfernen ...

Eine andere Lösung würde die Verwendung einer tatsächlichen Aufzählungsklasse und statischer Elemente beinhalten:

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

    //same as previous...
};

Es nimmt möglicherweise mehr Speicherplatz in Anspruch und ist länger in der Erstellung, verursacht jedoch einen Kompilierungsfehler für implizite int-Konvertierungen. Ich würde dieses deswegen benutzen!

Das ist sicherlich mit Aufwand verbunden, aber ich denke, es ist einfach einfacher und sieht besser aus als anderer Code, den ich gesehen habe. Es besteht auch die Möglichkeit, Funktionen hinzuzufügen, die alle innerhalb der Klasse liegen können.

Bearbeiten : Dies funktioniert und die meisten können vor der Ausführung kompiliert werden:

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

Das ist sehr interessant :-) Ihre aktuelle Version impliziert jedoch, dass Sie das Zeug manuell schreiben müssen case Enum::RED: return "red";. Die Frage ist, wie dieses Zeug vom Compiler (zur Kompilierungszeit) automatisiert werden kann. Die Idee der Frage ist, nur Enum-Werte zu ändern oder hinzuzufügen, ohne das Zeug aktualisieren zu müssen toString(). Siehst du? Danke
olibre

1

Sehr einfache Lösung mit einer großen Einschränkung: Sie können Werten keine benutzerdefinierten Werte zuweisen enum, aber mit der richtigen Regex können Sie dies. Sie können auch eine Karte hinzufügen, um sie enumohne großen Aufwand wieder in Werte zu übersetzen :

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

Anwendungsbeispiel:

EnumToString(MyEnum, Red, Green, Blue);

Vielen Dank an Malem für Ihre innovative Idee. Ich habe Ihre Antwort bearbeitet, um die Lesbarkeit zu verbessern. Ich hoffe dir gefallen meine Änderungen. Bitte verbessern Sie Ihre Antwort weiter: (1) Erweitern Sie den Abschnitt "Verwendungsbeispiel" um Folgendes auto name = MyEnumStrings["Red"];: (2) Warum verwenden Sie enum class? - (3) Unterstützen Sie enum class MyEnum : char { Red, Green, Blue };? - (4) Funktion erklären split()- (5) Benötigen Sie Parameter const std::regex& delim? - (6) Was ist mit dem Generieren?MyEnumStrings zur Kompilierungszeit? => Kannst du verwenden constexpr? ... Prost :-)
Olibre

Ich mag diesen Ansatz wirklich. Wirklich kurz und leicht zu verstehen.
Anton Holmberg

1

BEARBEITEN: Unten finden Sie eine neuere Version

Wie oben erwähnt, ist N4113 die endgültige Lösung für dieses Problem , aber wir müssen mehr als ein Jahr warten, bis es herauskommt.

Wenn Sie eine solche Funktion wünschen, müssen Sie auf "einfache" Vorlagen und etwas Präprozessor-Magie zurückgreifen.

Enumerator

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

Verwendungszweck

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

Einfache Erklärung

Enum<T>::m_counterwird in jeder Namespace-Deklaration auf 0 gesetzt.
( Könnte mich jemand darauf hinweisen, wo ^^ dieses Verhalten ^^ im Standard erwähnt wird? )
Die Präprozessor-Magie automatisiert die Deklaration von Enumeratoren.

Nachteile

  • Es ist kein echter enumTyp, daher nicht für int werbbar
  • Kann nicht in Switch-Fällen verwendet werden

Alternative Lösung

Dieser opfert die Zeilennummerierung (nicht wirklich) , kann aber für Switch-Fälle verwendet werden .

#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 0Konflikte mit -pedanticauf GCC und klirren.

Problemumgehung

Beginnen Sie entweder bei #line 1und subtrahieren Sie 1 von __LINE__.
Oder nicht verwenden-pedantic .
Und wenn wir schon dabei sind, vermeiden Sie VC ++ um jeden Preis, es war schon immer ein Witz eines Compilers.

Verwendungszweck

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

Implementierung und Verwendung im realen Leben

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Kurzübersicht

#line lineno - cppreference.com


0

Ich habe eine Bibliothek geschrieben, um dieses Problem zu lösen. Alles geschieht in der Kompilierungszeit, außer dass die Nachricht abgerufen wird.

Verwendungszweck:

Verwenden Sie Makro DEF_MSG, um ein Makro- und Nachrichtenpaar zu definieren:

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

CODE_OKist das zu verwendende Makro und "OK!"die entsprechende Nachricht.

Verwenden Sie get_message()oder nur gm(), um die Nachricht zu erhalten:

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

Verwenden MSG_NUMSie diese Option , um herauszufinden, wie viele Makros definiert wurden. Dies erhöht sich automatisch, Sie müssen nichts tun.

Vordefinierte Nachrichten:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Projekt: libcodemsg


Die Bibliothek erstellt keine zusätzlichen Daten. Alles geschieht in der Kompilierungszeit. In message_def.herzeugt es einen enumangerufenen MSG_CODE; In message_def.cgeneriert es eine Variable, die alle Zeichenfolgen enthält static const char* _g_messages[].

In diesem Fall kann die Bibliothek nur eine erstellen enum. Dies ist ideal für Rückgabewerte, zum Beispiel:

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

MSG_CODE ret = foo();

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

Eine andere Sache, die mir dieses Design gefällt, ist, dass Sie Nachrichtendefinitionen in verschiedenen Dateien verwalten können.


Ich fand die Lösung für diese Frage viel besser.


Hallo Madwyn. Danke für deine Idee. Aber wie funktioniert es? Was ist der Overhead? (Kein Overhead oder werden zusätzliche Daten erstellt?) Ihr Vorschlag scheint in Ordnung zu sein, aber leider DEF_MSGmuss für jeden enumWert eine Aussage verwendet / aktualisiert / gepflegt werden: - / Und das ist es, was wir idealerweise gerne aufhören würden ... Prost
olibre

Vielen Dank für die Antwort, @olibre. Bitte überprüfen Sie die aktualisierte Antwort. Ich sehe hier keinen Overhead, außer dass ein Funktionsaufruf für den Zugriff auf die Zeichenfolgen erforderlich ist. DEF_MSGmacht das enumeng mit der Nachricht gepaart, obwohl es einige Einschränkungen hat.
Madwyn

Vielen Dank für die beigefügte Erklärung in Ihrer Antwort :-) Ihre Bibliothek ist in Ordnung, kann aber nicht für mehrere Aufzählungen verwendet werden: - / Was ist mit der Unterstützung von enum class(C ++ 11) ? Sie können verwenden constexpr, um _g_messageszur Laufzeit zu begrenzen . Unterstützung mehrerer enumTypen (Vermeidung _g_messages) mithilfe von Metaprogrammierung (Typübermittlung {Aufzählungstyp, Aufzählungswert}) oder möglicherweise Vorlagenvariablen (C ++ 14) . Ich denke, Ihre Bibliothek entspricht (noch?) Nicht den Anforderungen von C ++ 11/14/17. Was denken Sie? Prost ;-)
Olibre

1
Vielen Dank für das Follow-up. Ich habe heute etwas Neues gelernt! Die Variablen enum class und template sehen gut aus. Ich denke, meine Antwort war ein bisschen "off topic", da sie C-Geschmack hatte.
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;
    }

Beispiel

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

Das Makro ENUM_MAKE generiert automatisch die 'Enum-Klasse' und die Hilfsklasse mit der 'Enum-Reflexionsfunktion'.

Um Fehler zu reduzieren, wird sofort alles mit nur einem ENUM_MAKE definiert.

Der Vorteil dieses Codes wird automatisch für die Reflexion und einen genauen Blick auf den leicht verständlichen Makrocode erstellt. Die Leistung von 'enum to string' und 'string to enum' ist der Algorithmus O (1).

Nachteile sind, dass bei der ersten Verwendung die Hilfsklasse für den String-Vektor und die Map von enum relection initialisiert wird. Wenn Sie möchten, werden Sie auch vorinitialisiert. - -


Während dieser Code die Frage beantworten kann, ist es besser zu erklären, wie er das Problem löst, ohne andere vorzustellen, und warum er verwendet wird. Nur-Code-Antworten sind auf lange Sicht nicht sinnvoll.
JAL

Hey Leute, es tut mir leid, dass ich nicht sehr gut Englisch spreche.
Desperado_98

Das Makro ENUM_MAKE generiert automatisch die 'Enum-Klasse' und die Hilfsklasse mit der 'Enum-Reflexionsfunktion'. / Um Fehler zu reduzieren, wird sofort alles mit nur einem ENUM_MAKE definiert. Der Vorteil dieses Codes wird automatisch für die Reflexion und einen genauen Blick auf den leicht verständlichen Makrocode erstellt. Die Leistung von 'enum to string' und 'string to enum' ist der Algorithmus O (1). Nachteile sind, dass bei der ersten Verwendung die Hilfsklasse für den String-Vektor und die Map von enum relection initialisiert wird. Wenn Sie möchten, werden Sie auch vorinitialisiert.
Desperado_98

Hallo Desperado_98. Danke für Ihren Beitrag. Bitte bearbeiten Sie Ihre Antwort und fügen Sie Ihren Kommentarinhalt ein. Der Compiler kann Ihr Beispiel zur Kompilierungszeit berechnen, wenn Sie einige Meta-Programmier-Tricks und verwenden constexpr. Ich meine die Funktionen toName()und toType()kann während der Kompilierung und nicht während der Ausführung (Laufzeit) ausgewertet werden. Bitte übernehmen Sie in Ihrer Antwort den C ++ 11/14/17 Stil. Prost ;-)
Olibre

Außerdem: Ist Ihr Makro kompatibel mit enum class MyEnum : short { A, B, C };?
Olibre

0

Meine Lösung ist ohne Makronutzung.

Vorteile:

  • Sie sehen genau, was Sie tun
  • Der Zugriff erfolgt über Hash-Maps, was für viele geschätzte Aufzählungen gut ist
  • Keine Notwendigkeit, Reihenfolge oder nicht aufeinanderfolgende Werte zu berücksichtigen
  • Die Übersetzung von Enum zu String und von String zu Enum muss hinzugefügt werden, während der hinzugefügte Enum-Wert nur an einer zusätzlichen Stelle hinzugefügt werden muss

Nachteile:

  • Sie müssen alle Aufzählungswerte als Text replizieren
  • Der Zugriff in der Hash-Map muss die Groß- und Kleinschreibung berücksichtigen
  • Wartung, wenn das Hinzufügen von Werten schmerzhaft ist - muss sowohl in der Aufzählung als auch in der direkten Übersetzungskarte hinzugefügt werden

Also ... bis zu dem Tag, an dem C ++ die C # Enum.Parse-Funktionalität implementiert, werde ich daran festhalten:

            #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

Nun, noch eine Option. Ein typischer Anwendungsfall besteht darin, dass Sie Konstanten für die HTTP-Verben sowie deren Versionswerte für Zeichenfolgen benötigen.

Das Beispiel:

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

}

Die VERB-Klasse:

// -----------------------------------------------------------
// -----------------------------------------------------------
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
Um die Speichernutzung zu reduzieren, können Sie das Mitglied const std::string textdurch just ersetzen theStrings[v]. Die Frage
bezieht sich

0

Meine Antwort ist hier.

Sie können Enum-Wertnamen und diese Indizes gleichzeitig als Zeichenfolge abrufen.

Diese Methode benötigt nur wenig Kopieren und Einfügen und Bearbeiten.

Das erhaltene Ergebnis erfordert eine Typumwandlung von size_t zu enum class type, wenn Sie einen enum class type value benötigen, aber ich denke, es ist eine sehr portable und leistungsstarke Methode, um enum class zu behandeln.

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

Sie könnten eine Reflexionsbibliothek wie Ponder verwenden :

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

(Analog zu https://stackoverflow.com/a/54967187/2338477 , leicht modifiziert).

Hier ist meine eigene Lösung mit minimaler Definitionsmagie und Unterstützung einzelner Aufzählungsaufgaben.

Hier ist die Header-Datei:

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

Und hier ist ein Beispiel für eine Testanwendung:

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

Die aktualisierte Version derselben Header-Datei wird hier gespeichert:

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


-5

Was ist mit einer einfachen Streaming-Überlastung? Sie müssen das Mapping noch beibehalten, wenn Sie keine Makromagie ausführen möchten, aber ich finde es sauberer als Ihre ursprüngliche Lösung.

#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) es erzeugt noch mehr Duplikate 2) es zwingt dich, Streams zu verwenden
Karoly Horvath

6
-1 Sorry @dau_sama, aber der Zweck all dieser Enum-to-String- wiederkehrenden Fragen besteht darin, die Wartung der Enum / String-Zuordnung zu vermeiden. Wenn Sie der Meinung sind, dass Ihre Antwort nicht dem Zweck entspricht, sollten Sie die Antwort löschen. Viel Glück bei Ihrer nächsten Antwort;) Cheers
olibre

-9

Der einfachste Weg?
Verwenden Sie Ada: Enumeration'Image( Value )macht genau das, was Sie wollen. Wenn Sie wirklich C ++ benötigen, können Sie versuchen, die Funktion zu exportieren:

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
Wie beantwortet dies die Frage überhaupt? Die Frage besagt eindeutig, dass eine Aufzählung in modernem C ++ in einen String konvertiert wird.
Michael Choi

1
@MichaelChoi - Das tut es, aber es gibt auch das Problem, das richtige Tool für den Job zu verwenden: Nur weil C ++ vollständig ist und daher alle lösbaren Probleme lösen kann, heißt das NICHT , dass die Lösung: schnell, wartbar oder effizient ist. Die Verwendung einer Sprache mit der richtigen / gewünschten Funktionalität und deren Export ist eine gültige Lösung.
Shark8

3
Im ersten Satz der Frage "Bei dieser Frage geht es um die Verwendung der neuen C ++ - Funktionen". dann "[Ich habe noch keinen] eleganten Weg gefunden, neue Funktionen von C ++ 11, C ++ 14 oder C ++ 17 zu verwenden". Der Autor suchte eindeutig nach einer C ++ - Lösung. Sie haben in Ada eine Lösung angegeben und die Frage nicht beantwortet. Sie schlagen vor, eine völlig andere Abhängigkeit zu integrieren, um etwas zu lösen, das wahrscheinlich nicht im Fragenbereich lag.
Michael Choi
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.