it-swarm.com.de

aufzählungszeichenfolge in modernen C++ 11/C++ 14/C++ 17 und zukünftigen C++ 20

Im Gegensatz zu allen anderen ähnlichen Fragen handelt es sich 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 unter Coliru kompilieren und ausführen.
( Ein anderes ehemaliges 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 unschätzbare Vervielfältigung von anderen Antworten oder Basislink .
  • Vermeiden Sie eine aufgeblasene makrobasierte Antwort, oder versuchen Sie, den #define-Overhead so gering wie möglich zu halten. 
  • Bitte keine manuelle enum -> string-Zuordnung.

Schön zu haben

  • Unterstützt enum-Werte ab einer anderen Zahl als Null
  • Unterstützt negative enum-Werte
  • Unterstützt fragmentierte enum-Werte 
  • Unterstützung class enum (C++ 11)
  • Unterstützt class enum : <type> mit beliebigem <type> (C++ 11)
  • Konvertierungszeit (nicht zur Laufzeit) in eine Zeichenfolge
    oder zumindest schnelle Ausführung zur Laufzeit (z. B. std::map ist keine gute Idee ...)
  • constexpr (C++ 11, entspannt in C++ 14)
  • noexcept (C++ 11)
  • ausschnitt C++ 14/C++ 17 freundlich
  • C++ Stand der Technik

Eine mögliche Idee wäre die Verwendung der C++ - Compiler-Funktionen zur Generierung von C++ - Code zur Kompilierungszeit mithilfe von Meta-Programmier-Tricks, die auf den Funktionen variadic template class und constexpr basieren.

237
olibre

Dies ist ähnlich wie Juri Finkelstein; benötigt aber keinen Boost. Ich verwende eine Karte, damit Sie den Aufzählungen und der 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 Enum-Klasse 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 erhalten

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

21
Danilo Ramos

(Der Ansatz der better_enums library)

Es gibt eine Möglichkeit, in aktuellen C++ eine Zeichenfolge aufzurufen, 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 constexpr ausgeführt werden. Sie können auch den in der Antwort erwähnten C++ 17-Reflektionsvorschlag von @ecatmur implementieren.

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

Die Definition des Makros ist etwas kompliziert, daher beantworte ich das auf verschiedene Weise.

  • 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 Langform-Tutorial beschreibt. [Soll ich es hierher verschieben? Ich denke, es ist zu viel für eine SO Antwort].
  • Es gibt eine Vollfunktionsbibliothek "Better Enums" , die das Makro in einer einzelnen Header-Datei implementiert. Es implementiert auch N4428 Type Property Queries , die aktuelle Überarbeitung des C++ 17-Reflektionsvorschlags N4113. Zumindest für Aufzählungen, die über dieses Makro deklariert werden, können Sie die vorgeschlagene C++ 17-Aufzählung jetzt in C++ 11/C++ 14 verwenden.

Es ist unkompliziert, diese Antwort auf die Funktionen der Bibliothek auszudehnen - hier wird nichts "Wichtiges" ausgelassen. Es ist jedoch ziemlich langwierig und es gibt Bedenken hinsichtlich der Portierbarkeit von Compilern.

Disclaimer: Ich bin Autor des Artikels CodeProject und der Bibliothek.

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


Erläuterung

Der folgende Code implementiert Konvertierungen zwischen Enums und Strings. Es kann jedoch auch für andere Zwecke, wie zum Beispiel Iteration, erweitert werden. Diese Antwort umschließt eine Aufzählung in eine struct. Sie können stattdessen auch ein Merkmal struct neben einem Enum erzeugen.

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. Am Ende wird etwas wie {Red = 1, Green, Blue} als Initialisierer für das Werte-Array stehen. Dies ist kein gültiges C++, da Red kein zuweisbarer Ausdruck ist. Dies wird gelöst, indem jede Konstante in einen Typ T umgewandelt wird, der über einen Zuweisungsoperator verfügt, die Zuweisung jedoch gelöscht wird: {(T)Red = 1, (T)Green, (T)Blue}.
  2. In ähnlicher Weise erhalten wir {"Red = 1", "Green", "Blue"} als Initialisierer für das Names-Array. Wir müssen den " = 1" abschneiden. Mir ist keine gute Möglichkeit bekannt, dies zur Kompilierzeit zu tun, daher werden wir dies auf die Laufzeit verschieben. Aus diesem Grund ist _to_string nicht constexpr, aber _from_string kann immer noch constexpr sein, da beim Vergleich mit unbeschnittenen Zeichenketten Leerzeichen und Gleichheitszeichen als Abschlusszeichen behandelt werden können.
  3. Beide benötigen ein "Mapping" -Makro, das auf jedes Element in __VA_ARGS__ ein anderes Makro anwenden kann. Das ist ziemlich normal. Diese Antwort beinhaltet eine einfache Version, die bis zu 8 Elemente verarbeiten kann.
  4. Wenn das Makro wirklich in sich abgeschlossen sein soll, muss es keine statischen Daten angeben, für die eine separate Definition erforderlich ist. In der Praxis bedeutet dies, dass Arrays einer besonderen Behandlung bedürfen. Es gibt zwei mögliche Lösungen: constexpr (oder einfach const) Arrays im Namespace-Bereich oder reguläre Arrays in statischen Inline-Funktionen, die nicht aus constexpr stammen. Der Code in dieser Antwort bezieht sich auf C++ 11 und verwendet den früheren Ansatz. Der CodeProject-Artikel ist für C++ 98 und übernimmt den letzteren.

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 druckt Red wie erwartet. Es gibt ein gewisses Maß an Typsicherheit, da Sie keine Enumeration erstellen können, ohne sie zu initialisieren. Wenn Sie einen der Fälle aus switch löschen, wird vom Compiler eine Warnung ausgegeben (abhängig von Ihrem Compiler und Flags). Beachten Sie außerdem, dass "Red" während der Kompilierung in eine Aufzählung umgewandelt wurde.

80
antron

Zum C++ 17 In C++ 20 interessieren Sie sich für die Arbeit der Reflection Study Group (SG7). Es gibt eine parallele Reihe von Artikeln, die wording ( P0194 ) und Begründung, Design und Entwicklung ( P0385 ) abdecken. (Links werden in jeder Serie auf das neueste Papier aufgelöst.)

Ab P0194r2 (2016-10-15) würde die Syntax das vorgeschlagene reflexpr-Schlüsselwort verwenden:

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

Zum Beispiel (angepasst von Matus Chocliks Reflexpr-Zweig von clang ):

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

Statische Überlegungen schafften es nicht nach C++ 17 (eher in den wahrscheinlich endgültigen Entwurf, der auf der Standardsitzung vom November 2016 in Issaquah vorgelegt wurde), es besteht jedoch die Gewissheit, dass es nach C++ 20 kommt. von Herb Sutters Reisebericht :

Insbesondere prüfte die Arbeitsgruppe Reflection den jüngsten Vorschlag der zusammengeführten statischen Reflexion und fand es bereit, bei unserem nächsten Treffen in die Hauptgruppen von Evolution einzutreten, um den einheitlichen Vorschlag für statische Reflexion für einen TS oder den nächsten Standard zu prüfen.

67
ecatmur

2011 habe ich ein Wochenende damit verbracht, eine makro-basierte Lösung zu optimieren und habe sie nie benutzt.

Meine aktuelle Prozedur ist, Vim zu starten, die Enumeratoren in einen leeren Switch-Body zu kopieren, ein neues Makro zu starten, den ersten Enumerator in eine case-Anweisung zu verwandeln, den Cursor an den Anfang der nächsten Zeile zu bewegen, das Makro anzuhalten und generieren Sie die verbleibenden case-Anweisungen, indem Sie das Makro auf den anderen Enumeratoren ausführen. 

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

Praxisbeispiel:

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

Ich werde das schaffen:

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.

Native Unterstützung für die Enumensammlung wäre jedoch viel besser. Ich bin sehr an den Ergebnissen der Reflexionsarbeitsgruppe in C++ 17 interessiert.

Eine Alternative dazu wurde von @sehe in den comments gepostet.

16
StackedCrooked

Ich weiß nicht, ob Ihnen das gefallen wird oder nicht, ich bin nicht sehr zufrieden mit dieser Lösung, aber es ist ein C++ 14-freundlicher Ansatz, da Template-Variablen verwendet werden und die Template-Spezialisierung 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 schwierig ist, einige ähnliche Ansätze aufrechtzuerhalten, oder?

Gute Punkte zu diesem Ansatz:

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

Live example

Bearbeiten

Geheimnisvoll ser673679 Sie haben Recht; Der C++ 14-Ansatz mit variablen Vorlagen behandelt den Laufzeitfall nicht. Es war meine Schuld, ihn zu vergessen :(

Wir können jedoch immer noch einige moderne C++ - Funktionen und Variablenvorlagen sowie Tricks mit variablen Vorlagen verwenden, um eine Laufzeitübersetzung von einem Aufzählungswert zu einer Zeichenfolge zu erzielen. Dies ist genauso lästig wie die anderen, aber es ist immer noch erwähnenswert.

Beginnen wir mit der Verwendung eines Vorlagenalias, um den Zugriff auf eine Enum-to-String-Map 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 die Variadic Template Trickery:

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 Map, die die Werte und Namen jedes Enum-Eintrags enthält. Diese Map ist in jeder Übersetzungseinheit gleich und hat überall den gleichen Namen. Wenn wir die Funktion initialize folgendermaßen aufrufen, ist dies ziemlich einfach und übersichtlich:

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

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

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

Kann aber mit SFINAE und dem Überladen des Operators << 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 << können wir die Aufzählung jetzt folgendermaßen verwenden:

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

Dies ist auch mühsam zu warten und kann verbessert werden, aber ich hoffe, Sie bekommen die Idee.

Live example

11
PaperBirdMaster

Wenn Ihre enum so aussieht

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

Sie können den Inhalt der enum in eine neue Datei verschieben:

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

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

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

Im nächsten Schritt können Sie die Elemente erneut in die Variable enum einfügen:

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

Und schließlich können Sie Hilfsfunktionen zu dieser enum erzeugen:

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 viel Aufwand und Wartung viel Code zu erzeugen.

6
eferion

Ich hatte vor ein paar Tagen das gleiche Problem. Ich konnte keine C++ - Lösung ohne etwas seltsame Makromagie finden. Daher entschied ich mich, a einen CMake-Codegenerator zu schreiben, um einfache Anweisungen für den Switch-Fall 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 bereitgestellten Include-Verzeichnisse), liest sie und führt einige reguläre Ausdrücke aus, um die Klasse und die Funktion (en) zu generieren.

ANMERKUNG: constexpr impliziert Inline in C++. Die Verwendung der USE_CONSTEXPR-Option generiert daher nur eine Header-Klasse!

Beispiel:

./includes/a.h:

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 bereichsabhängige Aufzählungen (enum class | struct) und Ich habe es mit einigen anderen Skripts, die ich häufig verwende, in ein separates Repo verschoben: https://github.com/mensinda/cmakeBuildTools

5
Mense

Erzeugen Sie einfach Ihr Enum. Das Schreiben eines Generators für diesen Zweck ist ungefähr fünf Minuten Arbeit.

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

Es ist auch sehr einfach zu erweitern, je nachdem, welche Funktionalität Sie wünschen.

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

erzeugte 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 die Portierung und Erweiterung. Dieser Beispielcode versucht wirklich, das Überschreiben von Dateien zu vermeiden, verwendet sie jedoch auf eigene Gefahr.

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 ein Port zu Python 3.5, weil er anders genug ist, um potenziell 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()
4
yeoman

Ich habe die Idee von @antron übernommen und anders umgesetzt: eine wahre enum-Klasse generiert. 

Diese Implementierung erfüllt alle in der ursprünglichen Frage aufgeführten Anforderungen, hat jedoch derzeit nur ein echte Einschränkung: Sie geht davon aus, dass die Aufzählungswerte entweder nicht angegeben sind oder, falls angegeben, mit 0 beginnen und ohne Lücken sequentiell aufwärts gehen müssen. 

Dies ist keine intrinsische Einschränkung - ich verwende einfach keine Ad-hoc-Enummenwerte. Wenn dies erforderlich ist, können Sie die Vektor-Suche durch die herkömmliche Implementierung von Switch/Case ersetzen. 

Die Lösung verwendet einige C++ 17 für Inline-Variablen. Dies kann jedoch bei Bedarf leicht vermieden werden. Es verwendet auch boost: trim aus Gründen der Einfachheit. 

Am wichtigsten ist jedoch, dass nur 30 Zeilen Code und keine schwarzen Magie-Makros benötigt werden. Der Code ist unten. Es soll in Header eingefügt und in mehrere Kompilierungsmodule aufgenommen werden.

Es kann auf dieselbe Weise wie zuvor in diesem Thread vorgeschlagen verwendet werden:

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

Wie auf Anfrage vom OP, hier eine abgespeckte Version der hässlichen Makrolösung basierend auf Boost Preprosessor und Variadic Macros

Es ermöglicht eine einfache listeähnliche Syntax der Enumerator-Elemente sowie die Einstellung von Werten für bestimmte Elemente

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

erweitert zu

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

Zusammen mit den notwendigen Funktionen zur Ausgabe und Rückwandlung. Dieses Makro gibt es schon seit Ewigkeiten hier, und ich bin nicht ganz sicher, ob es der effizienteste Weg ist oder dass es ein konformer Weg ist, aber es funktioniert seitdem

Der vollständige Code ist unter Ideone und Coliru in Aktion zu sehen. 

Ihre gigantische Hässlichkeit ist oben; Ich hätte es hinter Spoilern versteckt, um deine Augen zu schützen, wenn ich wüsste, 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';
}

Zusammenstellung (Kopieren Sie den Einfügekopf innerhalb von 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
3
PlasmaHH

Die Bibliothek magic_enum bietet die Funktionen "Enum-to-String" und "String-to-Enum", besteht aus einer einzigen Nur-Header-Datei und verwendet die Funktionen von C++ - 17. Beispielnutzung aus der Liesmich:

_#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
};
_
2
Romeo Valentin

Dieses Problem hat mich auch lange Zeit frustriert, zusammen mit dem Problem, dass ein Typ richtig in String konvertiert wird. Für das letzte Problem war ich jedoch von der in erläuterten Lösung überrascht. Ist es möglich, den Typ einer Variablen in Standard-C++ zu drucken? , unter Verwendung der Idee von Kann ich C++ - Typnamen auf eine constexpr-Weise erhalten? . Mit dieser Technik kann eine analoge Funktion konstruiert werden, um einen Aufzählungswert als String zu erhalten:

#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. Er sollte jedoch an andere Compiler anpassbar sein, indem er mit den Integer-Konstanten etwas fummelt. Natürlich verwendet es immer noch Makros unter der Haube, aber zumindest benötigt man keinen Zugriff auf die Enum-Implementierung.

2
Ignace

Ich bin nicht sicher, ob dieser Ansatz bereits in einer der anderen Antworten enthalten ist (tatsächlich ist es so, 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 entschied ich mich, 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 sollte drucken

ONE
TWO
13

Ich bin kein Fan von Makros. Wenn C++ jedoch nicht nativ das Konvertieren von Enumeraten in Strings unterstützt, müssen Sie eine Art Codegenerierung und/oder Makros verwenden (und ich bezweifle, dass dies zu früh geschieht). Ich verwende 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 ist das Definieren und Aufheben von Symbolen, die der Benutzer über ein Include an den X-marco übergeben wird. Die Verwendung ist so

#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 habe es bisher nicht gebraucht, aber es sollte einfach sein, Code zu ändern, um das zu ermöglichen. 

Erst nachdem ich das geschrieben hatte, wurde mir klar, dass es ziemlich ähnlich zu Antworten ist . Vielleicht habe ich es vorher gelesen und vielleicht war es die Hauptquelle der Inspiration. Ich konnte X-Makros nicht verstehen, bis ich meine eigenen schrieb;). 

2
user463035818

Solange Sie mit dem Schreiben eines separaten .h/.cpp-Paars für jede abfragbare Enumeration zufrieden sind, arbeitet diese Lösung mit fast derselben Syntax und den gleichen Fähigkeiten wie eine normale C++ - Enumeration:

// 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 .cpp-Datei besteht aus drei Zeilen Boilerplate:

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

Verwendungsbeispiel:

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

Bei dieser Implementierung wird die Tatsache ausgenutzt, dass die Liste der Elemente einer Enumendefinition als geschweifte Initialisierungsliste für die Initialisierung von Klassenmitgliedern verwendet werden kann.

Wenn ETRAITS im Kontext von EnumTraits.inl ausgewertet wird, wird Zu einer statischen Elementdefinition für die EnumTraits<>-Klasse ausgeweitet. 

Das Makro EDECL wandelt jedes Enumerationsmitglied in Initialisierungslistenwerte um, die anschließend an den Member-Konstruktor übergeben werden, um die Enumerationsinformationen aufzufüllen. 

Die EnumInitGuard-Klasse ist so konzipiert, dass sie die Werte für die Aufzählungsinitialisierung verbraucht und dann zusammenbricht, wodurch eine reine Liste der Aufzählungsdaten verbleibt.

Leistungen

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

* Im Gegensatz zu enums müssen Initialisierer in enum class-Typen, die auf andere Werte derselben Enumeration verweisen, diese Werte vollständig qualifiziert haben

Nachteile

  • Erfordert ein separates .h/.cpp-Paar für jede abfragbare enum
  • Abhängig von gewundener macro und include magic
  • Kleinere Syntaxfehler explodieren in viel größere Fehler
  • Das Definieren von class- oder namespace-Bereichsumgebungen ist nicht trivial
  • Keine Initialisierung der Kompilierzeit

Bemerkungen

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

Der #ifndef ENUM_INCLUDE_MULTI-Block oben in der Header-Datei ist ein geringfügiger Ärger, der möglicherweise zu einem Makro oder etwas reduziert werden könnte, er ist jedoch klein genug, um in seiner aktuellen Größe zu leben.

Das Deklarieren eines Enumments mit Namespace-Bereich erfordert, dass das Enum zuerst innerhalb seines Namespace-Bereichs vorwärts deklariert und anschließend im globalen Namespace definiert wird. Außerdem müssen alle Aufzählungsinitialisierer, die Werte derselben Aufzählung verwenden, diese Werte vollständig qualifiziert haben.

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
Jason Lim

Meine Antwort ist hier.

Mit dieser Bibliothek (von mir erstellt) können Sie Namen von Enum-Konstanten abrufen: https://github.com/Neargye/nameof

// Name of enum
auto c = Color::RED;
NAMEOF_ENUM(c) -> "RED"
// Name of enum
nameof::nameof_enum(c) -> "RED"

constexpr auto cx_name = NAMEOF_ENUM(c);
static_assert("RED" == cx_name);

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

2
Neargye

Dies Gist liefert ein einfaches Mapping basierend auf C++ - Variadic-Templates.

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

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

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

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

Eine Beispielverwendung:

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;

Der map<KeyValues...> Kann in beide Richtungen verwendet werden:

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

Dieses Beispiel finden Sie unter godbolt.org

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

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

main:
        mov     eax, 3
        ret
1
hutorny

Lösungen mit enum innerhalb von Klassen/Strukturen (Strukturvorgaben bei öffentlichen Mitgliedern) und überladenen 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 genau aus wie eine Klasse:

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

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

Dadurch wird "rot 1 2" ausgegeben. 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). Dies funktioniert jedoch nicht mit Color :: GREEN, da es nicht automatisch in Color konvertiert wird.

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

Color color;
switch (color) ...

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

int i = color;

Mit einer Enumenklasse würde sie nicht kompilieren. Sie sollten vorsichtig sein, wenn Sie zwei Funktionen überladen, die die Enumeration und eine Ganzzahl verwenden, oder die implizite Konvertierung entfernen ...

Eine andere Lösung würde die Verwendung einer tatsächlichen Enum-Klasse und statischer Member umfassen:

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 deshalb verwenden!

Das ist sicherlich ein Overhead, aber ich denke, es ist einfach einfacher und sieht besser aus als anderer Code, den ich gesehen habe. Es besteht auch die Möglichkeit, Funktionalität hinzuzufügen, die alle innerhalb der Klasse liegen könnte.

Edit : 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;
};
1
Pat-Laugh

Sehr einfache Lösung mit einer großen Einschränkung: Sie können enum-Werten keine benutzerdefinierten Werte zuweisen, aber mit der richtigen Regex können Sie dies tun. Sie können auch eine Karte hinzufügen, um sie ohne viel Aufwand wieder in enum-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);
1
malem

BEARBEITEN: siehe unten für eine neuere Version

Wie bereits erwähnt, ist N4113 die endgültige Lösung für diese Angelegenheit, 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äprozessorzauber 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_counter wird in jeder Namespace-Deklaration auf 0 gesetzt.
(Könnte mich jemand darauf hinweisen, wo dieses Verhalten auf der Norm erwähnt wird?)
.__ Die Präprozessor-Magie automatisiert die Deklaration von Enumeratoren.

Nachteile

  • Es handelt sich nicht um einen echten enum-Typ, daher nicht auf int aufrufbar
  • Kann nicht in Schalterfällen verwendet werden

Alternative Lösung

Dieses opfert die Zeilennummerierung (nicht wirklich) , kann aber für Schalterfä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 0 steht im Konflikt mit -pedantic in GCC und clang.

Workaround

Beginnen Sie entweder bei #line 1 und subtrahieren Sie 1 von __LINE__.
Oder -pedantic nicht verwenden.
Und wenn wir schon dabei sind, vermeiden Sie VC++ um jeden Preis, es war schon immer ein Scherz 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;
}

Realistische Implementierung und Verwendung

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Kurzübersicht

#line Leineno - cppreference.com

1
bit2shift

Sie können eine Reflexionsbibliothek verwenden, z. B. Ponder

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

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

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

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

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

Für die Konvertierung von enum in std::string können wir das Enum einfach in size_t umwandeln und die Zeichenfolge aus dem Array nachschlagen. Die Operation ist O(1) und erfordert keine Speicherzuordnung.

#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 die Konvertierung von std::string in enum müssten wir eine lineare Suche über das Array durchführen und den Arrayindex in enum umwandeln.

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

Edit: Meine Lösung wurde überarbeitet, sodass das benutzerdefinierte Enum innerhalb einer Klasse verwendet werden kann.

1
FKaria

meine Lösung ist ohne Makro-Nutzung.

vorteile:

  • sie sehen genau, was Sie tun
  • der Zugriff ist mit Hash-Maps möglich, also für viele geschätzte Aufzählungen gut
  • keine Notwendigkeit, Reihenfolge oder nicht aufeinanderfolgende Werte zu berücksichtigen
  • die Enum-to-String- und die String-to-Enum-Übersetzung müssen hinzugefügt werden, während der hinzugefügte Aufzählungswert 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 den String-Fall berücksichtigen
  • wartung, wenn das Hinzufügen von Werten schmerzhaft ist - muss sowohl in der Enumeration als auch in der Direktübersetzungskarte hinzugefügt werden

also ... bis zu dem Tag, an dem C++ die C # -Enum.Parse-Funktionalität implementiert, werde ich dabei bleiben:

            #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
Mia Shani

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

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

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 aufbewahrt:

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

0
TarmoPikaro

Ich habe eine Bibliothek geschrieben, um dieses Problem zu lösen. Alles geschieht in der Kompilierzeit, mit Ausnahme der Nachricht.

Verwendungszweck:

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

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

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

Verwenden Sie get_message() oder einfach gm(), um die Nachricht abzurufen:

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

Verwenden Sie MSG_NUM, um herauszufinden, wie viele Makros definiert wurden. Dies wird automatisch erhöht, 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 Kompilierzeit. In message_def.h erzeugt es eine enum namens MSG_CODE; In message_def.c erzeugt es eine Variable, die alle Zeichenfolgen in static const char* _g_messages[] enthält.

In diesem Fall kann die Bibliothek nur eine enum erstellen. 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 gefällt, ist, dass Sie Nachrichtendefinitionen in verschiedenen Dateien verwalten können.


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

0
Madwyn

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;
0
Shark8

Meine Antwort ist hier. 

Sie können die Namen der Aufzählungswerte und diese Indizes gleichzeitig als Zeichenkette erhalten. 

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

Das erhaltene Ergebnis erfordert eine Typumwandlung von size_t zu einem Enum-Klassentyp, wenn Sie einen Enum-Klassentypwert benötigen, aber ich denke, es ist eine sehr tragbare und leistungsstarke Methode, um Enum-Klasse 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
tensor5375
#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 ENUM_MAKE-Makro generiert automatisch eine 'enum-Klasse' und eine Hilfsklasse mit 'enum-Reflection-Funktion'. 

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 Makro-Code, einen leicht verständlichen Code, geschaffen. 'enum to string', 'string to enum' Performance ist Algorithmus O (1).

Nachteile sind, wenn Sie zum ersten Mal verwendet werden. Die Hilfsklasse für den Vektor und die Map der Enumrelection-Zeichenkette wird initialisiert. -

0
desperado_98