Как преобразовать переменную типа enum в строку?


128

Как заставить printf показывать значения переменных перечислимого типа? Например:

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

и мне нужно что-то вроде

printenum(OS_type, "My OS is %s", myOS);

который должен отображать строку «Linux», а не целое число.

Полагаю, сначала мне нужно создать массив строк с индексированием значений. Но я не знаю, лучший ли это способ сделать это. Это вообще возможно?


2
Выбирайте: здесь , здесь , здесь . Это беспорядок, все они полу-дубликаты.
rubenvb

Еще один , этот более полный.
bit2shift


Так много ответов, но нет "enum class"
Вивик

Ответы:


70

На самом деле нет красивого способа сделать это. Просто настройте массив строк, проиндексированных перечислением.

Если вы делаете много вывода, вы можете определить оператор <<, который принимает параметр перечисления и выполняет поиск за вас.


2
Также вы можете проверить во время компиляции, что в вашем массиве есть ожидаемое количество строк.
markh44

2
Я знаю, что меня в этом подавляющее меньшинство, но для таких программистов, как я, которые не хотят полагаться на массивные сторонние библиотеки и / или пронизанный маркой код для устранения недостатков, присущих языку, я считаю, что это на сегодняшний день самое простое и чистое решение для сегодняшних стандартов. +1
Syndog

13
@Syndog Затем этот программист обновляет длинное перечисление 56 перечислителей в вашем производственном коде под большим давлением, чтобы выпустить просроченную функцию, и он забывает обновить этот индексированный перечислением массив. Это остается незамеченным, потому что соответствующее средство печати используется только отладочным кодом приложения. 2 месяца спустя вы первый, кто действительно выполняет этот отладочный код: затем он дает вам неверную информацию, поэтому вы теряете полдня на построение предположений, основанных на этой неправильной информации, прежде чем осознаете, что вам сначала нужно отладить код отладки: дизайн основан на явном дублировании.
Ad N

1
@AdN Это неправильный дизайн. Отображение перечисления в удобочитаемую строку не следует реализовывать как массив строк, индексированных значением перечисления. Ваш опыт (предположительно) показывает почему. Отображение должно быть явным массивом пар (перечисление, строка), поэтому, если вы забудете добавить запись для своего нового значения перечисления, вы получите «???» в качестве вывода, но, по крайней мере, это не испортит имена всех ваших других перечислений.
brewbuck

8
@AdN ваш сценарий: почему я предпочитаю функцию, содержащую переключатель (без предложения по умолчанию), а не массив, и установить переключатели компилятора в файле сборки, чтобы выдать ошибку для переключения на перечисление, которое не охватывает все возможные значения. Добавление новой записи перечисления без обновления соответствующих операторов switch вызовет ошибку компиляции.
divegeek

131

Наивное решение, конечно же, - написать функцию для каждого перечисления, которая выполняет преобразование в строку:

enum OS_type { Linux, Apple, Windows };

inline const char* ToString(OS_type v)
{
    switch (v)
    {
        case Linux:   return "Linux";
        case Apple:   return "Apple";
        case Windows: return "Windows";
        default:      return "[Unknown OS_type]";
    }
}

Однако это катастрофа для обслуживания. С помощью библиотеки Boost.Preprocessor, которую можно использовать как с кодом C, так и с кодом C ++, вы можете легко воспользоваться преимуществами препроцессора и позволить ему сгенерировать эту функцию для вас. Макрос генерации выглядит следующим образом:

#include <boost/preprocessor.hpp>

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem)    \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }

Первый макрос (начинающийся с X_) используется вторым внутри. Второй макрос сначала генерирует перечисление, затем генерирует ToStringфункцию, которая принимает объект этого типа и возвращает имя перечислителя в виде строки (эта реализация, по очевидным причинам, требует, чтобы перечислители отображали уникальные значения).

В C ++ вы могли бы вместо этого реализовать ToStringфункцию как operator<<перегрузку, но я думаю, что будет немного проще требовать явного " ToString" для преобразования значения в строковую форму.

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

DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))

Хотя макрос сначала выглядит так, как будто это большой объем работы, а определение OS_typeвыглядит довольно странно, помните, что вам нужно написать макрос один раз, а затем вы можете использовать его для каждого перечисления. Вы можете добавить к нему дополнительные функции (например, преобразование строковой формы в перечисление) без особых проблем, и это полностью решает проблему обслуживания, поскольку вам нужно указать имена только один раз, когда вы вызываете макрос.

Затем перечисление можно использовать, как если бы оно было определено обычным образом:

#include <iostream>

int main()
{
    OS_type t = Windows;
    std::cout << ToString(t) << " " << ToString(Apple) << std::endl;
}

Фрагменты кода в этом посте, начинающиеся со #include <boost/preprocessor.hpp>строки, можно скомпилировать в том виде, в котором они опубликованы, для демонстрации решения.

Это конкретное решение предназначено для C ++, поскольку в нем используется специфичный для C ++ синтаксис (например, нет typedef enum) и перегрузка функций, но было бы просто заставить это работать и с C.


7
+1, механизм реализации пугающий, но конечный интерфейс сложно превзойти по элегантности.
deft_code

4
Есть ли способ получить это, чтобы вы также могли указывать целочисленные значения перечисления. Например, Windows будет 3, Linux 5 и Apple 7?
Марк

4
Да, вы можете изменить (Windows)на, а (Windows, 3)затем заменить на BOOST_PP_SEQ_ENUMсоответственно написанный BOOST_PP_SEQ_FOR_EACH. У меня нет такого удобного примера, но я могу написать его, если хотите.
Джеймс Макнеллис,

2
@JamesMcNellis Мне определенно нужен пример кода, который выполняет то, о чем просил Марк, не могли бы вы показать нам путь? :)
Омер Равив

2
ПРИМЕЧАНИЕ: препроцессор boost имеет жесткое ограничение в 256 элементов. Для более крупных перечислений необходимо другое решение.
dshin

32

Это блок препроцессора

#ifndef GENERATE_ENUM_STRINGS
    #define DECL_ENUM_ELEMENT( element ) element
    #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME
    #define END_ENUM( ENUM_NAME ) ENUM_NAME; \
            char* GetString##ENUM_NAME(enum tag##ENUM_NAME index);
#else
    #define DECL_ENUM_ELEMENT( element ) #element
    #define BEGIN_ENUM( ENUM_NAME ) char* gs_##ENUM_NAME [] =
    #define END_ENUM( ENUM_NAME ) ; char* GetString##ENUM_NAME(enum \
            tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; }
#endif

Определение перечисления

BEGIN_ENUM(Os_type)
{
    DECL_ENUM_ELEMENT(winblows),
    DECL_ENUM_ELEMENT(hackintosh),
} END_ENUM(Os_type)

Звоните с помощью

GetStringOs_type(winblows);

Взято отсюда . Как это круто ? :)


1
Это единственное решение, которое работает, когда ваше перечисление содержит более 256 элементов.
dshin

8

Используйте std::map<OS_type, std::string>и заполните его перечислением в качестве ключа и строковым представлением в качестве значений, тогда вы можете сделать это:

printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;

7

Проблема с перечислениями C в том, что это не собственный тип, как в C ++. Перечисление в C - это способ сопоставить идентификаторы с целыми значениями. Только то. Вот почему значение перечисления взаимозаменяемо с целочисленными значениями.

Как вы правильно догадались, хороший способ - создать сопоставление между значением перечисления и строкой. Например:

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};

Я предположил - очевидно, неправильно - язык программирования ограничен C.
Эндрю

1
вы немного не в себе, enum это типы в C. Константы интегрального типа перечисления относятся к типу, intа не к enumтипу, через который они определены, возможно, вы хотели сказать. Но я вообще не понимаю, при чем тут вопрос.
Йенс Густедт

7

Я объединил решения Джеймса , Ховарда и Эдера и создал более общую реализацию:

  • Значение int и настраиваемое строковое представление могут быть дополнительно определены для каждого элемента перечисления
  • "enum class" используется

Полный код написан ниже (используйте "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" для определения перечисления) ( онлайн-демонстрация ).

#include <boost/preprocessor.hpp>
#include <iostream>

// ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ implementation is taken from:
// http://lists.boost.org/boost-users/2012/09/76055.php
//
// This macro do the following:
// input:
//      (Element1, "Element 1 string repr", 2) (Element2) (Element3, "Element 3 string repr")
// output:
//      ((Element1, "Element 1 string repr", 2)) ((Element2)) ((Element3, "Element 3 string repr"))
#define HELPER1(...) ((__VA_ARGS__)) HELPER2
#define HELPER2(...) ((__VA_ARGS__)) HELPER1
#define HELPER1_END
#define HELPER2_END
#define ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(sequence) BOOST_PP_CAT(HELPER1 sequence,_END)


// CREATE_ENUM_ELEMENT_IMPL works in the following way:
//  if (elementTuple.GetSize() == 4) {
//      GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)),
//  } else {
//      GENERATE: elementTuple.GetElement(0),
//  }
// Example 1:
//      CREATE_ENUM_ELEMENT_IMPL((Element1, "Element 1 string repr", 2, _))
//  generates:
//      Element1 = 2,
//
// Example 2:
//      CREATE_ENUM_ELEMENT_IMPL((Element2, _))
//  generates:
//      Element1,
#define CREATE_ENUM_ELEMENT_IMPL(elementTuple)                                          \
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 4),                       \
    BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple),        \
    BOOST_PP_TUPLE_ELEM(0, elementTuple)                                                \
),

// we have to add a dummy element at the end of a tuple in order to make 
// BOOST_PP_TUPLE_ELEM macro work in case an initial tuple has only one element.
// if we have a tuple (Element1), BOOST_PP_TUPLE_ELEM(2, (Element1)) macro won't compile.
// It requires that a tuple with only one element looked like (Element1,).
// Unfortunately I couldn't find a way to make this transformation, so
// I just use BOOST_PP_TUPLE_PUSH_BACK macro to add a dummy element at the end
// of a tuple, in this case the initial tuple will look like (Element1, _) what
// makes it compatible with BOOST_PP_TUPLE_ELEM macro
#define CREATE_ENUM_ELEMENT(r, data, elementTuple)                                      \
    CREATE_ENUM_ELEMENT_IMPL(BOOST_PP_TUPLE_PUSH_BACK(elementTuple, _))

#define DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, element)                                        \
    case enumName::element : return BOOST_PP_STRINGIZE(element);
#define DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, element, stringRepresentation)  \
    case enumName::element : return stringRepresentation;

// GENERATE_CASE_FOR_SWITCH macro generates case for switch operator.
// Algorithm of working is the following
//  if (elementTuple.GetSize() == 1) {
//      DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, elementTuple.GetElement(0))
//  } else {
//      DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, elementTuple.GetElement(0), elementTuple.GetElement(1))
//  }
//
// Example 1:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element1, "Element 1 string repr", 2))
//  generates:
//      case EnumName::Element1 : return "Element 1 string repr";
//
// Example 2:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element2))
//  generates:
//      case EnumName::Element2 : return "Element2";
#define GENERATE_CASE_FOR_SWITCH(r, enumName, elementTuple)                                                                                                 \
    BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 1),                                                                                       \
        DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple)),                                                          \
        DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple), BOOST_PP_TUPLE_ELEM(1, elementTuple))     \
    )


// DEFINE_ENUM_CLASS_WITH_ToString_METHOD final macro witch do the job
#define DEFINE_ENUM_CLASS_WITH_ToString_METHOD(enumName, enumElements)          \
enum class enumName {                                                           \
    BOOST_PP_SEQ_FOR_EACH(                                                      \
        CREATE_ENUM_ELEMENT,                                                    \
        0,                                                                      \
        ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)                     \
    )                                                                           \
};                                                                              \
inline const char* ToString(const enumName element) {                           \
        switch (element) {                                                      \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                GENERATE_CASE_FOR_SWITCH,                                       \
                enumName,                                                       \
                ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)             \
            )                                                                   \
            default: return "[Unknown " BOOST_PP_STRINGIZE(enumName) "]";       \
        }                                                                       \
}

DEFINE_ENUM_CLASS_WITH_ToString_METHOD(Elements,
(Element1)
(Element2, "string representation for Element2 ")
(Element3, "Element3 string representation", 1000)
(Element4, "Element 4 string repr")
(Element5, "Element5", 1005)
(Element6, "Element6 ")
(Element7)
)
// Generates the following:
//      enum class Elements {
//          Element1, Element2, Element3 = 1000, Element4, Element5 = 1005, Element6,
//      };
//      inline const char* ToString(const Elements element) {
//          switch (element) {
//              case Elements::Element1: return "Element1";
//              case Elements::Element2: return "string representation for Element2 ";
//              case Elements::Element3: return "Element3 string representation";
//              case Elements::Element4: return "Element 4 string repr";
//              case Elements::Element5: return "Element5";
//              case Elements::Element6: return "Element6 ";
//              case Elements::Element7: return "Element7";
//              default: return "[Unknown " "Elements" "]";
//          }
//      }

int main() {
    std::cout << ToString(Elements::Element1) << std::endl;
    std::cout << ToString(Elements::Element2) << std::endl;
    std::cout << ToString(Elements::Element3) << std::endl;
    std::cout << ToString(Elements::Element4) << std::endl;
    std::cout << ToString(Elements::Element5) << std::endl;
    std::cout << ToString(Elements::Element6) << std::endl;
    std::cout << ToString(Elements::Element7) << std::endl;

    return 0;
}

Это лучший ответ до сих пор
Арнаут

6

Этот простой пример сработал для меня. Надеюсь это поможет.

#include <iostream>
#include <string>

#define ENUM_TO_STR(ENUM) std::string(#ENUM)

enum DIRECTION{NORTH, SOUTH, WEST, EAST};

int main()
{
  std::cout << "Hello, " << ENUM_TO_STR(NORTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(SOUTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(EAST) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(WEST) << "!\n";
}

13
Не будет работать, если у вас НАПРАВЛЕНИЕ a = СЕВЕР; а затем напишите ENUM_TO_STR (a)
mathreadler

5

Вы пробовали это:

#define stringify( name ) # name

enum enMyErrorValue
  {
  ERROR_INVALIDINPUT = 0,
  ERROR_NULLINPUT,
  ERROR_INPUTTOOMUCH,
  ERROR_IAMBUSY
  };

const char* enMyErrorValueNames[] = 
  {
  stringify( ERROR_INVALIDINPUT ),
  stringify( ERROR_NULLINPUT ),
  stringify( ERROR_INPUTTOOMUCH ),
  stringify( ERROR_IAMBUSY )
  };

void vPrintError( enMyErrorValue enError )
  {
  cout << enMyErrorValueNames[ enError ] << endl;
  }

int main()
  {
  vPrintError((enMyErrorValue)1);
  }

stringify()Макрос может быть использован , чтобы превратить любой текст в коде в строку, но только точный текст в скобках. Здесь нет разыменования переменных, макроподстановок или каких-либо других действий.

http://www.cplusplus.com/forum/general/2949/


Этот действительно должен быть лучшим, хотя достаточно и первого :)
pholat 02

Работает нормально, но вы должны добавить #ifndef stringify вверху, чтобы избежать ошибки компиляции. Я также изменил тип перечисления на std :: string, как предложил dgmz.
Astarakastara

5

Здесь много хороших ответов, но я подумал, что некоторые люди найдут мой полезный. Мне это нравится, потому что интерфейс, который вы используете для определения макроса, настолько прост, насколько это возможно. Это также удобно, потому что вам не нужно включать какие-либо дополнительные библиотеки - все это поставляется с C ++ и даже не требует действительно поздней версии. Я извлекал фрагменты из разных мест в Интернете, поэтому я не могу поверить в это все, но я думаю, что это достаточно уникально, чтобы заслужить новый ответ.

Сначала создайте файл заголовка ... назовите его EnumMacros.h или что-то в этом роде и вставьте в него следующее:

// Search and remove whitespace from both ends of the string
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { rit++; }
    return std::string(it, rit.base());
}

static void SplitEnumArgs(const char* szArgs, std::string Array[], int nMax)
{
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        Array[nIdx] = TrimEnumString(strSub);
        nIdx++;
    }
};
// This will to define an enum that is wrapped in a namespace of the same name along with ToString(), FromString(), and COUNT
#define DECLARE_ENUM(ename, ...) \
    namespace ename { \
        enum ename { __VA_ARGS__, COUNT }; \
        static std::string _Strings[COUNT]; \
        static const char* ToString(ename e) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            return _Strings[e].c_str(); \
        } \
        static ename FromString(const std::string& strEnum) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            for (int i = 0; i < COUNT; i++) { if (_Strings[i] == strEnum) { return (ename)i; } } \
            return COUNT; \
        } \
    }

Затем в своей основной программе вы можете сделать это ...

#include "EnumMacros.h"
DECLARE_ENUM(OsType, Windows, Linux, Apple)

void main() {
    OsType::OsType MyOs = OSType::Apple;
    printf("The value of '%s' is: %d of %d\n", OsType::ToString(MyOs), (int)OsType::FromString("Apple"), OsType::COUNT);
}

Где вывод будет >> Значение «Apple»: 2 из 4

Наслаждайтесь!


Ключевым моментом, который мне нравится в этом конкретном подходе, является то, что он работает с обычным синтаксисом перечисления, разделенным запятыми (до тех пор, пока он не включает какие-либо присвоения значений параметров в перечислении). В моем случае мне пришлось работать с существующим перечислением с большим количеством членов, поэтому его было намного проще использовать, чем подход с ускорением.
CuriousKea

4

Предполагая, что ваше перечисление уже определено, вы можете создать массив пар:

std::pair<QTask::TASK, QString> pairs [] = {
std::pair<OS_type, string>(Linux, "Linux"),
std::pair<OS_type, string>(Windows, "Windows"),
std::pair<OS_type, string>(Apple, "Apple"),
};

Теперь вы можете создать карту:

std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));

Теперь вы можете использовать карту. Если ваше перечисление изменилось, вы должны добавить / удалить пару из массива pair []. Я думаю, что это самый элегантный способ получить строку из enum в C ++.


2
Помимо справедливого комментария о том, что здесь нет необходимости в Qt, еще один момент заключается в том, что можно использовать Boost bimapв случае, если кто-то хочет проанализировать имена и превратить их в перечисления (например, из файла XML).
Дмитрий Нестерук

4
Если не использовать типы Qt в вопросе родового C ++.
Вектор

3

Для C99 есть P99_DECLARE_ENUMв Р99 , что позволяет просто объявить , enumкак это:

P99_DECLARE_ENUM(color, red, green, blue);

а затем используйте color_getname(A)для получения строки с названием цвета.


2

Вот мой код на C ++:

/* 
 * File:   main.cpp
 * Author: y2k1234
 *
 * Created on June 14, 2013, 9:50 AM
 */

#include <cstdlib>
#include <stdio.h>

using namespace std;


#define MESSAGE_LIST(OPERATOR)                          \
                                       OPERATOR(MSG_A), \
                                       OPERATOR(MSG_B), \
                                       OPERATOR(MSG_C)
#define GET_LIST_VALUE_OPERATOR(msg)   ERROR_##msg##_VALUE
#define GET_LIST_SRTING_OPERATOR(msg)  "ERROR_"#msg"_NAME"

enum ErrorMessagesEnum
{
   MESSAGE_LIST(GET_LIST_VALUE_OPERATOR)
};
static const char* ErrorMessagesName[] = 
{
   MESSAGE_LIST(GET_LIST_SRTING_OPERATOR)
};

int main(int argc, char** argv) 
{

    int totalMessages = sizeof(ErrorMessagesName)/4;

    for (int i = 0; i < totalMessages; i++)
    {
        if (i == ERROR_MSG_A_VALUE)
        {
                printf ("ERROR_MSG_A_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_B_VALUE)
        {
                printf ("ERROR_MSG_B_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_C_VALUE)
        {
                printf ("ERROR_MSG_C_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else
        {
                printf ("??? => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
    }   

    return 0;
}

Output:

ERROR_MSG_A_VALUE => [0]=[ERROR_MSG_A_NAME]

ERROR_MSG_B_VALUE => [1]=[ERROR_MSG_B_NAME]

ERROR_MSG_C_VALUE => [2]=[ERROR_MSG_C_NAME]

RUN SUCCESSFUL (total time: 126ms)

2

Немного поздно на вечеринку, но вот мое решение на C ++ 11:

namespace std {
    template<> struct hash<enum_one> {
        std::size_t operator()(const enum_one & e) const {
            return static_cast<std::size_t>(e);
        }
    };
    template<> struct hash<enum_two> { //repeat for each enum type
        std::size_t operator()(const enum_two & e) const {
            return static_cast<std::size_t>(e);
        }
    };
}

const std::string & enum_name(const enum_one & e) {
    static const std::unordered_map<enum_one, const std::string> names = {
    #define v_name(n) {enum_one::n, std::string(#n)}
        v_name(value1),
        v_name(value2),
        v_name(value3)
    #undef v_name
    };
    return names.at(e);
}

const std::string & enum_name(const enum_two & e) { //repeat for each enum type
    .................
}

1
error: ‘hash’ is not a class template->#include <functional>
Ruggero Turra

2

Я предпочитаю минимизировать как повторяющийся набор текста, так и сложные для понимания макросы, а также избегать введения макроопределений в общее пространство компилятора.

Итак, в заголовочном файле:

enum Level{
        /**
        * zero reserved for internal use
        */
        verbose = 1,
        trace,
        debug,
        info,
        warn,
        fatal
    };

static Level readLevel(const char *);

а реализация cpp:

 Logger::Level Logger::readLevel(const char *in) { 
 #  define MATCH(x) if (strcmp(in,#x) ==0) return x; 
    MATCH(verbose);
    MATCH(trace);
    MATCH(debug);
    MATCH(info);
    MATCH(warn);
    MATCH(fatal);
 # undef MATCH
    std::string s("No match for logging level ");
    s += in;
    throw new std::domain_error(s);
 }

Обратите внимание на #undef макроса, как только мы закончим с ним.


2

Мое решение, не использующее ускорение:

#ifndef EN2STR_HXX_
#define EN2STR_HXX_

#define MAKE_STRING_1(str     ) #str
#define MAKE_STRING_2(str, ...) #str, MAKE_STRING_1(__VA_ARGS__)
#define MAKE_STRING_3(str, ...) #str, MAKE_STRING_2(__VA_ARGS__)
#define MAKE_STRING_4(str, ...) #str, MAKE_STRING_3(__VA_ARGS__)
#define MAKE_STRING_5(str, ...) #str, MAKE_STRING_4(__VA_ARGS__)
#define MAKE_STRING_6(str, ...) #str, MAKE_STRING_5(__VA_ARGS__)
#define MAKE_STRING_7(str, ...) #str, MAKE_STRING_6(__VA_ARGS__)
#define MAKE_STRING_8(str, ...) #str, MAKE_STRING_7(__VA_ARGS__)

#define PRIMITIVE_CAT(a, b) a##b
#define MAKE_STRING(N, ...) PRIMITIVE_CAT(MAKE_STRING_, N)     (__VA_ARGS__)


#define PP_RSEQ_N() 8,7,6,5,4,3,2,1,0
#define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
#define PP_NARG( ...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N())

#define MAKE_ENUM(NAME, ...) enum NAME { __VA_ARGS__ };            \
  struct NAME##_str {                                              \
    static const char * get(const NAME et) {                       \
      static const char* NAME##Str[] = {                           \
                MAKE_STRING(PP_NARG(__VA_ARGS__), __VA_ARGS__) };  \
      return NAME##Str[et];                                        \
      }                                                            \
    };

#endif /* EN2STR_HXX_ */

И вот как это использовать

int main()
  {
  MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d);
  pippo c = d;
  cout << pippo_str::get(c) << "\n";
  return 0;
  }

2

Еще один опаздывающий на вечеринку, используя препроцессор:

 1  #define MY_ENUM_LIST \
 2      DEFINE_ENUM_ELEMENT(First) \
 3      DEFINE_ENUM_ELEMENT(Second) \
 4      DEFINE_ENUM_ELEMENT(Third) \
 5  
 6  //--------------------------------------
 7  #define DEFINE_ENUM_ELEMENT(name) , name
 8  enum MyEnum {
 9      Zeroth = 0
10      MY_ENUM_LIST
11  };
12  #undef DEFINE_ENUM_ELEMENT
13 
14  #define DEFINE_ENUM_ELEMENT(name) , #name
15  const char* MyEnumToString[] = {
16      "Zeroth"
17      MY_ENUM_LIST
18  };
19  #undef DEFINE_ENUM_ELEMENT
20
21  #define DEFINE_ENUM_ELEMENT(name) else if (strcmp(s, #name)==0) return name;
22  enum MyEnum StringToMyEnum(const char* s){
23      if (strcmp(s, "Zeroth")==0) return Zeroth;
24      MY_ENUM_LIST
25      return NULL;
26  }
27  #undef DEFINE_ENUM_ELEMENT

(Я просто ввел номера строк, чтобы было легче говорить.) Строки 1–4 - это то, что вы редактируете, чтобы определить элементы перечисления. (Я назвал его «макросом списка», потому что это макрос, который составляет список вещей. @Lundin сообщает мне, что это хорошо известный метод, называемый X-макросами.)

Строка 7 определяет внутренний макрос, чтобы заполнить фактическое объявление перечисления в строках 8-11. Строка 12 отменяет определение внутреннего макроса (просто чтобы отключить предупреждение компилятора).

Строка 14 определяет внутренний макрос, чтобы создать строковую версию имени элемента перечисления. Затем в строках 15-18 генерируется массив, который может преобразовать значение перечисления в соответствующую строку.

Строки 21–27 генерируют функцию, которая преобразует строку в значение перечисления или возвращает NULL, если строка не соответствует ни одному.

Это немного громоздко с точки зрения обработки 0-го элемента. Я действительно работал над этим в прошлом.

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


«X-макросы» редко бывают элегантным решением какой-либо проблемы. В этом случае было бы гораздо удобнее просто определить элементы макроса as #define TEST_1 hello #define TEST_2 worldthen, typedef enum { TEST_1, TEST_2 } test_t;а затем создать таблицу поиска строк, в которой используется макрос Stringify: const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; уже есть несколько ответов, намекающих на похожие решения. Намного читабельнее.
Lundin

@Lundin: Я только утверждаю, что 1) это работает даже с самым примитивным компилятором C, и 2) добавление или удаление элемента - это однострочное редактирование.
Майк Данлэви

Я опубликовал свой ответ: stackoverflow.com/a/39877228/584518 . Надеюсь, это избавит какую-то бедную душу от решений макросов x.
Lundin

1
Я использовал ваше решение. Я считаю, что это лучший вариант. C-синтаксис по-прежнему существует, поэтому вы понимаете, что происходит, а список определяется только один раз. Вы можете удалить 0-й элемент, поставив запятую после записи в вашем DEFINE_ENUM_ELEMENT.
состоится

1

Вот метод Old Skool (который широко использовался в gcc) с использованием только препроцессора C. Полезно, если вы создаете дискретные структуры данных, но вам нужно поддерживать согласованный порядок между ними. Записи в mylist.tbl, конечно, можно расширить до чего-то гораздо более сложного.

test.cpp:

enum {
#undef XX
#define XX(name, ignore) name ,
#include "mylist.tbl"
  LAST_ENUM
};

char * enum_names [] = {
#undef XX
#define XX(name, ignore) #name ,
#include "mylist.tbl"
   "LAST_ENUM"
};

И затем mylist.tbl:

/*    A = enum                  */
/*    B = some associated value */
/*     A        B   */
  XX( enum_1 , 100)
  XX( enum_2 , 100 )
  XX( enum_3 , 200 )
  XX( enum_4 , 900 )
  XX( enum_5 , 500 )

1
Этот метод называется макросами x!
Ватусимото

0

В c ++ вот так:

enum OS_type{Linux, Apple, Windows};

std::string ToString( const OS_type v )
{
  const std::map< OS_type, std::string > lut =
    boost::assign::map_list_of( Linux, "Linux" )(Apple, "Apple )( Windows,"Windows");
  std::map< OS_type, std::string >::const_iterator it = lut.find( v );
  if ( lut.end() != it )
    return it->second;
  return "NOT FOUND";
}

0
#include <EnumString.h>

из http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C и после

enum FORM {
    F_NONE = 0,
    F_BOX,
    F_CUBE,
    F_SPHERE,
};

вставка

Begin_Enum_String( FORM )
{
    Enum_String( F_NONE );
    Enum_String( F_BOX );
    Enum_String( F_CUBE );
    Enum_String( F_SPHERE );
}
End_Enum_String;

Прекрасно работает, если значения в перечислении не повторяются.

Пример кода для преобразования значения перечисления в строку:

enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );

Пример кода для прямо противоположного:

assert( EnumString< FORM >::To( f, str ) );

0

Спасибо, Джеймс, за ваше предложение. Это было очень полезно, поэтому я реализовал другой способ, чтобы внести свой вклад.

#include <iostream>
#include <boost/preprocessor.hpp>

using namespace std;

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data,  elem) \
    case data::elem : return BOOST_PP_STRINGIZE(elem);

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF(r, data, elem) \
    if (BOOST_PP_SEQ_TAIL(data) ==                                     \
            BOOST_PP_STRINGIZE(elem)) return                           \
            static_cast<int>(BOOST_PP_SEQ_HEAD(data)::elem); else

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)         \
    enum class name {                                                  \
        BOOST_PP_SEQ_ENUM(enumerators)                                 \
    };                                                                 \
                                                                       \
    inline const char* ToString(name v)                                \
    {                                                                  \
        switch (v)                                                     \
        {                                                              \
            BOOST_PP_SEQ_FOR_EACH(                                     \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,   \
                name,                                                  \
                enumerators                                            \
            )                                                          \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";  \
        }                                                              \
    }                                                                  \
                                                                       \
    inline int ToEnum(std::string s)                                   \
    {                                                                  \
        BOOST_PP_SEQ_FOR_EACH(                                         \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF,       \
                (name)(s),                                             \
                enumerators                                            \
            )                                                          \
        return -1;                                                     \
    }


DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows));

int main(void)
{
    OS_type t = OS_type::Windows;

    cout << ToString(t) << " " << ToString(OS_type::Apple) << " " << ToString(OS_type::Linux) << endl;

    cout << ToEnum("Windows") << " " << ToEnum("Apple") << " " << ToEnum("Linux") << endl;

    return 0;
}

0

Чтобы расширить ответ Джеймса, кому-то нужен пример кода для поддержки определения enum со значением int, у меня также есть это требование, поэтому вот мой способ:

Первый - это макрос внутреннего использования, который используется FOR_EACH:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE(r, data, elem)         \
    BOOST_PP_IF(                                                                \
        BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elem), 2),                           \
        BOOST_PP_TUPLE_ELEM(0, elem) = BOOST_PP_TUPLE_ELEM(1, elem),            \
        BOOST_PP_TUPLE_ELEM(0, elem) ),

И вот макрос определения:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                  \
    enum name {                                                                 \
        BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \
                              0, enumerators) };

Поэтому, используя его, вы можете писать так:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum,
    ((FIRST, 1))
    ((SECOND))
    ((MAX, SECOND)) )

который расширится до:

enum MyEnum
{
    FIRST = 1,
    SECOND,
    MAX = SECOND,
};

Основная идея состоит в том, чтобы определить SEQ, каждый элемент которого является TUPLE, чтобы мы могли добавить значение для члена enum. В цикле FOR_EACH проверьте размер элемента TUPLE, если размер равен 2, разверните код до KEY = VALUE, иначе просто оставьте первый элемент TUPLE.

Поскольку входной SEQ на самом деле является TUPLE, поэтому, если вы хотите определить функции STRINGIZE, вам может потребоваться сначала предварительно обработать входные перечислители, вот макрос для выполнения этой работы:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM(r, data, elem)           \
    BOOST_PP_TUPLE_ELEM(0, elem),

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ(enumerators)         \
    BOOST_PP_SEQ_SUBSEQ(                                                        \
        BOOST_PP_TUPLE_TO_SEQ(                                                  \
            (BOOST_PP_SEQ_FOR_EACH(                                             \
                DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM, 0, enumerators) \
            )),                                                                 \
            0,                                                                  \
            BOOST_PP_SEQ_SIZE(enumerators))

Макрос DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQсохранит только первый элемент в каждом TUPLE, а затем преобразует его в SEQ, теперь измените код Джеймса, вы получите полную мощность.

Моя реализация, возможно, не самая простая, поэтому, если вы не найдете чистого кода, мой для вашей справки.


0

Чистое и безопасное решение в чистом стандарте C:

#include <stdio.h>

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

/* list of enum constants */
#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N
} test_t;

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

int main()
{  
  _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
                 "Incorrect number of items in enum or look-up table");

  printf("%d %s\n", hello, test_str[hello]);
  printf("%d %s\n", world, test_str[world]);
  test_t x = world;
  printf("%d %s\n", x, test_str[x]);

  return 0;
}

Вывод

0 hello
1 world
1 world

обоснование

Решая основную задачу «иметь константы перечисления с соответствующими строками», здравомыслящий программист выдвинет следующие требования:

  • Избегайте повторения кода (принцип «СУХОЙ»).
  • Код должен быть масштабируемым, поддерживаемым и безопасным, даже если элементы добавляются или удаляются внутри перечисления.
  • Весь код должен быть качественным: легко читаемым, простым в обслуживании.

Первое требование, а может быть и второе, может быть выполнено с помощью различных беспорядочных макро-решений, таких как печально известный трюк с «x macro» или другие формы макро-магии. Проблема с такими решениями заключается в том, что они оставляют вас с совершенно нечитаемым беспорядком загадочных макросов - они не соответствуют третьему требованию выше.

Единственное, что здесь нужно, это фактически иметь таблицу поиска строк, к которой мы можем получить доступ, используя переменную enum в качестве индекса. Такая таблица, естественно, должна напрямую соответствовать enum и наоборот. При обновлении одного из них необходимо обновить и другой, иначе он не будет работать.


Пояснение к коду

Предположим, у нас есть перечисление вроде

typedef enum
{
  hello,
  world
} test_t;

Это можно изменить на

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

С тем преимуществом, что эти макроконстанты теперь можно использовать где-нибудь еще, например, для создания таблицы поиска строк. Преобразование константы препроцессора в строку может быть выполнено с помощью макроса "stringify":

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

И это все. Используя hello, мы получаем константу перечисления со значением 0. Используя, test_str[hello]мы получаем строку "hello".

Чтобы перечисление и таблица поиска соответствовали напрямую, мы должны убедиться, что они содержат одинаковое количество элементов. Если кто-то будет поддерживать код и изменять только перечисление, а не таблицу поиска, или наоборот, этот метод не будет работать.

Решение состоит в том, чтобы перечисление сообщало вам, сколько элементов оно содержит. Для этого есть часто используемый трюк C, просто добавьте элемент в конце, который только заполняет цель сообщить, сколько элементов имеет перечисление:

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N  // will have value 2, there are 2 enum constants in this enum
} test_t;

Теперь мы можем проверить во время компиляции, что количество элементов в перечислении равно количеству элементов в таблице поиска, предпочтительно с помощью статического утверждения C11:

_Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
               "Incorrect number of items in enum or look-up table");

(Есть уродливые, но полнофункциональные способы создания статических утверждений в более старых версиях стандарта C, если кто-то настаивает на использовании компиляторов динозавров. Что касается C ++, он также поддерживает статические утверждения.)


В качестве примечания, в C11 мы также можем достичь более высокой безопасности типов, изменив макрос stringify:

#define STRINGIFY(x) _Generic((x), int : STRF(x))

( intпоскольку константы перечисления на самом деле имеют тип int, а не test_t)

Это предотвратит STRINGIFY(random_stuff)компиляцию кода вроде .


Я понимаю, что вы говорите, но суть остается. Типичные предсказуемые изменения требуют минимальных правок (например, 1 строку). (Я думаю, что это причина DRY.) Итак, если размер перечисления равен 500, и вы хотите вставить новый элемент посередине (или удалить / переименовать / поменять местами), сколько строк кода вы должны изменения, и сколько проверок нужно сделать, чтобы убедиться, что вы не ошиблись? Также могут быть другие части кода, которые делают что-то единообразное для каждого элемента списка.
Майк Данлэви

Спасибо, что сказали мне, что это называется X-макросами . Я этого не знал. Чего я не вижу, так это того, что люди вообще их очерняют.
Майк Данлэви

@MikeDunlavey Независимо от размера перечисления, вам придется изменить ровно 3 строки: добавить #define, добавить ссылку на это определение в объявлении перечисления и в справочной таблице. Если вы допустите ошибку при добавлении этих строк, программа не скомпилируется. Числа, которые я добавил к идентификаторам, ни в коем случае не являются обязательными, вы также можете написать, #define APPLES helloа #define ORANGES worldзатем typedef enum { APPES, ORANGES, TEST_N } test_t;и так далее.
Lundin

@MikeDunlavey Что касается макросов X, аргументы против них такие же, как аргументы против любых макросов, подобных функциям. Вам не нужно далеко ходить, чтобы найти множество очень обоснованных критических замечаний в отношении функционально-подобных макросов.
Lundin

0

То, что я сделал, является комбинацией того, что я видел здесь и в аналогичных вопросах на этом сайте. Я сделал это Visual Studio 2013. Я не тестировал его с другими компиляторами.

Прежде всего, я определяю набор макросов, которые помогут.

// concatenation macros
#define CONCAT_(A, B) A ## B
#define CONCAT(A, B)  CONCAT_(A, B)

// generic expansion and stringification macros
#define EXPAND(X)           X
#define STRINGIFY(ARG)      #ARG
#define EXPANDSTRING(ARG)   STRINGIFY(ARG)        

// number of arguments macros
#define NUM_ARGS_(X100, X99, X98, X97, X96, X95, X94, X93, X92, X91, X90, X89, X88, X87, X86, X85, X84, X83, X82, X81, X80, X79, X78, X77, X76, X75, X74, X73, X72, X71, X70, X69, X68, X67, X66, X65, X64, X63, X62, X61, X60, X59, X58, X57, X56, X55, X54, X53, X52, X51, X50, X49, X48, X47, X46, X45, X44, X43, X42, X41, X40, X39, X38, X37, X36, X35, X34, X33, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, ...) N
#define NUM_ARGS(...) EXPAND(NUM_ARGS_(__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1))

// argument extraction macros
#define FIRST_ARG(ARG, ...) ARG
#define REST_ARGS(ARG, ...) __VA_ARGS__

// arguments to strings macros
#define ARGS_STR__(N, ...)  ARGS_STR_##N(__VA_ARGS__)
#define ARGS_STR_(N, ...)   ARGS_STR__(N, __VA_ARGS__)
#define ARGS_STR(...)       ARGS_STR_(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

#define ARGS_STR_1(ARG)     EXPANDSTRING(ARG)
#define ARGS_STR_2(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_1(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_3(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_2(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_4(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_3(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_5(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_4(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_6(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_5(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_7(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_6(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_8(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_7(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_9(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_8(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_10(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_9(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_11(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_10(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_12(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_11(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_13(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_12(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_14(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_13(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_15(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_14(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_16(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_15(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_17(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_16(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_18(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_17(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_19(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_18(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_20(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_19(EXPAND(REST_ARGS(__VA_ARGS__)))
// expand until _100 or as much as you need

Затем определите один макрос, который создаст класс перечисления и функции для получения строк.

#define ENUM(NAME, ...)                                                                                             \
    enum class NAME                                                                                                 \
    {                                                                                                               \
        __VA_ARGS__                                                                                                 \
    };                                                                                                              \
                                                                                                                    \
    static const std::array<std::string, NUM_ARGS(__VA_ARGS__)> CONCAT(NAME, Strings) = { ARGS_STR(__VA_ARGS__) };  \
                                                                                                                    \
    inline const std::string& ToString(NAME value)                                                                  \
    {                                                                                                               \
        return CONCAT(NAME, Strings)[static_cast<std::underlying_type<NAME>::type>(value)];                         \
    }                                                                                                               \
                                                                                                                    \
    inline std::ostream& operator<<(std::ostream& os, NAME value)                                                   \
    {                                                                                                               \
        os << ToString(value);                                                                                      \
        return os;                                                                                                  \
    }

Теперь определение типа перечисления и наличие для него строк становится действительно простым. Все, что вам нужно сделать, это:

ENUM(MyEnumType, A, B, C);

Следующие строки можно использовать для проверки.

int main()
{
    std::cout << MyEnumTypeStrings.size() << std::endl;

    std::cout << ToString(MyEnumType::A) << std::endl;
    std::cout << ToString(MyEnumType::B) << std::endl;
    std::cout << ToString(MyEnumType::C) << std::endl;

    std::cout << MyEnumType::A << std::endl;
    std::cout << MyEnumType::B << std::endl;
    std::cout << MyEnumType::C << std::endl;

    auto myVar = MyEnumType::A;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::B;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::C;
    std::cout << myVar << std::endl;

    return 0;
}

Это выведет:

3
A
B
C
A
B
C
A
B
C

Я считаю, что он очень чистый и простой в использовании. Есть некоторые ограничения:

  • Вы не можете присваивать значения членам перечисления.
  • Значения члена перечисления используются в качестве индекса, но это должно быть нормально, потому что все определяется в одном макросе.
  • Вы не можете использовать его для определения типа перечисления внутри класса.

Если вы можете обойти это. Я думаю, особенно как его использовать, это красиво и скудно. Преимущества:

  • Легко использовать.
  • Разделение строк во время выполнения не требуется.
  • Во время компиляции доступны отдельные строки.
  • Легко читать. Для первого набора макросов может потребоваться дополнительная секунда, но на самом деле это не так сложно.

0

Чистым решением этой проблемы было бы:

#define RETURN_STR(val, e) {if (val == e) {return #e;}}

std::string conv_dxgi_format_to_string(int value) {
    RETURN_STR(value, DXGI_FORMAT_UNKNOWN);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_FLOAT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_UINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_SINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_FLOAT);

    /* ... */

    return "<UNKNOWN>";
}

Преимущество этого решения заключается в том, что оно простое, а также создание функции может быть легко выполнено с помощью копирования и замены. Обратите внимание, что если вы собираетесь выполнить много преобразований, а ваше перечисление имеет слишком много возможных значений, это решение может потребовать загрузки ЦП.


0

Я немного опоздал, но вот мое решение с использованием g ++ и только стандартных библиотек. Я попытался свести к минимуму загрязнение пространства имен и избавиться от необходимости повторно вводить имена перечислений.

Заголовочный файл my_enum.hpp:

#include <cstring>

namespace ENUM_HELPERS{
    int replace_commas_and_spaces_with_null(char* string){
        int i, N;
        N = strlen(string);
        for(i=0; i<N; ++i){
            if( isspace(string[i]) || string[i] == ','){
                string[i]='\0';
            }
        }
        return(N);
    }

    int count_words_null_delim(char* string, int tot_N){
        int i;
        int j=0;
        char last = '\0';
        for(i=0;i<tot_N;++i){
            if((last == '\0') && (string[i]!='\0')){
                ++j;
            }
            last = string[i];
        }
        return(j);
    }

    int get_null_word_offsets(char* string, int tot_N, int current_w){
        int i;
        int j=0;
        char last = '\0';
        for(i=0; i<tot_N; ++i){
            if((last=='\0') && (string[i]!='\0')){
                if(j == current_w){
                    return(i);
                }
                ++j;
            }
            last = string[i];
        }
        return(tot_N); //null value for offset
    }

    int find_offsets(int* offsets, char* string, int tot_N, int N_words){
        int i;
        for(i=0; i<N_words; ++i){
            offsets[i] = get_null_word_offsets(string, tot_N, i);
        }
        return(0);
    }
}


#define MAKE_ENUM(NAME, ...)                                            \
namespace NAME{                                                         \
    enum ENUM {__VA_ARGS__};                                            \
    char name_holder[] = #__VA_ARGS__;                                  \
    int name_holder_N =                                                 \
        ENUM_HELPERS::replace_commas_and_spaces_with_null(name_holder); \
    int N =                                                             \
        ENUM_HELPERS::count_words_null_delim(                           \
            name_holder, name_holder_N);                                \
    int offsets[] = {__VA_ARGS__};                                      \
    int ZERO =                                                          \
        ENUM_HELPERS::find_offsets(                                     \
            offsets, name_holder, name_holder_N, N);                    \
    char* tostring(int i){                                              \
       return(&name_holder[offsets[i]]);                                \
    }                                                                   \
}

Пример использования:

#include <cstdio>
#include "my_enum.hpp"

MAKE_ENUM(Planets, MERCURY, VENUS, EARTH, MARS)

int main(int argc, char** argv){    
    Planets::ENUM a_planet = Planets::EARTH;
    printf("%s\n", Planets::tostring(Planets::MERCURY));
    printf("%s\n", Planets::tostring(a_planet));
}

Это выведет:

MERCURY
EARTH

Вам нужно определить все только один раз, ваше пространство имен не должно быть загрязнено, и все вычисления выполняются только один раз (остальное - просто поиск). Однако вы не получаете типобезопасность классов перечислений (они по-прежнему являются короткими целыми числами), вы не можете присваивать значения перечислениям, вы должны определять перечисления где-нибудь, где вы можете определять пространства имен (например, глобально).

Я не уверен, насколько хорошо это работает, и насколько это хорошая идея (я изучал C до C ++, поэтому мой мозг все еще работает таким образом). Если кто-нибудь знает, почему это плохая идея, не стесняйтесь указать на это.


0

Это 2017 год, но вопрос все еще жив

Еще один способ:

#include <iostream>

#define ERROR_VALUES \
ERROR_VALUE(NO_ERROR, 0, "OK") \
ERROR_VALUE(FILE_NOT_FOUND, 1, "Not found") \
ERROR_VALUE(LABEL_UNINITIALISED, 2, "Uninitialized usage")

enum Error
{
#define ERROR_VALUE(NAME, VALUE, TEXT) NAME = VALUE,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME, VALUE, TEXT) case NAME: return os << "[" << errVal << "]" << #NAME << ", " << TEXT;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << NO_ERROR << std::endl;
    std::cout << "Error: " << FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << LABEL_UNINITIALISED << std::endl;
    return 0;
}

Выходы:

Error: [0]NO_ERROR, OK
Error: [1]FILE_NOT_FOUND, Not found
Error: [2]LABEL_UNINITIALISED, Uninitialized usage

0
#pragma once

#include <string>
#include <vector>
#include <sstream>
#include <algorithm>

namespace StringifyEnum
{
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { ++rit; }
    return std::string(it, rit.base());
}

static std::vector<std::string> SplitEnumArgs(const char* szArgs, int     nMax)
{
    std::vector<std::string> enums;
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        enums.push_back(StringifyEnum::TrimEnumString(strSub));
        ++nIdx;
    }
    return std::move(enums);
}    
}

#define DECLARE_ENUM_SEQ(ename, n, ...) \
    enum class ename { __VA_ARGS__ }; \
    const int MAX_NUMBER_OF_##ename(n); \
    static std::vector<std::string> ename##Strings = StringifyEnum::SplitEnumArgs(#__VA_ARGS__, MAX_NUMBER_OF_##ename); \
    inline static std::string ename##ToString(ename e) { \
        return ename##Strings.at((int)e); \
    } \
    inline static ename StringTo##ename(const std::string& en) { \
        const auto it = std::find(ename##Strings.begin(), ename##Strings.end(), en); \
        if (it != ename##Strings.end()) \
            return (ename) std::distance(ename##Strings.begin(), it); \
        throw std::runtime_error("Could not resolve string enum value");     \
    }

Это усовершенствованная версия расширенного перечисления класса ... она не добавляет никаких других значений перечисления, кроме предоставленных.

Использование: DECLARE_ENUM_SEQ (CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)


0

Мне нужно, чтобы это работало в обоих направлениях, и я часто встраиваю свои перечисления в содержащий класс, поэтому я начал с решения Джеймса Макнеллиса, путь в верхней части этих ответов, но я сделал это решение. Обратите внимание, что я предпочитаю класс enum, а не просто enum, что несколько усложняет ответ.

#define X_DEFINE_ENUMERATION(r, datatype, elem) case datatype::elem : return BOOST_PP_STRINGIZE(elem);

// The data portion of the FOR_EACH should be (variable type)(value)
#define X_DEFINE_ENUMERATION2(r, dataseq, elem) \
    if (BOOST_PP_SEQ_ELEM(1, dataseq) == BOOST_PP_STRINGIZE(elem) ) return BOOST_PP_SEQ_ELEM(0, dataseq)::elem;

#define DEFINE_ENUMERATION_MASTER(modifier, name, toFunctionName, enumerators)    \
    enum class name {                                                         \
        Undefined,                                                            \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    modifier const char* ToString(const name & v)                               \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUMERATION,                                         \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }                                                                         \
                                                                              \
    modifier const name toFunctionName(const std::string & value)               \
    {                                                                         \
        BOOST_PP_SEQ_FOR_EACH(                                                \
            X_DEFINE_ENUMERATION2,                                            \
            (name)(value),                                                    \
            enumerators                                                       \
        )                                                                     \
        return name::Undefined;                                               \
    }

#define DEFINE_ENUMERATION(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(inline, name, toFunctionName, enumerators)

#define DEFINE_ENUMERATION_INSIDE_CLASS(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(static, name, toFunctionName, enumerators)

Чтобы использовать его внутри класса, вы можете сделать что-то вроде этого:

class ComponentStatus {
public:
    /** This is a simple bad, iffy, and good status. See other places for greater details. */
    DEFINE_ENUMERATION_INSIDE_CLASS(Status, toStatus, (RED)(YELLOW)(GREEN)
}

И я написал тест CppUnit, который демонстрирует, как его использовать:

void
ComponentStatusTest::testSimple() {
    ComponentStatus::Status value = ComponentStatus::Status::RED;

    const char * valueStr = ComponentStatus::ToString(value);

    ComponentStatus::Status convertedValue = ComponentStatus::toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

DEFINE_ENUMERATION(Status, toStatus, (RED)(YELLOW)(GREEN))

void
ComponentStatusTest::testOutside() {
    Status value = Status::RED;

    const char * valueStr = ToString(value);

    Status convertedValue = toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

Вы должны выбрать, какой макрос использовать: DEFINE_ENUMERATION или DEFINE_ENUMERATION_INSIDE_CLASS. Вы увидите, что я использовал последнее при определении ComponentStatus :: Status, но я использовал первое, когда просто определял Status. Отличие простое. Внутри класса я использую префикс методов to / from как «static», а если не в классе, я использую «inline». Мелкие отличия, но необходимые.

К сожалению, я не думаю, что есть чистый способ избежать этого:

const char * valueStr = ComponentStatus::ToString(value);

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

inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }

0

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

#pragma once
#include <string>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name> {                                         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };

/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

        printf( EnumToString(enumValue3).c_str() );

    From string to enumeration:

       enumName value;

       if( !StringToEnum("enumValue4", value) )
            printf("Conversion failed...");

    WARNING: At the moment assigning enum value to specific number is not supported.
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = (int)t;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (id == 0)
            return std::string(token, next);
        id--;
    } while (*next != 0);

    return std::string();
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = 0;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (strncmp(token, enumName, next - token) == 0)
        {
            t = (T)id;
            return true;
        }

        id++;
    } while (*next != 0);

    return false;
}

Последнюю версию можно найти на github здесь:

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


0

На это есть много других ответов, но я думаю, что лучший способ - использовать функции C ++ 17 и использовать constexpr, чтобы переводы выполнялись во время компиляции. Это типобезопасный вариант, и нам не нужно возиться с макросами. Увидеть ниже:

//enum.hpp
#include <array>
#include <string_view>

namespace Enum
{

template <class ENUM_TYPE, size_t SIZE>
constexpr ENUM_TYPE findKey(const char * value, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Value not in map":
        (std::string_view(map[index - 1].second) == value) ? map[index- 1].first:
        findKey(value, map, index - 1);
};

template <class ENUM_TYPE, size_t SIZE>
constexpr const char * findValue(ENUM_TYPE key, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Key not in map":
        (map[index - 1].first == key) ? map[index- 1].second:
        findValue(key, map, index - 1);
};

}

//test_enum.hpp
#include "enum.hpp"

namespace TestEnum
{
    enum class Fields
    {
        Test1,
        Test2,
        Test3,
        //This has to be at the end
        NUMBER_OF_FIELDS
    };

    constexpr std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> GetMap()
    {
        std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> map =
        {
            {
                    {Fields::Test1, "Test1"},
                    {Fields::Test2, "Test2"},
                    {Fields::Test3, "Test3"},
            }
        };
        return map;
    };

    constexpr Fields StringToEnum(const char * value)
    {
        return Enum::findKey(value, GetMap());
    }

    constexpr const char * EnumToString(Fields key)
    {
        return Enum::findValue(key, GetMap());
    }

}

Затем это можно легко использовать для обнаружения ошибок строкового ключа во время компиляции:

#include "test_enum.hpp"

int main()
{
    auto constexpr a = TestEnum::StringToEnum("Test2"); //a = TestEnum::Fields::Test2
    auto constexpr b = TestEnum::EnumToString(TestEnum::Fields::Test1); //b = "Test1"
    auto constexpr c = TestEnum::StringToEnum("AnyStringNotInTheMap"); //compile time failure
    return 0;
}

Код более подробный, чем некоторые другие решения, но мы можем легко выполнить преобразование Enum в String и преобразование String в Enum во время компиляции и обнаружить ошибки типа. С некоторыми из будущих функций C ++ 20 это, вероятно, можно будет еще немного упростить.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.