Einfache Möglichkeit, Variablen von Aufzählungstypen als Zeichenfolge in C zu verwenden?


87

Folgendes versuche ich zu tun:

typedef enum { ONE, TWO, THREE } Numbers;

Ich versuche, eine Funktion zu schreiben, die einen Schalterfall ähnlich dem folgenden ausführt:

char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, num); //some way to get the symbolic constant name in here?
    } break;
    default:
      return 0; //no match
  return 1;
}

Gibt es eine Möglichkeit, anstatt in jedem Fall zu definieren, die Enum-Variable wie oben beschrieben festzulegen?

Antworten:


14

Es gibt keine eingebaute Lösung. Der einfachste Weg ist mit einem Array, char*in dem der int-Wert der Aufzählung auf eine Zeichenfolge indiziert, die den beschreibenden Namen dieser Aufzählung enthält. Wenn Sie eine Sparse haben enum(eine, die nicht bei 0 beginnt oder Lücken in der Nummerierung aufweist), bei der einige der intZuordnungen hoch genug sind, um eine Array-basierte Zuordnung unpraktisch zu machen, können Sie stattdessen eine Hash-Tabelle verwenden.


Wenn dies tatsächlich eine linear inkrementierende Liste ist, können Sie einfach das Makro-Tool Ihres Editors verwenden, um jeden der Namen aufzuzeichnen und in eine Zeichenfolge aufzulösen. Es ist wenig zusätzliche Eingabe erforderlich, und Sie müssen zunächst keine Definitionen vornehmen. Ich klicke auf das letzte der kopierten Makros auf Aufnahme, füge danach ein Zitat hinzu und gehe in der nächsten Zeile an dieselbe Stelle. Ich drücke Stop. Ich drücke X mal und mache so viele wie es gibt (oder nur einen Schritt). Ich kann es dann in ein String-Array einschließen.
user2262111

69

Die Technik, etwas sowohl zu einem C-Bezeichner als auch zu einem String zu machen? kann hier verwendet werden.

Wie bei solchen Präprozessor-Inhalten üblich, kann das Schreiben und Verstehen des Präprozessor-Teils schwierig sein. Dazu gehört das Übergeben von Makros an andere Makros und die Verwendung von Operatoren # und ##, aber die Verwendung ist sehr einfach. Ich finde diesen Stil sehr nützlich für lange Aufzählungen, bei denen es sehr mühsam sein kann, dieselbe Liste zweimal zu pflegen.

Werkscode - nur einmal eingegeben, normalerweise in der Kopfzeile versteckt:

enumFactory.h:

// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,

// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;

// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;

/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
  enum EnumType { \
    ENUM_DEF(ENUM_VALUE) \
  }; \
  const char *GetString(EnumType dummy); \
  EnumType Get##EnumType##Value(const char *string); \

/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
  const char *GetString(EnumType value) \
  { \
    switch(value) \
    { \
      ENUM_DEF(ENUM_CASE) \
      default: return ""; /* handle input error */ \
    } \
  } \
  EnumType Get##EnumType##Value(const char *str) \
  { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; /* handle input error */ \
  } \

Werkseitig verwendet

someEnum.h:

#include "enumFactory.h"
#define SOME_ENUM(XX) \
    XX(FirstValue,) \
    XX(SecondValue,) \
    XX(SomeOtherValue,=50) \
    XX(OneMoreValue,=100) \

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp:

#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)

Die Technik kann leicht erweitert werden, sodass XX-Makros mehr Argumente akzeptieren, und Sie können auch mehr Makros vorbereitet haben, um XX für unterschiedliche Anforderungen zu ersetzen, ähnlich den drei, die ich in diesem Beispiel bereitgestellt habe.

Vergleich mit X-Makros mit #include / #define / #undef

Dies ähnelt zwar den X-Makros, die andere erwähnt haben, aber ich denke, diese Lösung ist eleganter, da sie kein #undefing erfordert, wodurch Sie mehr von den komplizierten Dingen verstecken können, die in der Fabrik die Header-Datei - die Header-Datei - enthält ist etwas, das Sie überhaupt nicht berühren, wenn Sie eine neue Aufzählung definieren müssen, daher ist die neue Aufzählungsdefinition viel kürzer und sauberer.


2
Ich bin mir nicht sicher, wie man sagen kann, dass dies besser / schlechter ist als X-Makros - das sind X-Makros. Das SOME_ENUM(XX)ist genau ein X-Makro (um genau zu sein, das "Benutzerformular", das die XXFunktion übergibt , anstatt sie zu verwenden #def #undef), und dann wird wiederum das gesamte X-MACRO an DEFINE_ENUM übergeben, das es verwendet. Der Lösung nichts wegzunehmen - es funktioniert gut. Nur um zu verdeutlichen, dass es sich um eine Verwendung von X-Makros handelt.
BeeOnRope

1
@BeeOnRope Der Unterschied, den Sie feststellen, ist erheblich und unterscheidet diese Lösung von idiomatischen X-Makros (wie den Beispielen von Wikipedia ). Der Vorteil der Weitergabe XXüber Wieder #defineing ist , dass das frühere Muster in Makroerweiterungen verwendet werden kann. Beachten Sie, dass die einzigen anderen Lösungen, die so präzise sind wie diese, die Erstellung und mehrfache Aufnahme einer separaten Datei erfordern, um eine neue Aufzählung zu definieren.
pmttavara

1
Ein weiterer Trick besteht darin, den Aufzählungsnamen als Makronamen zu verwenden. Sie können einfach schreiben #define DEFINE_ENUM(EnumType) ..., ersetzen Sie ENUM_DEF(...)mit EnumType(...)und den Benutzer zu sagen haben #define SomeEnum(XX) .... Der C-Präprozessor wird kontextuell SomeEnumin den Makroaufruf erweitert, wenn Klammern folgen, andernfalls in ein reguläres Token. (Dies verursacht natürlich Probleme, wenn der Benutzer lieber in SomeEnum(2)den Aufzählungstyp als in (SomeEnum)2oder static_cast<SomeEnum>(2)
umwandeln möchte

1
@pmttavara - sicher, wenn eine Schnellsuche ein Hinweis ist, verwendet die häufigste Verwendung von x-Makros einen festen inneren Makronamen zusammen mit #defineund #undef. Stimmen Sie jedoch nicht zu, dass das "Benutzerformular" (vorgeschlagen zum Beispiel am Ende dieses Artikels ) eine Art X-Makro ist? Ich habe es sicherlich auch immer als x-Makro bezeichnet und in den C-Codebasen, in denen ich in letzter Zeit war, ist es die häufigste Form (das ist offensichtlich eine voreingenommene Beobachtung). Möglicherweise habe ich das OP falsch analysiert.
BeeOnRope

2
@BeeOnRope Der aktuelle Wortlaut ist ein Ergebnis der Bearbeitung, da Sie mich damals überzeugt haben, dass dies ein X-Makro ist, auch wenn es damals vielleicht weniger häufig verwendet wurde (oder mindestens eine weniger in Artikeln erwähnte).
Suma

62
// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
    ENUM(ONE),
    ENUM(TWO),
    ENUM(FOUR)
ENUM_END( Numbers )

// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };

// Now in one and only one .c file, redefine the ENUM macros and reinclude
//  the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED   // whatever you need to do to enable reinclusion
#include "numbers.h"

// Now you can do exactly what you want to do, with no retyping, and for any
//  number of enumerated types defined with the ENUM macro family
//  Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
    } break;
    default:
      return 0; //no match
  return 1;
}

// Sweet no ? After being frustrated by this for years, I finally came up
//  with this solution for my most recent project and plan to reuse the idea
//  forever

3
Dies ist die Art von Dingen, für die cpp gemacht wurde. +1.
Derrick Turk

5
Dies ist eine gute Antwort, es scheint ungefähr das Beste zu sein, was man ohne Spezialwerkzeuge tun kann, und ich habe so etwas schon einmal gemacht. aber es fühlt sich immer noch nie wirklich 'richtig' an und ich mache es nie wirklich gerne ...
Michael Burr

Kleine Änderung: #define ENUM_END(typ) }; extern const char * typ ## _name_table[];in defs.hDatei - Dadurch wird Ihre Namenstabelle in den von Ihnen verwendeten Dateien deklariert. (Ich kann jedoch keinen guten Weg finden, um die Tabellengröße zu deklarieren.) Auch persönlich würde ich das letzte Semikolon weglassen, aber die Vorzüge sind in beiden Fällen umstritten.
Chris Lutz

1
@ Bill, warum sich mit typin der Leitung beschäftigen #define ENUM_END(typ) };?
Pacerier

Dies funktioniert nicht, wenn mein Makro als "ONE = 5" definiert werden soll
UKMonkey

13

Es gibt definitiv eine Möglichkeit, dies zu tun - verwenden Sie X () -Makros . Diese Makros verwenden den C-Präprozessor, um Aufzählungen, Arrays und Codeblöcke aus einer Liste von Quelldaten zu erstellen. Sie müssen der #define, die das X () -Makro enthält, nur neue Elemente hinzufügen. Die switch-Anweisung wird automatisch erweitert.

Ihr Beispiel kann wie folgt geschrieben werden:

 // Source data -- Enum, String
 #define X_NUMBERS \
    X(ONE,   "one") \
    X(TWO,   "two") \
    X(THREE, "three")

 ...

 // Use preprocessor to create the Enum
 typedef enum {
  #define X(Enum, String)       Enum,
   X_NUMBERS
  #undef X
 } Numbers;

 ...

 // Use Preprocessor to expand data into switch statement cases
 switch(num)
 {
 #define X(Enum, String) \
     case Enum:  strcpy(num_str, String); break;
 X_NUMBERS
 #undef X

     default: return 0; break;
 }
 return 1;

Es gibt effizientere Möglichkeiten (z. B. die Verwendung von X-Makros zum Erstellen eines String-Arrays und eines Enum-Index), dies ist jedoch die einfachste Demo.


8

Ich weiß, dass Sie ein paar gute, solide Antworten haben, aber kennen Sie den Operator # im C-Präprozessor?

Damit können Sie Folgendes tun:

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}

char const *kConstStr[]
Anne van Rossum

6

C oder C ++ bietet diese Funktionalität nicht, obwohl ich sie oft gebraucht habe.

Der folgende Code funktioniert, obwohl er am besten für nicht spärliche Aufzählungen geeignet ist.

typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);

Mit nicht spärlich meine ich nicht von der Form

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

da hat das große lücken.

Der Vorteil dieser Methode besteht darin, dass die Definitionen der Aufzählungen und Zeichenfolgen nahe beieinander liegen. Eine switch-Anweisung in einer Funktion speariert sie. Dies bedeutet, dass Sie weniger wahrscheinlich eines ohne das andere ändern.


6

KUSS. Sie werden alle möglichen anderen Switch / Case-Dinge mit Ihren Aufzählungen tun. Warum sollte das Drucken anders sein? Das Vergessen eines Falls in Ihrer Druckroutine ist keine große Sache, wenn Sie bedenken, dass es ungefähr 100 andere Stellen gibt, an denen Sie einen Fall vergessen können. Kompilieren Sie einfach -Wall, um vor nicht erschöpfenden Fallübereinstimmungen zu warnen. Verwenden Sie nicht "Standard", da dies den Schalter erschöpfend macht und Sie keine Warnungen erhalten. Lassen Sie stattdessen den Schalter beenden und behandeln Sie den Standardfall wie folgt ...

const char *myenum_str(myenum e)
{
    switch(e) {
    case ONE: return "one";
    case TWO: return "two";
    }
    return "invalid";
}


4

Die Verwendung von boost :: preprocessor ermöglicht eine elegante Lösung wie die folgende:

Schritt 1: Fügen Sie die Header-Datei hinzu:

#include "EnumUtilities.h"

Schritt 2: Deklarieren Sie das Aufzählungsobjekt mit der folgenden Syntax:

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

Schritt 3: Verwenden Sie Ihre Daten:

Ermitteln der Anzahl der Elemente:

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

Abrufen der zugehörigen Zeichenfolge:

std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl;
std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl;
std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl;

Abrufen des Aufzählungswerts aus der zugehörigen Zeichenfolge:

std::cout << "Value of x is " << TestData2Enum("x") << std::endl;
std::cout << "Value of y is " << TestData2Enum("y") << std::endl;
std::cout << "Value of z is " << TestData2Enum("z") << std::endl;

Dies sieht sauber und kompakt aus, ohne dass zusätzliche Dateien enthalten sein müssen. Der Code, den ich in EnumUtilities.h geschrieben habe, lautet wie folgt:

#include <boost/preprocessor/seq/for_each.hpp>
#include <string>

#define REALLY_MAKE_STRING(x) #x
#define MAKE_STRING(x) REALLY_MAKE_STRING(x)
#define MACRO1(r, data, elem) elem,
#define MACRO1_STRING(r, data, elem)    case elem: return REALLY_MAKE_STRING(elem);
#define MACRO1_ENUM(r, data, elem)      if (REALLY_MAKE_STRING(elem) == eStrEl) return elem;


#define MakeEnum(eName, SEQ) \
    enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \
    last_##eName##_enum}; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    }; \
    static enum eName eName##2Enum(const std::string eStrEl) \
    { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \
        return (enum eName)0; \
    };

Es gibt einige Einschränkungen, z. B. die von boost :: preprocessor. In diesem Fall darf die Liste der Konstanten nicht größer als 64 Elemente sein.

Nach der gleichen Logik könnten Sie auch daran denken, eine spärliche Aufzählung zu erstellen:

#define EnumName(Tuple)                 BOOST_PP_TUPLE_ELEM(2, 0, Tuple)
#define EnumValue(Tuple)                BOOST_PP_TUPLE_ELEM(2, 1, Tuple)
#define MACRO2(r, data, elem)           EnumName(elem) EnumValue(elem),
#define MACRO2_STRING(r, data, elem)    case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem));

#define MakeEnumEx(eName, SEQ) \
    enum eName { \
    BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \
    last_##eName##_enum }; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    };  

In diesem Fall lautet die Syntax:

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

Die Verwendung ist ähnlich wie oben (abzüglich der Funktion eName ## 2Enum, die Sie versuchen könnten, aus der vorherigen Syntax zu extrapolieren).

Ich habe es auf Mac und Linux getestet, aber beachten Sie, dass boost :: preprocessor möglicherweise nicht vollständig portierbar ist.


3

Durch das Zusammenführen einiger Techniken hier habe ich die einfachste Form gefunden:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}

2

Wenn Sie gcc verwenden, können Sie Folgendes verwenden:

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

Dann rufen Sie einfach zum Beispiel an

enum_to_string_map[enum1]

1

Schauen Sie sich die Ideen im Mu Dynamics Research Labs - Blog Archive an . Ich habe dies Anfang dieses Jahres gefunden - ich vergesse den genauen Kontext, in dem ich darauf gestoßen bin - und habe es in diesen Code angepasst. Wir können die Vorzüge des Hinzufügens eines E an der Front diskutieren. Es gilt für das jeweilige Problem, ist jedoch nicht Teil einer allgemeinen Lösung. Ich habe dies in meinem 'Vignetten'-Ordner aufbewahrt - wo ich interessante Codefetzen aufbewahre, falls ich sie später haben möchte. Es ist mir peinlich zu sagen, dass ich nicht notiert habe, woher diese Idee damals kam.

Header: paste1.h

/*
@(#)File:           $RCSfile: paste1.h,v $
@(#)Version:        $Revision: 1.1 $
@(#)Last changed:   $Date: 2008/05/17 21:38:05 $
@(#)Purpose:        Automated Token Pasting
*/

#ifndef JLSS_ID_PASTE_H
#define JLSS_ID_PASTE_H

/*
 * Common case when someone just includes this file.  In this case,
 * they just get the various E* tokens as good old enums.
 */
#if !defined(ETYPE)
#define ETYPE(val, desc) E##val,
#define ETYPE_ENUM
enum {
#endif /* ETYPE */

   ETYPE(PERM,  "Operation not permitted")
   ETYPE(NOENT, "No such file or directory")
   ETYPE(SRCH,  "No such process")
   ETYPE(INTR,  "Interrupted system call")
   ETYPE(IO,    "I/O error")
   ETYPE(NXIO,  "No such device or address")
   ETYPE(2BIG,  "Arg list too long")

/*
 * Close up the enum block in the common case of someone including
 * this file.
 */
#if defined(ETYPE_ENUM)
#undef ETYPE_ENUM
#undef ETYPE
ETYPE_MAX
};
#endif /* ETYPE_ENUM */

#endif /* JLSS_ID_PASTE_H */

Beispielquelle:

/*
@(#)File:           $RCSfile: paste1.c,v $
@(#)Version:        $Revision: 1.2 $
@(#)Last changed:   $Date: 2008/06/24 01:03:38 $
@(#)Purpose:        Automated Token Pasting
*/

#include "paste1.h"

static const char *sys_errlist_internal[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) desc,
#include "paste1.h"
    0
#undef ETYPE
};

static const char *xerror(int err)
{
    if (err >= ETYPE_MAX || err <= 0)
        return "Unknown error";
    return sys_errlist_internal[err];
}

static const char*errlist_mnemonics[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) [E ## val] = "E" #val,
#include "paste1.h"
#undef ETYPE
};

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < ETYPE_MAX; i++)
    {
        printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
    }
    return(0);
}

Dies ist nicht unbedingt die weltweit sauberste Verwendung des C-Vorprozessors - verhindert jedoch, dass das Material mehrmals ausgeschrieben wird.



0

Wenn der Aufzählungsindex auf 0 basiert, können Sie die Namen in ein Array von char * einfügen und mit dem Aufzählungswert indizieren.



0

Ich habe eine einfache Template - Klasse erstellt , streamable_enumdass Anwendungen streamen Operatoren <<und >>und basiert auf der std::map<Enum, std::string>:

#ifndef STREAMABLE_ENUM_HPP
#define STREAMABLE_ENUM_HPP

#include <iostream>
#include <string>
#include <map>

template <typename E>
class streamable_enum
{
public:
    typedef typename std::map<E, std::string> tostr_map_t;
    typedef typename std::map<std::string, E> fromstr_map_t;

    streamable_enum()
    {}

    streamable_enum(E val) :
        Val_(val)
    {}

    operator E() {
        return Val_;
    }

    bool operator==(const streamable_enum<E>& e) {
        return this->Val_ == e.Val_;
    }

    bool operator==(const E& e) {
        return this->Val_ == e;
    }

    static const tostr_map_t& to_string_map() {
        static tostr_map_t to_str_(get_enum_strings<E>());
        return to_str_;
    }

    static const fromstr_map_t& from_string_map() {
        static fromstr_map_t from_str_(reverse_map(to_string_map()));
        return from_str_;
    }
private:
    E Val_;

    static fromstr_map_t reverse_map(const tostr_map_t& eToS) {
        fromstr_map_t sToE;
        for (auto pr : eToS) {
            sToE.emplace(pr.second, pr.first);
        }
        return sToE;
    }
};

template <typename E>
streamable_enum<E> stream_enum(E e) {
    return streamable_enum<E>(e);
}

template <typename E>
typename streamable_enum<E>::tostr_map_t get_enum_strings() {
    // \todo throw an appropriate exception or display compile error/warning
    return {};
}

template <typename E>
std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) {
    auto& mp = streamable_enum<E>::to_string_map();
    auto res = mp.find(e);
    if (res != mp.end()) {
        os << res->second;
    } else {
        os.setstate(std::ios_base::failbit);
    }
    return os;
}

template <typename E>
std::istream& operator>>(std::istream& is, streamable_enum<E>& e) {
    std::string str;
    is >> str;
    if (str.empty()) {
        is.setstate(std::ios_base::failbit);
    }
    auto& mp = streamable_enum<E>::from_string_map();
    auto res = mp.find(str);
    if (res != mp.end()) {
        e = res->second;
    } else {
        is.setstate(std::ios_base::failbit);
    }
    return is;
}

#endif

Verwendung:

#include "streamable_enum.hpp"

using std::cout;
using std::cin;
using std::endl;

enum Animal {
    CAT,
    DOG,
    TIGER,
    RABBIT
};

template <>
streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() {
    return {
        { CAT, "Cat"},
        { DOG, "Dog" },
        { TIGER, "Tiger" },
        { RABBIT, "Rabbit" }
    };
}

int main(int argc, char* argv []) {
    cout << "What animal do you want to buy? Our offering:" << endl;
    for (auto pr : streamable_enum<Animal>::to_string_map()) {          // Use from_string_map() and pr.first instead
        cout << " " << pr.second << endl;                               // to have them sorted in alphabetical order
    }
    streamable_enum<Animal> anim;
    cin >> anim;
    if (!cin) {
        cout << "We don't have such animal here." << endl;
    } else if (anim == Animal::TIGER) {
        cout << stream_enum(Animal::TIGER) << " was a joke..." << endl;
    } else {
        cout << "Here you are!" << endl;
    }

    return 0;
}

0

Hier ist eine Lösung mit Makros mit den folgenden Funktionen:

  1. Schreiben Sie jeden Wert der Aufzählung nur einmal, sodass keine doppelten Listen gepflegt werden müssen

  2. Bewahren Sie die Enum-Werte nicht in einer separaten Datei auf, die später # eingeschlossen wird, damit ich sie schreiben kann, wo immer ich will

  3. Ersetzen Sie nicht die Aufzählung selbst, ich möchte immer noch den Aufzählungstyp definieren, aber zusätzlich möchte ich in der Lage sein, jeden Aufzählungsnamen der entsprechenden Zeichenfolge zuzuordnen (um keinen Einfluss auf den Legacy-Code zu haben).

  4. Die Suche sollte schnell sein, also vorzugsweise kein Switch-Case für diese riesigen Aufzählungen

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


0

Ich dachte, dass eine Lösung wie Boost.Fusion eine für die Anpassung von Strukturen und Klassen wäre schön, sie hatten es sogar irgendwann, Aufzählungen als Fusionssequenz zu verwenden.

Also habe ich nur ein paar kleine Makros erstellt, um den Code zum Drucken der Aufzählungen zu generieren. Dies ist nicht perfekt und hat mit Boost.Fusion-generiertem Boilerplate-Code nichts zu sehen, kann aber wie die Boost Fusion-Makros verwendet werden. Ich möchte wirklich die Typen generieren, die von Boost.Fusion für die Integration in diese Infrastruktur benötigt werden, die das Drucken von Namen von Strukturelementen ermöglicht, aber dies wird später geschehen, im Moment sind dies nur Makros:

#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP

#include <swissarmyknife/detail/config.hpp>

#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>


#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
    R, unused, ENUMERATION_ENTRY)                                               \
    case ENUMERATION_ENTRY:                                                     \
      return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
    break;                                                                      

/**
 * \brief Adapts ENUM to reflectable types.
 *
 * \param ENUM_TYPE To be adapted
 * \param ENUMERATION_SEQ Sequence of enum states
 */
#define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ)                   \
    inline std::string to_string(const ENUM_TYPE& enum_value) {                 \
      switch (enum_value) {                                                     \
      BOOST_PP_SEQ_FOR_EACH(                                                    \
          SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C,                   \
          unused, ENUMERATION_SEQ)                                              \
        default:                                                                \
          return BOOST_PP_STRINGIZE(ENUM_TYPE);                                 \
      }                                                                         \
    }                                                                           \
                                                                                \
    inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
      os << to_string(value);                                                   \
      return os;                                                                \
    }

#endif

Die alte Antwort unten ist ziemlich schlecht, bitte benutze das nicht. :) :)

Alte Antwort:

Ich habe nach einem Weg gesucht, der dieses Problem löst, ohne die Syntax der Enums-Deklaration zu stark zu ändern. Ich bin zu einer Lösung gekommen, die den Präprozessor verwendet, um eine Zeichenfolge aus einer stringifizierten Enumerationsdeklaration abzurufen.

Ich kann nicht spärliche Aufzählungen wie folgt definieren:

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

Und ich kann auf verschiedene Arten mit ihnen interagieren:

// With a stringstream
std::stringstream ss;
ss << State::FAULT;
std::string myEnumStr = ss.str();

//Directly to stdout
std::cout << State::FAULT << std::endl;

//to a string
std::string myStr = State::to_string(State::FAULT);

//from a string
State::State myEnumVal = State::from_string(State::FAULT);

Basierend auf folgenden Definitionen:

#define SMART_ENUM(enumTypeArg, ...)                                                     \
namespace enumTypeArg {                                                                  \
    __VA_ARGS__;                                                                         \
    std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) {                 \
            os << swissarmyknife::enums::to_string(#__VA_ARGS__, val);                   \
            return os;                                                                   \
    }                                                                                    \
                                                                                     \
    std::string to_string(const enumTypeArg& val) {                                      \
            return swissarmyknife::enums::to_string(#__VA_ARGS__, val);                  \
    }                                                                                    \
                                                                                     \
    enumTypeArg from_string(const std::string &str) {                                    \
            return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str);   \
    }                                                                                    \
}                                                                                        \


namespace swissarmyknife { namespace enums {

    static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            if (enumVal == count) {
                std::string identifiersSubset = identifiers.substr(0, found);
                size_t beginId = identifiersSubset.find_last_of("{,");
                identifiersSubset = identifiersSubset.substr(beginId+1);
                boost::algorithm::trim(identifiersSubset);
                return identifiersSubset;
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("The enum declaration provided doesn't contains this state.");
    }                                                  

    template <typename EnumType>
    static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            std::string identifiersSubset = identifiers.substr(0, found);
            size_t beginId = identifiersSubset.find_last_of("{,");
            identifiersSubset = identifiersSubset.substr(beginId+1);
            boost::algorithm::trim(identifiersSubset);

            if (identifiersSubset == enumStr) {
                return static_cast<EnumType>(count);
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("No valid enum value for the provided string");
    }                      

}}

Wenn ich Unterstützung für spärliche Aufzählungen benötige und mehr Zeit habe, werde ich die Implementierungen to_string und from_string mit boost :: xpressive verbessern. Dies kostet jedoch Kompilierungszeit, da wichtige Vorlagen erstellt und die ausführbare Datei generiert werden wahrscheinlich wirklich größer sein. Dies hat jedoch den Vorteil, dass es besser lesbar und wartbar ist als dieser hässliche manuelle Code zur Manipulation von Zeichenfolgen. : D.

Ansonsten habe ich immer boost :: bimap verwendet, um solche Zuordnungen zwischen enums value und string durchzuführen, aber es muss manuell gepflegt werden.


0

Da ich aus den üblichen Gründen keine Makros verwenden möchte, habe ich eine eingeschränktere Makrolösung verwendet, die den Vorteil hat, dass das Makro für die Aufzählungsdeklaration frei bleibt. Zu den Nachteilen gehört das Kopieren, Einfügen der Makrodefinition für jede Aufzählung und das explizite Hinzufügen eines Makroaufrufs beim Hinzufügen von Werten zur Aufzählung.

std::ostream& operator<<(std::ostream& os, provenance_wrapper::CaptureState cs)
{
#define HANDLE(x) case x: os << #x; break;
    switch (cs) {
    HANDLE(CaptureState::UNUSED)
    HANDLE(CaptureState::ACTIVE)
    HANDLE(CaptureState::CLOSED)
    }
    return os;
#undef HANDLE
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.