Простий спосіб використовувати змінні типів переліку як рядок у C?


87

Ось що я намагаюся зробити:

typedef enum { ONE, TWO, THREE } Numbers;

Я намагаюся написати функцію, яка робила б випадок перемикання, подібний до наступного:

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

Замість визначення в кожному випадку, чи є спосіб встановити його за допомогою змінної enum, як я намагаюся зробити вище?

Відповіді:


14

Вбудованого рішення немає. Найпростіший спосіб - це масив, char*де значення int перерахування індексується у рядок, що містить описове ім’я цього переліку. Якщо у вас є розріджений enum(той, який не починається з 0 або має прогалини в нумерації), де деякі intзіставлення є досить високими, щоб зробити відображення на основі масиву непрактичним, тоді замість цього ви можете використовувати хеш-таблицю.


Розширюючи це, якщо це справді список лінійно зростаючих, ви можете просто скористатися інструментом макросу вашого редактора, щоб записати та вирішити кожне з імен у рядок. Потрібно трохи додаткового набору тексту, і ви позбавляєтеся від необхідності визначати. Я натискаю запис на останньому з скопійованих макросів, додаю лапку після і переходжу до того самого місця в наступному рядку. Я натискаю на зупинку. Я натискаю пробіг X разів і роблю скільки завгодно багато (або лише один крок). Потім я можу обернути його в масив рядків.
user2262111

70

Техніка створення чогось одночасно ідентифікатора C та рядка? можна використовувати тут.

Як зазвичай з такими матеріалами препроцесора, написання та розуміння частини препроцесора може бути важким, і включає передачу макросів іншим макросам і передбачає використання операторів # та ##, але використовувати це дуже просто. Я вважаю цей стиль дуже корисним для тривалих перерахувань, де ведення одного списку двічі може бути дуже складним.

Заводський код - набирається лише один раз, зазвичай прихований у заголовку:

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

Фабрика використовується

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)

Метод можна легко розширити, щоб макроси XX приймали більше аргументів, а також ви могли підготувати більше макросів, щоб замінити XX для різних потреб, подібно до трьох, які я подав у цьому зразку.

Порівняння з X-Macros за допомогою #include / #define / #undef

Хоча це схоже на X-Macros, про які вже згадували інші, я думаю, що це рішення є більш елегантним, оскільки воно не вимагає #undefing нічого, що дозволяє приховати більше складних речей, що знаходяться на заводі заголовочний файл - заголовочний файл це те, до чого ви взагалі не торкаєтесь, коли вам потрібно визначити новий перелік, тому нове визначення переліку набагато коротше і чистіше.


2
Я не знаю , як ви можете сказати , що це краще / гірше , ніж х-макроси - це є й-макросами. Це SOME_ENUM(XX)точно X-макрос (якщо бути точніше, "форма користувача", яка передає XXфункцію, а не використовує #def #undef), а потім, у свою чергу, весь X-MACRO потім передається DEFINE_ENUM, який використовує його. Щоб нічого не відібрати від рішення - воно працює добре. Тільки для пояснення, що це використання макросів X.
BeeOnRope

1
@BeeOnRope Різниця, яку ви зазначаєте, значна і відрізняє це рішення від ідіоматичних макросів X (таких як приклади Вікіпедії ). Перевага переходу XXнад журчанням #defineполягає в тому, що перший шаблон може бути використаний при розширенні макросів. Зверніть увагу, що єдині такі короткі рішення, як це, вимагають створення та багаторазового включення окремого файлу для визначення нового переліку.
pmttavara

1
Інший фокус полягає у використанні імені перерахування як імені макросу. Ви можете просто написати #define DEFINE_ENUM(EnumType) ..., замінити ENUM_DEF(...)на EnumType(...)і запропонувати користувачеві сказати #define SomeEnum(XX) .... Препроцесор C контекстно розширюватиметься SomeEnumдо виклику макросу, якщо слідуватиме дужками, і в інший звичайний маркер. (Звичайно, це спричиняє проблеми, якщо користувач любить використовувати SomeEnum(2)приведення до типу перерахування, а не (SomeEnum)2або static_cast<SomeEnum>(2).)
pmttavara

1
@pmttavara - впевнений, якщо швидкий пошук є ознакою, то найпоширеніше використання x-макросів використовує фіксовану назву внутрішнього макросу разом із #defineта #undef. Чи не погоджуєтесь ви з тим, що "форма користувача" (запропонована, наприклад, внизу цієї статті ) є типом x-макросів? Я, звичайно, завжди називав це також x-макросом, і в С-кодах, в яких я був останнім часом, це найпоширеніша форма (це, очевидно, необ'єктивне спостереження). Можливо, я неправильно розбирав OP.
BeeOnRope

2
@BeeOnRope Поточне формулювання є результатом редагування, оскільки ви переконали мене тоді, це x-макрос, навіть якщо тоді, можливо, він був менш використовуваною формою (або принаймні однією з менш згаданих у статтях).
Сума

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
Для такого типу створено cpp. +1.
Деррік Турк

6
Це хороша відповідь, здається, це найкраще, що можна зробити без використання спеціальних інструментів, і я вже робив подібні речі раніше; але це все одно ніколи не відчуває себе "правильно", і мені ніколи не подобається це робити ...
Майкл Берр,

Невелика зміна: #define ENUM_END(typ) }; extern const char * typ ## _name_table[];у defs.hфайлі - це оголосить вашу таблицю імен у файлах, якими ви її використовуєте. (Хоча я не можу зрозуміти хорошого способу оголосити розмір таблиці.) Крім того, особисто я б залишив останню крапку з комою, але суть суперечлива в будь-якому випадку.
Chris Lutz,

1
@Bill, навіщо турбуватися typв рядку #define ENUM_END(typ) };?
Pacerier

Це не працює там, де я хочу, щоб мій макрос був визначений як "ONE = 5"
UKMonkey

13

Безумовно, це можна зробити - використовувати макроси X () . Ці макроси використовують препроцесор С для побудови переліків, масивів та блоків коду зі списку вихідних даних. Вам потрібно лише додати нові елементи до #define, що містить макрос X (). Оператор перемикача розширюватиметься автоматично.

Ваш приклад можна записати наступним чином:

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

Є більш ефективні способи (тобто використання X Macros для створення масиву рядків та індексу перерахування), але це найпростіша демонстрація.


8

Я знаю, що у вас є кілька хороших надійних відповідей, але чи знаєте ви про оператор # у препроцесорі С?

Це дозволяє вам:

#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[]
Енн ван Россум,

6

C або C ++ не надає цієї функціональності, хоча мені це часто потрібно.

Наступний код працює, хоча він найкраще підходить для нерозріджених перерахувань.

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

Під негустим я маю на увазі не таку форму

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

оскільки це має величезні прогалини.

Перевага цього методу полягає в тому, що він визначає перелічення та рядки один біля одного; наявність оператора switch у функції промовляє їх. Це означає, що у вас менше шансів змінити одне без іншого.


6

ПОЦІЛУЙ. Ви будете робити всілякі інші справи, пов’язані з перемиканням / справою, з переліками, то чому друк повинен бути іншим? Забуття справи у друкарській справі - це не величезна справа, якщо ви вважаєте, що є близько 100 інших місць, про які ви можете забути справу. Просто скомпілюйте -Wall, яка попередить про невичерпні збіги справ. Не використовуйте "за замовчуванням", оскільки це зробить перемикач вичерпним, і ви не отримуватимете попереджень. Натомість, дозвольте комутатору вийти і розглядати випадки за замовчуванням так ...

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


4

Використання препроцесора boost :: робить можливим елегантне рішення, таке як:

Крок 1: включіть файл заголовка:

#include "EnumUtilities.h"

Крок 2: оголосіть об’єкт перерахування з таким синтаксисом:

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

Крок 3: Використовуйте свої дані:

Отримання кількості елементів:

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

Отримання відповідного рядка:

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;

Отримання значення переліку з відповідного рядка:

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;

Це виглядає чисто і компактно, без зайвих файлів для включення. Код, який я написав у EnumUtilities.h, такий:

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

Є певні обмеження, тобто обмеження попереднього процесора boost ::. У цьому випадку список констант не може бути більше 64 елементів.

Слідуючи тій же логіці, ви також можете подумати створити розріджений перелік:

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

У цьому випадку синтаксис:

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

Використання подібне до наведеного вище (за винятком функції eName ## 2Enum, яку ви можете спробувати екстраполювати з попереднього синтаксису).

Я протестував його на mac та linux, але майте на увазі, що препроцесор boost :: може бути не повністю портативним.


3

Об’єднавши тут деякі прийоми, я придумав найпростішу форму:

#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

Якщо ви використовуєте gcc, можна використовувати:

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

Тоді просто зателефонуйте, наприклад

enum_to_string_map[enum1]

1

Перевірте ідеї в дослідницьких лабораторіях Mu Dynamics - Архів блогів . Я знайшов це на початку цього року - я забуваю точний контекст, де я натрапив - і адаптував його до цього коду. Ми можемо обговорити переваги додавання букви Е спереду; він застосовується до конкретної вирішуваної проблеми, але не є частиною загального рішення. Я сховав це у своїй папці "віньєтки" - там я зберігаю цікаві фрагменти коду на випадок, якщо захочу їх пізніше. Мені соромно сказати, що я тоді не робив записки, звідки ця ідея.

Заголовок: 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 */

Приклад джерела:

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

Не обов’язково найчистіше у світі використання попереднього процесора C, але це заважає записувати матеріал кілька разів.



0

Якщо індекс перерахування базується на 0, ви можете помістити імена в масив char * та індексувати їх зі значенням переліку.



0

Я створив простий шаблонний клас , streamable_enumякий використовує потік операторів <<і >>і грунтується на 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

Використання:

#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

Ось рішення із використанням макросів із наступними функціями:

  1. запишіть кожне значення переліку лише один раз, тому немає подвійних списків для ведення

  2. не зберігайте значення переліку в окремому файлі, який пізніше #included, тому я можу писати його куди завгодно

  3. не замінювати сам перелік, я все одно хочу визначити тип переліку, але на додаток до нього я хочу мати можливість зіставити кожне ім'я переліку у відповідний рядок (щоб не впливати на застарілий код)

  4. пошук повинен бути швидким, тож для цих величезних переліків бажано не перемикач

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


0

Я думав, що таке рішення, як Boost.Fusion, для адаптації структур та класів було б непоганим, вони навіть мали його в певний момент, щоб використовувати перерахування як послідовність злиття.

Тож я створив лише декілька невеликих макросів для генерації коду для друку переліків. Це не ідеально і нічого не можна побачити із згенерованим шаблоном Boost.Fusion, але може використовуватися як макроси Boost Fusion. Я дійсно хочу генерувати типи, необхідні Boost.Fusion для інтеграції в цю інфраструктуру, яка дозволяє друкувати імена членів структури, але це станеться пізніше, наразі це лише макроси:

#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

Стара відповідь нижче досить погана, будь ласка, не використовуйте її. :)

Стара відповідь:

Я шукав спосіб вирішити цю проблему, не змінюючи занадто багато синтаксису оголошення enums. Я прийшов до рішення, яке використовує препроцесор для отримання рядка з обнародованого оголошення перелічення.

Я можу визначити нерозріджені перерахування таким чином:

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

І я можу взаємодіяти з ними по-різному:

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

Виходячи з таких визначень:

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

}}

Коли мені знадобиться підтримка розрідженого перерахування, і коли у мене буде більше часу, я вдосконалюю реалізації to_string та from_string за допомогою boost :: xpressive, але це буде коштувати на час компіляції через важливе виконане шаблонування та згенерований виконуваний файл. ймовірно, буде насправді більшим. Але ця перевага полягає в тому, що він буде читабельнішим і ремонтопридатнішим, ніж цей потворний ручний код обробки рядків.: D

В іншому випадку я завжди використовував boost :: bimap для виконання таких зіставлення між значенням перерахувань і рядком, але воно повинно підтримуватися вручну.


0

Оскільки я вважаю за краще не використовувати макроси з усіх звичних причин, я використав більш обмежене рішення макросів, яке має ту перевагу, що макрос декларації enum залишається вільним. Недоліки включають необхідність копіювання вставки визначення макросу для кожного переліку та необхідність явного додавання виклику макроса при додаванні значень до переліку.

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
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.