Ответ 1
На самом деле нет прекрасного способа сделать это. Просто настройте массив строк, проиндексированных перечислением.
Если вы делаете большой вывод, вы можете определить оператор < < который принимает параметр enum и выполняет поиск для вас.
Как заставить printf отображать значения переменных, которые имеют тип перечисления? Например:
typedef enum {Linux, Apple, Windows} OS_type;
OS_type myOS = Linux;
и мне нужно что-то вроде
printenum(OS_type, "My OS is %s", myOS);
который должен показывать строку "Linux", а не целое число.
Я полагаю, сначала мне нужно создать массив строк с индексированием значений. Но я не знаю, является ли это самым красивым способом сделать это. Возможно ли вообще?
На самом деле нет прекрасного способа сделать это. Просто настройте массив строк, проиндексированных перечислением.
Если вы делаете большой вывод, вы можете определить оператор < < который принимает параметр enum и выполняет поиск для вас.
Наивное решение, конечно, состоит в том, чтобы написать функцию для каждой перечисления, которая выполняет преобразование в строку:
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, так и с С++, вы можете легко воспользоваться препроцессором и позволить ему генерировать эту функцию для вас. Макрос генерации выглядит следующим образом:
#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
, которая берет объект этого типа и возвращает имя перечислителя в виде строки (эта реализация по очевидным причинам требует, чтобы перечисления отображались на уникальные значения).
В С++ вы могли бы реализовать функцию 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>
, могут быть скомпилированы как отправленные для демонстрации решения.
Это конкретное решение для С++, поскольку оно использует С++-специфический синтаксис (например, no typedef enum
) и перегрузку функции, но было бы легко также сделать эту работу с C.
Это блок предварительного процессора
#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
Определение enum
BEGIN_ENUM(Os_type)
{
DECL_ENUM_ELEMENT(winblows),
DECL_ENUM_ELEMENT(hackintosh),
}
Вызов с использованием
GetStringOs_type(winblows);
Взято из здесь. Как это круто?:)
Проблема с C перечислениями заключается в том, что она не является ее собственным типом, как в С++. Перечисление в C - способ сопоставления идентификаторов с целыми значениями. Только то. Поэтому значение enum является взаимозаменяемым с целыми значениями.
Как вы правильно поняли, хороший способ - создать сопоставление между значением enum и строкой. Например:
char * OS_type_label[] = {
"Linux",
"Apple",
"Windows"
};
Я объединил решения Джеймса, Говарда и Эдера и создал более общую реализацию:
Полный код приведен ниже (используйте "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;
}
Этот простой пример работал у меня. Надеюсь, это поможет.
#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";
}
Используйте std::map<OS_type, std::string>
и заполните его с помощью enum as key и строкового представления как значения, затем вы можете сделать следующее:
printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;
Здесь много хороших ответов, но я подумал, что некоторые люди могут найти мои полезными. Мне это нравится, потому что интерфейс, который вы используете для определения макроса, настолько прост, насколько это возможно. Это также удобно, потому что вам не нужно включать какие-либо дополнительные библиотеки - все это поставляется с 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
Наслаждайтесь!
Предполагая, что ваше перечисление уже определено, вы можете создать массив пар:
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 в С++.
В C99 есть P99_DECLARE_ENUM
в P99, который позволяет просто объявить enum
следующим образом:
P99_DECLARE_ENUM(color, red, green, blue);
а затем используйте color_getname(A)
, чтобы получить строку с именем цвета.
Вы пробовали это:
#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()
может использоваться для превращения любого текста в ваш код в строку, но только точный текст между круглыми скобками. Нет никаких переменных разыменований или макрозамен или каких-либо других вещей.
Вот мой код на С++:
/*
* 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)
Мое решение, не использующее boost:
#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;
}
Здесь используется метод 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 )
Немного поздно на вечеринку, но вот мое решение на С++ 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
.................
}
Мое собственное предпочтение состоит в том, чтобы свести к минимуму как повторяющуюся типизацию, так и трудно понять макросы и избежать введения макроопределений в общее пространство компилятора.
Итак, в файле заголовка:
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 макроса, как только мы закончим с ним.
Еще одна поздняя вечеринка, использующая препроцессор:
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-й элемент. В прошлом я работал над этим.
Я признаю, что этот метод беспокоит людей, которые не хотят думать, что сам препроцессор может быть запрограммирован для написания кода для вас. Я думаю, что это сильно иллюстрирует разницу между читабельностью и ремонтопригодностью. Код трудно читать, но если перечисление содержит несколько сотен элементов, вы можете добавлять, удалять или изменять элементы и все же быть уверенными, что сгенерированный код не имеет ошибок.
В С++, например:
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";
}
#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 ) );
Спасибо Джеймсу за ваше предложение. Это было очень полезно, поэтому я внедрил другой способ, чтобы внести определенный вклад.
#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;
}
Чтобы продлить ответ Джеймса, кто-то хочет, чтобы какой-то примерный код поддерживал определение перечисления с помощью значения 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 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, поэтому мы можем поместить добавочное значение для члена перечисления. В цикле 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, теперь изменить код Джеймса, вы будете иметь полную мощность.
Моя реализация, возможно, не самая простая, поэтому, если вы не найдете какой-либо чистый код, для моей справки.
Чистое, безопасное решение в чистом стандарте 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
Обоснование
При решении основной проблемы "имеют константы континуума с соответствующими строками", разумный программист придет к следующим требованиям:
Первое требование, а может быть и второе, может быть выполнено с помощью различных беспорядочных макроресурсов, таких как печально известный "х макро-фокус" или другие формы макромагии. Проблема с такими решениями заключается в том, что они оставляют вас совершенно непонятным беспорядком таинственных макросов - они не отвечают третьему требованию выше.
Единственное, что нужно здесь, это на самом деле иметь таблицу поиска строк, к которой мы можем получить доступ, используя переменную 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]
, мы получим строку "привет".
Чтобы таблица соответствия и таблица соответствия соответствовали друг другу, мы должны обеспечить, чтобы они содержали одинаковое количество элементов. Если кто-то сохранит код и изменит только перечисление, а не справочную таблицу, или наоборот, этот метод не будет работать.
Решение состоит в том, чтобы перечисление указывало, сколько элементов оно содержит. Для этого используется обычно используемый трюк 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:
_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)
.
То, что я сделал, представляет собой комбинацию того, что я видел здесь и в похожих вопросах на этом сайте. Я сделал это 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(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
Я считаю, что он очень чист и прост в использовании. Существуют некоторые ограничения:
Если вы можете обойти это. Я думаю, особенно, как использовать его, это хорошо и скудно. Преимущества:
Чистым решением этой проблемы будет:
#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>";
}
Хорошая вещь об этом решении заключается в том, что он прост, а также построить функцию можно легко с помощью копирования и замены. Обратите внимание: если вы собираетесь делать много конверсий, и ваше перечисление имеет слишком много возможных значений, это решение может стать интенсивным.
Я немного опоздал, но здесь мое решение с использованием 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 до С++, поэтому мой мозг все еще работает именно так). Если кто-нибудь знает, почему это плохая идея, не стесняйтесь указать на это.
Это 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
#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)
Мне нужно было, чтобы это работало в обоих направлениях, и я часто встраивал свои перечисления в содержащий класс, и поэтому я начал с решения Джеймсом Макнеллисом, кстати, в верхней части этих ответов, но я нашел это решение. Заметьте также, что я предпочитаю 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, но я использовал первое при определении Status. Разница проста. Внутри класса я добавляю методы to/from как "статические", а если не в классе, я использую "inline". Тривиальные отличия, но необходимые.
К сожалению, я не думаю, что есть чистый способ избежать этого:
const char * valueStr = ComponentStatus::ToString(value);
хотя вы можете вручную создать встроенный метод после определения класса, который просто соединяется с методом класса, что-то вроде:
inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }
Мой собственный ответ, не использующий повышение - использующий мой собственный подход без магии сильного определения, и это решение имеет ограничение, заключающееся в невозможности определить конкретное значение перечисления.
#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
Я добавил один ответ, который не поддерживал значения перечисления, теперь добавил поддержку, которая также поддерживает присвоение значения перечисления. Как и в предыдущем решении, в этом используется минимум определения магии.
Вот заголовочный файл:
#pragma once
#include <string>
#include <map>
#include <regex>
template <class Enum>
class EnumReflect
{
public:
static const char* getEnums() { return ""; }
};
//
// Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
static std::map<std::string, int> enum2int;
static std::map<int, std::string> int2enum;
static void EnsureEnumMapReady( const char* enumsInfo )
{
if (*enumsInfo == 0 || enum2int.size() != 0 )
return;
// Should be called once per each enumeration.
std::string senumsInfo(enumsInfo);
std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *"); // C++ identifier to optional " = <value>"
std::smatch sm;
int value = 0;
for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
{
string enumName = sm[1].str();
string enumValue = sm[2].str();
if (enumValue.length() != 0)
value = atoi(enumValue.c_str());
enum2int[enumName] = value;
int2enum[value] = enumName;
}
}
};
template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;
template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;
#define DECLARE_ENUM(name, ...) \
enum name { __VA_ARGS__ }; \
template <> \
class EnumReflect<##name>: public EnumReflectBase<##name> { \
public: \
static const char* getEnums() { return #__VA_ARGS__; } \
};
/*
Basic usage:
Declare enumeration:
DECLARE_ENUM( enumName,
enumValue1,
enumValue2,
enumValue3 = 5,
// 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...");
*/
//
// Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
auto& int2enum = EnumReflect<T>::int2enum;
auto it = int2enum.find(t);
if (it == int2enum.end())
return "";
return it->second;
}
//
// Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
auto& enum2int = EnumReflect<T>::enum2int;
auto it = enum2int.find(enumName);
if (it == enum2int.end())
return false;
t = (T) it->second;
return true;
}
А вот пример тестового приложения:
DECLARE_ENUM(TestEnum,
ValueOne,
ValueTwo,
ValueThree = 5,
ValueFour = 7
);
DECLARE_ENUM(TestEnum2,
ValueOne2 = -1,
ValueTwo2,
ValueThree2 = -4,
ValueFour2
);
void main(void)
{
string sName1 = EnumToString(ValueOne);
string sName2 = EnumToString(ValueTwo);
string sName3 = EnumToString(ValueThree);
string sName4 = EnumToString(ValueFour);
TestEnum t1, t2, t3, t4, t5 = ValueOne;
bool b1 = StringToEnum(sName1.c_str(), t1);
bool b2 = StringToEnum(sName2.c_str(), t2);
bool b3 = StringToEnum(sName3.c_str(), t3);
bool b4 = StringToEnum(sName4.c_str(), t4);
bool b5 = StringToEnum("Unknown", t5);
string sName2_1 = EnumToString(ValueOne2);
string sName2_2 = EnumToString(ValueTwo2);
string sName2_3 = EnumToString(ValueThree2);
string sName2_4 = EnumToString(ValueFour2);
TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
bool b2_5 = StringToEnum("Unknown", t2_5);
Обновленная версия того же заголовочного файла будет храниться здесь:
https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h