Łatwy sposób używania zmiennych typu wyliczeniowego jako łańcucha w C?


87

Oto, co próbuję zrobić:

typedef enum { ONE, TWO, THREE } Numbers;

Próbuję napisać funkcję, która zrobiłaby przypadek przełącznika podobny do następującego:

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

Zamiast definiować w każdym przypadku, czy istnieje sposób na ustawienie go za pomocą zmiennej wyliczeniowej, tak jak próbuję to zrobić powyżej?

Odpowiedzi:


14

Nie ma wbudowanego rozwiązania. Najłatwiejszym sposobem jest tablica, w char*której wartość int wyliczenia indeksuje ciąg znaków zawierający opisową nazwę tego wyliczenia. Jeśli masz rzadki enum(taki, który nie zaczyna się od 0 lub ma luki w numeracji), w którym niektóre intmapowania są wystarczająco wysokie, aby mapowanie oparte na tablicach było niepraktyczne, możesz zamiast tego użyć tabeli skrótów.


Rozwijając to, jeśli rzeczywiście jest to lista liniowo rosnąca, możesz po prostu użyć narzędzia makr edytora, aby zarejestrować i rozwiązać każdą z nazw w ciąg. Potrzebujesz trochę dodatkowego wpisywania i oszczędzasz przede wszystkim potrzeby definiowania. Klikam rekord na ostatnim ze skopiowanych makr, po czym dodaję cytat i przechodzę w to samo miejsce w następnym wierszu. Wciskam stop. Naciskam run X razy i robię tyle, ile jest (lub tylko jeden krok). Następnie mogę zawinąć go w tablicę ciągów.
user2262111

70

Technika z tworzenia czegoś zarówno identyfikatora C, jak i łańcucha? można użyć tutaj.

Jak zwykle w przypadku takich preprocesorów, pisanie i rozumienie części preprocesora może być trudne i obejmuje przekazywanie makr do innych makr i wymaga użycia operatorów # i ##, ale korzystanie z tego jest naprawdę łatwe. Uważam, że ten styl jest bardzo przydatny w przypadku długich wyliczeń, gdzie dwukrotne utrzymywanie tej samej listy może być naprawdę kłopotliwe.

Kod fabryczny - wpisywany tylko raz, zwykle ukryty w nagłówku:

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

Przemysłowy użytek

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)

Technikę można łatwo rozszerzyć, aby makra XX przyjmowały więcej argumentów, a także można przygotować więcej makr zastępujących XX dla różnych potrzeb, podobnie jak trzy, które podałem w tym przykładzie.

Porównanie z X-Macros przy użyciu #include / #define / #undef

Chociaż jest to podobne do X-Macros, o których wspominali inni, myślę, że to rozwiązanie jest bardziej eleganckie, ponieważ nie wymaga niczego #undefing, co pozwala ukryć więcej skomplikowanych rzeczy w fabryce plik nagłówkowy - plik nagłówkowy jest czymś, czego w ogóle nie dotykasz, gdy musisz zdefiniować nowe wyliczenie, dlatego nowa definicja wyliczenia jest znacznie krótsza i bardziej przejrzysta.


2
Nie jestem pewien, jak możesz powiedzieć, że jest to lepsze / gorsze niż x-macros - to jest x-macros. Jest SOME_ENUM(XX)to dokładnie X-makro (a dokładniej „formularz użytkownika”, który przekazuje XXfunkcję, a nie używa #def #undef), a następnie całe X-MACRO jest następnie przekazywane do DEFINE_ENUM, które go używa. Nie odejmować niczego od rozwiązania - działa dobrze. Wystarczy wyjaśnić, że jest to użycie makr X.
BeeOnRope

1
@BeeOnRope Różnica, którą zauważyłeś, jest znacząca i odróżnia to rozwiązanie od idiomatycznych makr X (takich jak przykłady z Wikipedii ). Zaletą XXpomijania #definereing jest to, że poprzedni wzorzec może być użyty w ekspansjach makro. Zwróć uwagę, że jedyne inne rozwiązania tak zwięzłe, jak to, wymagają utworzenia i wielokrotnego włączenia oddzielnego pliku w celu zdefiniowania nowego wyliczenia.
pmttavara

1
Inną sztuczką jest użycie nazwy wyliczenia jako nazwy makra. Można po prostu napisać #define DEFINE_ENUM(EnumType) ..., wymienić ENUM_DEF(...)się EnumType(...)i mają głosu użytkownika #define SomeEnum(XX) .... Preprocesor C kontekstowo rozwinie SomeEnumsię do wywołania makra, jeśli będzie poprzedzony nawiasami, aw przeciwnym razie do zwykłego tokenu. (Oczywiście powoduje to problemy, jeśli użytkownik lubi SomeEnum(2)rzutować na typ wyliczeniowy zamiast (SomeEnum)2lub static_cast<SomeEnum>(2).)
pmttavara

1
@pmttavara - jasne, jeśli szybkie wyszukiwanie jest jakąś wskazówką, najczęściej używane makra x używają ustalonej nazwy makra wewnętrznego wraz z #definei #undef. Czy nie zgadzasz się jednak z tym, że „formularz użytkownika” (sugerowany np. Na dole tego artykułu ) jest rodzajem makra x? Z pewnością zawsze nazywałam to również makro x, aw bazach kodu C, w których ostatnio byłem, jest to najbardziej powszechna forma (jest to oczywiście tendencyjna obserwacja). Być może źle analizowałem OP.
BeeOnRope

2
@BeeOnRope Obecne sformułowanie jest wynikiem edycji, ponieważ przekonałeś mnie wtedy, że jest to x-makro, nawet jeśli była to być może mniej używana forma (lub przynajmniej jedna mniej wspomniana w artykułach) wtedy.
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
Do tego właśnie stworzono cpp. +1.
Derrick Turk

6
To dobra odpowiedź, wydaje się, że jest to najlepsze, co można zrobić bez użycia specjalnych narzędzi. Robiłem już takie rzeczy wcześniej; ale to nigdy nie wydaje się być „właściwe” i nigdy nie lubię tego robić ...
Michael Burr

Mała zmiana: #define ENUM_END(typ) }; extern const char * typ ## _name_table[];w defs.hpliku - to zadeklaruje tablicę nazw w plikach, których używasz. (Nie mogę znaleźć dobrego sposobu na zadeklarowanie rozmiaru tabeli). Osobiście zostawiłbym także ostatni średnik, ale zalety są dyskusyjne.
Chris Lutz,

1
@Bill, po co zawracać sobie głowę typw kolejce #define ENUM_END(typ) };?
Pacerier,

To nie działa, gdy chcę, aby moje makro było zdefiniowane jako „ONE = 5”
UKMonkey

13

Na pewno jest na to sposób - użyj makr X () . Te makra używają preprocesora C do konstruowania wyliczeń, tablic i bloków kodu z listy danych źródłowych. Musisz tylko dodać nowe elementy do #define zawierającego makro X (). Instrukcja switch rozwijałaby się automatycznie.

Twój przykład można zapisać w następujący sposób:

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

Istnieją bardziej wydajne sposoby (np. Użycie X Macros do tworzenia tablicy ciągów i indeksu wyliczeniowego), ale to jest najprostsze demo.


8

Wiem, że masz kilka dobrych, solidnych odpowiedzi, ale czy wiesz o operatorze # w preprocesorze C?

Pozwala to zrobić:

#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 lub C ++ nie zapewniają tej funkcjonalności, chociaż często jej potrzebowałem.

Poniższy kod działa, chociaż najlepiej nadaje się do nierzadkich wyliczeń.

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

Mówiąc „nie rzadkie”, mam na myśli nie formę

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

ponieważ ma w tym ogromne luki.

Zaletą tej metody jest to, że umieszcza ona definicje wyliczeń i łańcuchów obok siebie; posiadanie instrukcji switch w funkcji powoduje ich spearowanie. Oznacza to, że jest mniej prawdopodobne, że zmienisz jedno bez drugiego.


6

POCAŁUNEK. Będziesz robił wiele innych rzeczy związanych z przełączaniem / przypadkami ze swoimi wyliczeniami, więc dlaczego drukowanie miałoby być inne? Zapomnienie o sprawie w rutynowych czynnościach drukowania nie jest wielkim problemem, biorąc pod uwagę około 100 innych miejsc, w których można zapomnieć o skrzynce. Po prostu skompiluj -Wall, co będzie ostrzegać o niewyczerpujących dopasowaniach wielkości liter. Nie używaj wartości „domyślnej”, ponieważ spowoduje to, że zmiana będzie wyczerpująca i nie otrzymasz ostrzeżeń. Zamiast tego pozwól przełącznikowi wyjść i zajmij się przypadkiem domyślnym w ten sposób ...

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


4

Zastosowanie boost :: preprocessor umożliwia eleganckie rozwiązanie, takie jak:

Krok 1: dołącz plik nagłówkowy:

#include "EnumUtilities.h"

Krok 2: zadeklaruj obiekt wyliczenia z następującą składnią:

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

Krok 3: wykorzystaj swoje dane:

Uzyskanie liczby elementów:

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

Pobieranie powiązanego ciągu:

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;

Pobieranie wartości wyliczenia z powiązanego ciągu:

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;

Wygląda na czysty i kompaktowy, bez dodatkowych plików do dołączenia. Kod, który napisałem w EnumUtilities.h, jest następujący:

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

Istnieją pewne ograniczenia, np. Te z boost :: preprocessor. W tym przypadku lista stałych nie może być większa niż 64 elementy.

Postępując zgodnie z tą samą logiką, możesz również pomyśleć o utworzeniu rzadkiego wyliczenia:

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

W tym przypadku składnia jest następująca:

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

Sposób użycia jest podobny do powyższego (bez funkcji eName ## 2Enum, którą można spróbować ekstrapolować z poprzedniej składni).

Przetestowałem to na Macu i Linuksie, ale pamiętaj, że preprocesor boost :: może nie być w pełni przenośny.


3

Łącząc niektóre z technik tutaj, otrzymałem najprostszą formę:

#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

Jeśli używasz gcc, możesz użyć:

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

Po prostu zadzwoń na przykład

enum_to_string_map[enum1]

1

Sprawdź pomysły w Mu Dynamics Research Labs - Archiwum bloga . Znalazłem to na początku tego roku - zapomniałem dokładnego kontekstu, w którym go znalazłem - i dostosowałem go do tego kodu. Możemy dyskutować o zaletach dodania litery E z przodu; ma zastosowanie do konkretnego problemu, którego dotyczy problem, ale nie jest częścią ogólnego rozwiązania. Schowałem to w moim folderze z winietami - gdzie trzymam interesujące fragmenty kodu na wypadek, gdyby chciał je później. Wstydzę się powiedzieć, że nie zanotowałem wtedy, skąd ten pomysł.

Nagłówek: 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 */

Przykładowe źródło:

/*
@(#)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);
}

Niekoniecznie najczystsze na świecie wykorzystanie preprocesora C - ale zapobiega to wielokrotnemu zapisywaniu materiału.



0

Jeśli indeks wyliczenia jest oparty na 0, można umieścić nazwy w tablicy znaków * i zindeksować je wartością wyliczenia.



0

Stworzyłem prostą klasę matrycy streamable_enumże strumień zastosowania operatorów <<i >>jest w oparciu o 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

Stosowanie:

#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

Oto rozwiązanie wykorzystujące makra z następującymi funkcjami:

  1. zapisuj każdą wartość wyliczenia tylko raz, więc nie ma podwójnych list do obsługi

  2. nie przechowuj wartości wyliczenia w osobnym pliku, który jest później # uwzględniony, więc mogę zapisać go w dowolnym miejscu

  3. nie zastępuj samego wyliczenia, nadal chcę mieć zdefiniowany typ wyliczenia, ale oprócz tego chcę mieć możliwość mapowania każdej nazwy wyliczenia na odpowiedni ciąg (aby nie wpływać na starszy kod)

  4. wyszukiwanie powinno być szybkie, więc najlepiej bez przełączników dla tych ogromnych wyliczeń

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


0

Pomyślałem, że rozwiązanie takie jak Boost.Fusion do dostosowywania struktur i klas byłoby fajne, mieli nawet w pewnym momencie możliwość użycia wyliczeń jako sekwencji fusion.

Zrobiłem więc tylko kilka małych makr, aby wygenerować kod do wydrukowania wyliczeń. To nie jest idealne i nie ma nic do zobaczenia z kodem standardowym wygenerowanym przez Boost.Fusion, ale może być używane jak makra Boost Fusion. Naprawdę chcę generować typy potrzebne Boost.Fusion do integracji w tej infrastrukturze, która pozwala drukować nazwy członków struktury, ale stanie się to później, na razie to tylko makra:

#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

Stara odpowiedź poniżej jest dość zła, nie używaj jej. :)

Stara odpowiedź:

Szukałem sposobu, który rozwiązuje ten problem bez zbytniej zmiany składni deklaracji wyliczeń. Doszedłem do rozwiązania, które używa preprocesora do pobrania ciągu znaków ze stringified enum.

Jestem w stanie zdefiniować nierzadkie wyliczenia w następujący sposób:

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

Mogę wchodzić z nimi w interakcje na różne sposoby:

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

Na podstawie następujących definicji:

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

}}

Kiedy będę potrzebował wsparcia rzadki enum i kiedy będę miał więcej czasu będę poprawić to_string i from_string implementacje z boost :: Xpressive, ale koszty będą w czasie kompilacji ze względu na ważny szablonów wykonywane i generowany wykonywalne prawdopodobnie będzie naprawdę większy. Ma to jednak tę zaletę, że będzie bardziej czytelny i łatwiejszy w utrzymaniu niż ten brzydki kod ręcznej manipulacji na ciągach.:RE

W przeciwnym razie zawsze używałem boost :: bimap do wykonywania takich mapowań między wartością wyliczeń a ciągiem znaków, ale musi to być obsługiwane ręcznie.


0

Ponieważ wolę nie używać makr ze wszystkich zwykłych powodów, użyłem bardziej ograniczonego rozwiązania makr, które ma tę zaletę, że makra deklaracji wyliczenia są wolne. Wady obejmują konieczność kopiowania i wklejania definicji makra dla każdego wyliczenia oraz konieczność jawnego dodawania wywołania makra podczas dodawania wartości do wyliczenia.

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
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.