Простой способ использовать переменные перечислимого типа как строку в C?

Вот что я пытаюсь сделать:

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, как я пытаюсь сделать выше?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
91
0
106 911
19
Перейти к ответу Данный вопрос помечен как решенный

Ответы 19

Ответ принят как подходящий

Встроенного решения нет. Самый простой способ - использовать массив char*, где значение int перечисления указывает на строку, содержащую описательное имя этого перечисления. Если у вас есть разреженный enum (тот, который не начинается с 0 или имеет пробелы в нумерации), где некоторые сопоставления int достаточно высоки, чтобы сделать сопоставление на основе массива непрактичным, вы можете вместо этого использовать хеш-таблицу.

Расширяя это, если это действительно линейно увеличивающийся список, вы можете просто использовать инструмент макроса вашего редактора, чтобы записать и преобразовать каждое из имен в строку. Требуется немного дополнительного набора текста, и вы в первую очередь избавляетесь от необходимости в определениях. Я нажимаю запись на последнем из скопированных макросов, добавляю цитату после и перехожу к тому же месту в следующей строке. Я нажимаю стоп. Я нажимаю Run X раз и делаю столько, сколько есть (или только один шаг). Затем я могу обернуть его в массив строк.

user2262111 01.05.2019 21:54

Если индекс перечисления основан на 0, вы можете поместить имена в массив char * и проиндексировать их с помощью значения перечисления.

Попробуйте Преобразование перечислений C++ в строки. В Комментарии есть улучшения, которые решают проблему, когда элементы перечисления имеют произвольные значения.

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 в функции выделяет их. Это означает, что у вас меньше шансов изменить одно без другого.

Ознакомьтесь с идеями на Исследовательские лаборатории Mu Dynamics - Архив блога. Я обнаружил это ранее в этом году - я забыл точный контекст, в котором я это наткнулся - и адаптировал его в этот код. Мы можем обсудить достоинства добавления E впереди; он применим к конкретной рассматриваемой проблеме, но не является частью общего решения. Я спрятал это в своей папке «виньетки» - где я храню интересные фрагменты кода на случай, если они мне понадобятся позже. Стыдно сказать, что в то время я не записал, откуда пришла эта идея.

Заголовок: 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, но оно предотвращает многократную запись материала.

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

Это то, для чего был создан cpp. +1.

Derrick Turk 13.05.2010 01:25

Это хороший ответ, кажется, лучшее, что можно сделать без использования специальных инструментов, и я делал подобные вещи раньше; но это все равно никогда не кажется `` правильным '', и мне никогда не очень нравится это делать ...

Michael Burr 14.05.2010 02:41

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

Chris Lutz 23.12.2010 11:31

@Bill, зачем заморачиваться с typ в строке #define ENUM_END(typ) };?

Pacerier 16.05.2015 02:46

Это не работает там, где я хочу, чтобы мой макрос был определен как "ONE = 5"

UKMonkey 13.09.2017 16:03

Определенно есть способ сделать это - использовать Макросы X (). Эти макросы используют препроцессор C для создания перечислений, массивов и блоков кода из списка исходных данных. Вам нужно только добавить новые элементы в #define, содержащий макрос X (). Оператор switch расширится автоматически.

Ваш пример можно записать так:

 // 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 для создания массива строк и индекса перечисления), но это простейшая демонстрация.

Я знаю, что у вас есть пара хороших твердых ответов, но знаете ли вы об операторе # в препроцессоре C?

Это позволяет вам делать это:

#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 15.08.2018 13:04

Здесь можно использовать технику из Делать что-то одновременно идентификатором 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-макросами с использованием #include / #define / #undef

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

Я упомянул эту технику в сообщении блога об общем программировании на C: abissell.com/2014/01/16/…

Andrew Bissell 20.01.2014 03:28

Я не уверен, как можно сказать, что это лучше / хуже, чем х-макросы - это х-макросы является. SOME_ENUM(XX) - это в точности X-макрос (точнее, «пользовательская форма», которая передает функцию XX, а не использует #def#undef), а затем, в свою очередь, весь X-MACRO затем передается в DEFINE_ENUM, который его использует. Ничего не отнимать от раствора - работает хорошо. Просто чтобы уточнить, что это использование макросов X.

BeeOnRope 14.02.2017 22:49

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

pmttavara 29.12.2017 06:48

Другой трюк - использовать имя перечисления как в качестве имени макроса. Вы можете просто написать #define DEFINE_ENUM(EnumType) ..., заменить ENUM_DEF(...) на EnumType(...) и попросить пользователя сказать #define SomeEnum(XX) .... Препроцессор C будет контекстно расширять SomeEnum в вызов макроса, если за ним следуют круглые скобки, и в обычный токен в противном случае. (Конечно, это вызывает проблемы, если пользователю нравится использовать SomeEnum(2) для приведения к типу перечисления, а не (SomeEnum)2 или static_cast<SomeEnum>(2).)

pmttavara 29.12.2017 06:57

@pmttavara - конечно, если быстрый поиск указывает на то, что наиболее частое использование x-макросов использует фиксированное имя внутреннего макроса вместе с #define и #undef. Вы не согласны с тем, что «форма пользователя» (предлагаемая, например, внизу эта статья) является типом x-макроса? Я, конечно, всегда называл это x-макросом, и в кодовых базах C, в которых я был в последнее время, это самая распространенная форма (это, очевидно, предвзятое наблюдение). Возможно, я неправильно разбирал OP.

BeeOnRope 29.12.2017 22:27

Например, я интерпретировал Сравнение с X-макросами с использованием #include / #define / #undef как «Сравнение этой техники (которая не является x-макросом) с техникой x-макроса (с примечанием о define / undef просто как дескриптор того, как использовались x-макросы)». Я вижу, что это также может быть проанализировано как «Сравнение этого варианта x-макросов с другим вариантом x-макросов, который не передает макрос в качестве аргумента и использует несколько включений». В последнем случае у меня нет проблем - это просто вопрос, возможно, немного неясной формулировки!

BeeOnRope 29.12.2017 22:29

@BeeOnRope Текущая формулировка является результатом редактирования, как вы убедили меня тогда, это x-macro, даже если в то время это была, возможно, менее используемая форма (или, по крайней мере, одна меньше упоминалась в статьях).

Suma 29.12.2017 23:19

@suma - хорошо, теперь история имеет смысл. Я хотел связать статью еще в моем первоначальном комментарии, но забыл. «Пользовательская форма» там приписывается Андреску, но я не нашел для этого источника (возможно, это было предложено непосредственно автору). С обновленной формулировкой вы можете в основном игнорировать мой исходный комментарий.

BeeOnRope 29.12.2017 23:40

ЦЕЛОВАТЬ. Вы будете делать с вашими перечислениями множество других вещей, связанных с переключателями / регистрами, так почему же печать должна быть другой? Забыть футляр в рутине печати - не такая уж большая проблема, если учесть, что есть около 100 других мест, где вы можете забыть футляр. Просто скомпилируйте -Wall, который предупредит о неполном совпадении регистра. Не используйте «по умолчанию», потому что это сделает переключение исчерпывающим и вы не получите предупреждений. Вместо этого позвольте переключателю выйти и разобраться со случаем по умолчанию следующим образом ...

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

Использование 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 :: preprocessor. В этом случае список констант не может быть больше 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 :: может быть не полностью переносимым.

Я подумал, что такое решение, как Boost.Fusion one для адаптации структур и классов, было бы неплохим, у них даже было его в какой-то момент, чтобы использовать перечисления в качестве последовательности слияния.

Поэтому я сделал несколько небольших макросов для генерации кода для печати перечислений. Это не идеально и не имеет ничего общего с шаблонным кодом, сгенерированным 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

Старый ответ ниже довольно плохой, пожалуйста, не используйте его. :)

Старый ответ:

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

Я могу определять нередкие перечисления следующим образом:

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

}}

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

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

#define stringify( name ) # name

enum MyEnum {
    ENUMVAL1
};
...stuff...

stringify(EnumName::ENUMVAL1);  // Returns MyEnum::ENUMVAL1

Дальнейшее обсуждение этого метода

Уловки с директивой препроцессора для новичков

Я создал простой шаблонный класс 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;
}

Вот решение с использованием макросов со следующими функциями:

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

  2. не храните значения перечисления в отдельном файле, который позже #included, поэтому я могу записать его где угодно

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

  4. поиск должен быть быстрым, поэтому желательно без переключателя для этих огромных перечислений

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

Если вы используете gcc, можно использовать:

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

Тогда просто позвоните, например,

enum_to_string_map[enum1]

Объединив некоторые из представленных здесь техник, я получил простейшую форму:

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

Поскольку я предпочитаю не использовать макросы по всем обычным причинам, я использовал более ограниченное решение для макросов, которое имеет то преимущество, что макрос объявления 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
}

Другие вопросы по теме