Як перетворити змінну типу 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


Стільки відповідей поки що немає "перерахунку"
Vivick

Відповіді:


70

Дійсно немає прекрасного способу зробити це. Просто встановіть масив рядків, індексованих перерахунком.

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


2
Крім того, ви можете перевірити під час компіляції, що ваш масив має очікувану кількість рядків у ньому.
markh44

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

13
@Syndog Потім цей програміст оновлює 56 перелічувачів у вашому виробничому коді під великим тиском, щоб випустити прострочену функцію, і він забуває оновити цей перелічений масив. Це залишається непоміченим, оскільки відповідний інструмент друку використовується лише кодом налагодження програми. Через 2 місяці ви першим реально виконали цей код налагодження: він надає неправильну інформацію, тож ви втрачаєте припущення щодо побудови на півдня на основі цієї неправильної інформації, перш ніж усвідомити, що спочатку довелося налагоджувати код налагодження: дизайн покладається на явне дублювання.
Оголошення N

1
@AdN Цей дизайн неправильний. Відображення від enum до людського для читання рядка не повинно бути реалізовано як масив рядків, індексованих значенням enum. Ваш досвід (імовірно) показує, чому. Відображення має бути масивом explitiy (enum, string) пар, тож якщо ви забудете додати запис для нового значення enum, ви отримаєте "???" як вихід, але принаймні це не викрутить імена всіх ваших інших переліків.
заварка

8
@AdN ваш сценарій тому я віддаю перевагу функції, що містить перемикач (без за замовчуванням), а не масив, і встановити компілятори компілятора у файлі збірки, щоб видати помилку для перемикання на enum, яка не охоплює всіх можливі значення. Додавання нового запису enum без оновлення відповідних операторів перемикання призведе до помилки компіляції.
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
Чи є в будь-якому випадку це отримати, щоб ви також могли дати значення enum цілих значень. Наприклад, Windows буде 3, Linux 5 та Apple 7?
Марк

4
Так, ви можете змінити (Windows)в (Windows, 3)то замінити BOOST_PP_SEQ_ENUMз відповідним написано BOOST_PP_SEQ_FOR_EACH. Я не маю приклад цього зручного, але я можу написати його, якщо хочете.
Джеймс Мак-Нілліс

2
@JamesMcNellis Я, безумовно, хотів би приклад коду, який виконує те, про що просив Марк, чи будете ви такими ласкавими, щоб показати нам шлях? :)
Омер Равів

2
ПРИМІТКА: передпроцесор посилення має жорсткий ліміт у 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>та заповнюйте його enum як ключ та представлення рядків як значення, тоді ви можете зробити це:

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

7

Проблема з перерахунками C полягає в тому, що він не є його власним типом, як це є в C ++. Перерахунок на C - це спосіб відображення ідентифікаторів на цілісні значення. Просто це. Тому значення enum взаємозамінне з цілими значеннями.

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

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

Я припускав - мабуть, неправильно - мова програмування обмежена С.
Ендрю

1
Ви трохи збігаєтеся, enum є типами C. Константи типу інтегрального перерахування мають тип, intа не той enumтип, через який вони визначені, - це, мабуть, те, що ви хотіли сказати. Але я взагалі не бачу, що це стосується питання.
Jens Gustedt

7

Я поєднав рішення Джеймса , Говарда та Едера та створив більш загальну реалізацію:

  • int значення та спеціальне представлення рядків можуть бути необов'язково визначені для кожного елемента enum
  • використовується "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/


Це насправді буде вершиною, хоча достатньо лише першого з них буде достатньо :)
folat

Добре працює, але слід додати #ifndef stringify вгорі, щоб уникнути помилки компіляції. Я також змінив тип enum на std :: string, як це запропонував dgmz.
астаракастара

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

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


Ключове, що мені подобається в цьому конкретному підході, - це те, що він працює з нормальним синтаксисом enum, відокремленим комами (до тих пір, поки він не містить жодних присвоєнь параметрів значення в enum). У моєму випадку мені довелося працювати з існуючим перерахунком з великою кількістю членів, тому це було набагато простіше, ніж підхід до підвищення.
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]));

Тепер ви можете використовувати карту. Якщо ваш перерахунок змінено, вам доведеться додати / видалити пару з пар масивів []. Я думаю, що це найелегантніший спосіб отримати рядок із 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 визначає внутрішній макрос, щоб створити версію рядка імені елемента enum. Потім рядки 15-18 генерують масив, який може перетворити значення перерахунку у відповідний рядок.

Рядки 21-27 генерують функцію, яка перетворює рядок у значення перерахунку, або повертає NULL, якщо рядок не відповідає жодному.

Це трохи громіздко в тому, як він обробляє 0-й елемент. Я фактично працював над цим у минулому.

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


"X макроси" рідко коли-небудь є елегантним рішенням будь-якої проблеми. У цьому випадку було б набагато зручніше просто визначити макроелементи як #define TEST_1 hello #define TEST_2 worldтоді, typedef enum { TEST_1, TEST_2 } test_t;а потім створити таблицю пошуку рядків, яка використовує макрос Strify: const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; Вже є кілька відповідей, що натякають на подібні рішення. Набагато легше для читання.
Лундін

@Lundin: Я стверджую лише, що 1) це працює навіть з найпримітивнішим компілятором C, і 2) додавання або видалення елемента - це редакція в 1 рядку.
Майк Данлаве

Я опублікував власну відповідь: stackoverflow.com/a/39877228/584518 . Сподіваємось, це вбереже бідну душу від рішень x макросів.
Лундін

1
Я використав ваше рішення. Я думаю, що це найкраще. Синтаксис C все ще існує, щоб ви зрозуміли, що відбувається, і список визначається лише один раз. Ви можете видалити 0-й елемент, поставивши коску після запису у вашому DEFINE_ENUM_ELEMENT.
isgoed

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

Чисте, безпечне рішення в чистому стандартному С:

#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

Обґрунтування

При вирішенні основної проблеми "мати константи перерахунку з відповідними рядками", розумний програміст висуне такі вимоги:

  • Уникайте повторення коду (принцип "DRY").
  • Код повинен бути масштабованим, підтримуваним та безпечним, навіть якщо елементи додаються або видаляються всередині enum.
  • Весь код повинен бути високоякісним: легкий для читання, простий у обслуговуванні.

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

Тут потрібно лише мати таблицю пошуку рядків, до якої ми можемо отримати доступ, використовуючи змінну 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, ми отримуємо константу enum зі значенням 0. Використовуючи, test_str[hello]ми отримуємо рядок "привіт".

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

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

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

Тепер ми можемо перевірити під час компіляції, що кількість елементів у перерахунку стільки, скільки кількість елементів у оглядовій таблиці, бажано зі статичним твердженням С11:

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

(Існують некрасиві, але повністю функціональні способи створення статичних тверджень і в старих версіях стандарту С, якщо хтось наполягає на використанні компіляторів динозаврів. Що стосується C ++, він також підтримує статичні твердження.)


Як бічна примітка, в C11 ми також можемо досягти більш високої безпеки безпеки, змінивши строковий макрос:

#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;і так далі.
Лундін

@MikeDunlavey Що стосується макросів X, аргументи проти них такі ж, як аргументи проти будь-яких функціональних макросів. Вам не потрібно буде шукати далеко, щоб знайти багато дуже вагомих критик проти функціональних макросів.
Лундін

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

Далі визначте один макрос, який створить клас enum та функції для отримання рядків.

#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 та мати рядки для нього стає дійсно просто. Все, що вам потрібно зробити, це:

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

Я вважаю, що це дуже чисте і просте у використанні. Є деякі обмеження:

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

Якщо ви можете обійти це. Я думаю, особливо як це використовувати, це приємно і худорляво. Переваги:

  • Простий у використанні.
  • Не потрібно розбивати рядки під час виконання.
  • Окремі рядки доступні під час компіляції.
  • Легко читати. Перший набір макросів може потребувати додаткової секунди, але насправді це не так складно.

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

Ви повинні визначити все лише один раз, ваше ім’я не повинно бути забруднене, а всі обчислення проводяться лише один раз (решта - це просто пошук). Однак ви не отримуєте безпеку типу класів enum (вони все ще є лише короткими цілими числами), ви не можете призначити значення перелікам, ви повинні визначити enums десь ви можете визначити простори імен (наприклад, глобально).

Я не впевнений, наскільки хороша ефективність щодо цього, чи якщо це гарна ідея (я навчився 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");     \
    }

Це розроблена версія розширеної enum класу ... вона не додає жодного іншого значення перерахунку, окрім наданого.

Використання: DECLARE_ENUM_SEQ (CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)


0

Мені це було потрібно для роботи в обох напрямках. І я часто вкладав свої переписки всередині класу, що містить вміст, і тому я почав із вирішення Джеймса Мак-Нілліса способом, що знаходиться у верхній частині цих відповідей, але я прийняв це рішення. Зауважте також, що я віддаю перевагу enum class, а не просто 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, але я використовував перший, коли просто визначав статус. Різниця проста. Всередині класу я встановлюю префікс методів до / від "статичних", а якщо немає, то використовую "вбудований". Тривіальні відмінності, але необхідні.

На жаль, я не думаю, що існує чистий спосіб уникнути цього:

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

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

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

0

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

#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 to Enum під час компіляції та виявити помилки типу. З деякими майбутніми функціями C ++ 20 це, ймовірно, може бути спрощено трохи більше.

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