Как преобразовать переменную типа enum в строку?
Как заставить printf показывать значения переменных типа enum? Например:
typedef enum {Linux, Apple, Windows} OS_type;
OS_type myOS = Linux;
и мне нужно что-то вроде
printenum(OS_type, "My OS is %s", myOS);
который должен показывать строку "Linux", а не целое число.
Я полагаю, сначала я должен создать индексированный массив строк строк. Но я не знаю, если это самый красивый способ сделать это. Это вообще возможно?
38 ответов
Там действительно нет красивого способа сделать это. Просто установите массив строк, проиндексированных перечислением.
Если вы много выводите, вы можете определить оператор<<, который принимает параметр 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, так и с 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.
Это блок предварительного процессора
#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),
}
Звонить используя
GetStringOs_type(winblows);
Взято отсюда. Как это круто?:)
Я объединил решения Джеймса, Говарда и Эдера и создал более общую реализацию:
- Значение 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;
}
Уже есть много хороших ответов, но вам стоит взглянуть на magic_enum.
Он описывает себя как -
Статическое отражение перечислений (в строку, из строки, итерацию) для современного C++ работает с любым типом перечисления без макросов или шаблонного кода.
Библиотека C++17 только для заголовков обеспечивает статическое отражение для перечислений, работает с любым типом перечисления без каких-либо макросов или шаблонного кода.
Пример использования
enum Color { RED = 2, BLUE = 4, GREEN = 8 };
Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"
std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name);
if (color.has_value()) {
// color.value() -> Color::GREEN
}
Использование std::map<OS_type, std::string>
и заполните его enum в качестве ключа и строковое представление в качестве значений, тогда вы можете сделать это:
printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;
Вы пробовали это:
#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()
макрос может использоваться для преобразования любого текста в вашем коде в строку, но только точный текст в скобках. Не существует разыменования переменных, макро-замен или каких-либо других действий.
Проблема с перечислениями в C заключается в том, что это не свой собственный тип, как в C++. Перечисление в C - это способ сопоставить идентификаторы с целочисленными значениями. Только то. Вот почему значение enum взаимозаменяемо с целочисленными значениями.
Как вы правильно догадываетесь, хорошим способом является создание отображения между значением перечисления и строкой. Например:
char * OS_type_label[] = {
"Linux",
"Apple",
"Windows"
};
Этот простой пример работал для меня. Надеюсь это поможет.
#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";
}
Я предпочитаю минимизировать как повторяющуюся типизацию, так и сложные для понимания макросы и избегать введения определений макросов в общее пространство компилятора.
Итак, в заголовочном файле:
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 макроса, как только мы закончим с ним.
Для С99 есть P99_DECLARE_ENUM
в P99, который позволяет просто объявить enum
как это:
P99_DECLARE_ENUM(color, red, green, blue);
а затем использовать color_getname(A)
получить строку с именем цвета.
Здесь много хороших ответов, но я подумал, что некоторые люди могут найти мои полезными. Мне это нравится, потому что интерфейс, который вы используете для определения макроса, настолько прост, насколько это возможно. Это также удобно, потому что вам не нужно включать какие-либо дополнительные библиотеки - все это поставляется с 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 в C++.
Мое решение, не использующее повышение:
#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;
}
Вот мой код 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)
Немного опоздал на вечеринку, но вот мое решение 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
.................
}
Мне нужно, чтобы это работало в обоих направлениях, и я часто встраивал свои перечисления в содержащий класс, и поэтому я начал с решения Джеймсом МакНеллисом, как раз в начале этих ответов, но я нашел это решение. Заметьте также, что я предпочитаю 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); }
Еще один опоздал на вечеринку, используя препроцессор:
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 определяет внутренний макрос, чтобы заполнить фактическое объявление enum в строках 8-11. Строка 12 определяет внутренний макрос (только для того, чтобы отключить предупреждение компилятора).
Строка 14 определяет внутренний макрос, чтобы создать строковую версию имени элемента enum. Затем строки 15-18 генерируют массив, который может преобразовать значение перечисления в соответствующую строку.
Строки 21-27 генерируют функцию, которая преобразует строку в значение enum или возвращает NULL, если строка не соответствует ни одной.
Это немного громоздко в способе обработки 0-го элемента. Я действительно работал над этим в прошлом.
Я признаю, что эта техника беспокоит людей, которые не хотят думать, что сам препроцессор может быть запрограммирован для написания кода для вас. Я думаю, что это сильно иллюстрирует разницу между удобочитаемостью и ремонтопригодностью. Код трудно читать, но если enum содержит несколько сотен элементов, вы можете добавлять, удалять или переставлять элементы и при этом быть уверенным, что сгенерированный код не содержит ошибок.
Лично я бы пошел на что-нибудь простое и использовал бы для этого оператор.
Учитывая следующее перечисление:
enum WeekDay { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };
Мы можем создать оператор для вывода результата в виде std::ostream
.
std::ostream &operator<<(std::ostream &stream, const WeekDay day) {
switch (day) {
case MONDAY:
stream << "Monday";
break;
case TUESDAY:
stream << "Tuesday";
break;
case WEDNESDAY:
stream << "Wednesday";
break;
case THURSDAY:
stream << "Thursday";
break;
case FRIDAY:
stream << "Friday";
break;
case SATURDAY:
stream << "Saturday";
break;
case SUNDAY:
stream << "Sunday";
break;
}
return stream;
}
Шаблонный код действительно довольно большой по сравнению с некоторыми другими методами, представленными в этом потоке. Тем не менее, он имеет то преимущество, что он довольно прост и удобен в использовании.
std::cout << "First day of the week is " << WeekDay::Monday << std::endl;
Мой собственный ответ, не использующий повышение - использующий мой собственный подход без магии сильного определения, и это решение имеет ограничение, заключающееся в невозможности определить конкретное значение перечисления.
#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
Вот метод Old Skool (раньше широко использовался в gcc) с использованием только препроцессора Си. Полезно, если вы генерируете дискретные структуры данных, но должны поддерживать порядок между ними. Записи в 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 )
На это есть много других ответов, но я думаю, что лучший способ - использовать функции C++17 и использовать constexpr, чтобы переводы выполнялись во время компиляции. Это типобезопасный вариант, и нам не нужно возиться с макросами. Смотри ниже:
//enum.hpp
#include <array>
#include <string_view>
namespace Enum
{
template <class ENUM_TYPE, size_t SIZE>
constexpr ENUM_TYPE findKey(const char * value, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
index = (index == -1) ? map.size() : index;
return
(index == 0) ? throw "Value not in map":
(std::string_view(map[index - 1].second) == value) ? map[index- 1].first:
findKey(value, map, index - 1);
};
template <class ENUM_TYPE, size_t SIZE>
constexpr const char * findValue(ENUM_TYPE key, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
index = (index == -1) ? map.size() : index;
return
(index == 0) ? throw "Key not in map":
(map[index - 1].first == key) ? map[index- 1].second:
findValue(key, map, index - 1);
};
}
//test_enum.hpp
#include "enum.hpp"
namespace TestEnum
{
enum class Fields
{
Test1,
Test2,
Test3,
//This has to be at the end
NUMBER_OF_FIELDS
};
constexpr std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> GetMap()
{
std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> map =
{
{
{Fields::Test1, "Test1"},
{Fields::Test2, "Test2"},
{Fields::Test3, "Test3"},
}
};
return map;
};
constexpr Fields StringToEnum(const char * value)
{
return Enum::findKey(value, GetMap());
}
constexpr const char * EnumToString(Fields key)
{
return Enum::findValue(key, GetMap());
}
}
Затем это можно легко использовать, чтобы ошибки строкового ключа обнаруживались во время компиляции:
#include "test_enum.hpp"
int main()
{
auto constexpr a = TestEnum::StringToEnum("Test2"); //a = TestEnum::Fields::Test2
auto constexpr b = TestEnum::EnumToString(TestEnum::Fields::Test1); //b = "Test1"
auto constexpr c = TestEnum::StringToEnum("AnyStringNotInTheMap"); //compile time failure
return 0;
}
Код более подробный, чем некоторые другие решения, но мы можем легко выполнить преобразование Enum в String и преобразование String в Enum во время компиляции и обнаружить ошибки типа. С некоторыми из будущих функций C++20 это, вероятно, можно будет еще немного упростить.
Чтобы расширить ответ Джеймса, кто-то хочет, чтобы некоторый пример кода поддерживал определение 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, теперь измените код Джеймса, вы получите полную мощность.
Моя реализация, возможно, не самая простая, поэтому, если вы не найдете никакого чистого кода, мой для вашей справки.
Чистое, безопасное решение в чистом стандарте C:
#include <stdio.h>
#define STRF(x) #x
#define STRINGIFY(x) STRF(x)
/* list of enum constants */
#define TEST_0 hello
#define TEST_1 world
typedef enum
{
TEST_0,
TEST_1,
TEST_N
} test_t;
const char* test_str[]=
{
STRINGIFY(TEST_0),
STRINGIFY(TEST_1),
};
int main()
{
_Static_assert(sizeof test_str / sizeof *test_str == TEST_N,
"Incorrect number of items in enum or look-up table");
printf("%d %s\n", hello, test_str[hello]);
printf("%d %s\n", world, test_str[world]);
test_t x = world;
printf("%d %s\n", x, test_str[x]);
return 0;
}
Выход
0 hello
1 world
1 world
обоснование
При решении основной проблемы "иметь константы перечисления с соответствующими строками" разумный программист предложит следующие требования:
- Избегайте повторения кода (принцип "СУХОЙ").
- Код должен быть масштабируемым, обслуживаемым и безопасным, даже если элементы добавляются или удаляются внутри перечисления.
- Весь код должен быть высокого качества: легко читаемый, простой в обслуживании.
Первое требование, а может быть и второе, может быть выполнено с помощью различных грязных макро-решений, таких как печально известный трюк с "x-макросом" или другие виды магии макросов. Проблема с такими решениями заключается в том, что они оставляют вам совершенно нечитаемый беспорядок загадочных макросов - они не соответствуют третьему требованию, описанному выше.
Единственное, что здесь необходимо - это иметь таблицу поиска строк, к которой мы можем получить доступ, используя переменную 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;
Теперь мы можем проверить во время компиляции, что количество элементов в перечислении равно количеству элементов в справочной таблице, предпочтительно со статическим утверждением C11:
_Static_assert(sizeof test_str / sizeof *test_str == TEST_N,
"Incorrect number of items in enum or look-up table");
(Есть уродливые, но полностью функциональные способы создания статических утверждений и в старых версиях стандарта C, если кто-то настаивает на использовании компиляторов динозавров. Что касается C++, он также поддерживает статические утверждения.)
Как примечание, в C11 мы также можем добиться более высокой безопасности типов, изменив макрос stringify:
#define STRINGIFY(x) _Generic((x), int : STRF(x))
(int
потому что константы перечисления на самом деле имеют тип int
не test_t
)
Это предотвратит такой код STRINGIFY(random_stuff)
от компиляции.
Расширяя ответ @Reno, вот рабочий пример,
#include <stdio.h>
//debug macro, keep it defined or undefine
#define DEBUG
//#undef DEBUG
#ifndef DEBUG
#define DECL_ENUM_ELEMENT( element ) element
#define BEGIN_ENUM( ENUM_NAME ) typedef enum
#define END_ENUM( ENUM_NAME ) ENUM_NAME;
#else
#define DECL_ENUM_ELEMENT( element ) #element
#define BEGIN_ENUM( ENUM_NAME ) const char* gs_##ENUM_NAME [] =
#define END_ENUM( ENUM_NAME ); \
int gs_##ENUM_NAME##size = sizeof(gs_##ENUM_NAME)/sizeof(gs_##ENUM_NAME[0]); \
const char* MatchEnumToString##ENUM_NAME(int index) { \
if (index > (gs_##ENUM_NAME##size - 1) || index < 0) \
{ \
return "ERR: invalid"; \
} \
else \
return gs_##ENUM_NAME [index]; \
}
#endif
BEGIN_ENUM(Days)
{
DECL_ENUM_ELEMENT(sunday),
DECL_ENUM_ELEMENT(monday),
DECL_ENUM_ELEMENT(tuesday),
DECL_ENUM_ELEMENT(wednesday),
DECL_ENUM_ELEMENT(thursday),
DECL_ENUM_ELEMENT(friday),
DECL_ENUM_ELEMENT(saturday)
}
END_ENUM(Days)
BEGIN_ENUM(fruit)
{
DECL_ENUM_ELEMENT(apple),
DECL_ENUM_ELEMENT(orange),
DECL_ENUM_ELEMENT(mango)
}
END_ENUM(fruit)
void match_etos( int index )
{
#ifdef DEBUG
printf("Day is %s ,", MatchEnumToStringDays(index) );
printf("Fruit is %s\n", MatchEnumToStringfruit(index) );
#else
printf("disabled match_etos, index: %d\n", index);
#endif
}
int main()
{
match_etos(0);
match_etos(1);
match_etos(2);
match_etos(3);
match_etos(4);
match_etos(5);
match_etos(6);
match_etos(-43);
#ifdef DEBUG
printf("gs_Dayssize %d, gs_fruitsize %d\n", gs_Dayssize, gs_fruitsize);
#endif
return 0;
}
Скомпилируйте приведенный выше пример с
g++ <savedfilename>.cpp
./a.out
мой вывод, когда DEBUG определен,
Day is sunday ,Fruit is apple
Day is monday ,Fruit is orange
Day is tuesday ,Fruit is mango
Day is wednesday ,Fruit is ERR: invalid
Day is thursday ,Fruit is ERR: invalid
Day is friday ,Fruit is ERR: invalid
Day is saturday ,Fruit is ERR: invalid
Day is ERR: invalid ,Fruit is ERR: invalid
gs_Dayssize 7, gs_fruitsize 3
Когда DEBUG не определен,
disabled match_etos, index: 0
disabled match_etos, index: 1
disabled match_etos, index: 2
disabled match_etos, index: 3
disabled match_etos, index: 4
disabled match_etos, index: 5
disabled match_etos, index: 6
disabled match_etos, index: -43
если макрос DEBUG определен, он создаст массив, если его не определенный исходный enum будет создан после предварительной обработки.
В с ++ вот так:
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 ) );
#pragma once
#include <string>
#include <vector>
#include <sstream>
#include <algorithm>
namespace StringifyEnum
{
static std::string TrimEnumString(const std::string &s)
{
std::string::const_iterator it = s.begin();
while (it != s.end() && isspace(*it)) { it++; }
std::string::const_reverse_iterator rit = s.rbegin();
while (rit.base() != it && isspace(*rit)) { ++rit; }
return std::string(it, rit.base());
}
static std::vector<std::string> SplitEnumArgs(const char* szArgs, int nMax)
{
std::vector<std::string> enums;
std::stringstream ss(szArgs);
std::string strSub;
int nIdx = 0;
while (ss.good() && (nIdx < nMax)) {
getline(ss, strSub, ',');
enums.push_back(StringifyEnum::TrimEnumString(strSub));
++nIdx;
}
return std::move(enums);
}
}
#define DECLARE_ENUM_SEQ(ename, n, ...) \
enum class ename { __VA_ARGS__ }; \
const int MAX_NUMBER_OF_##ename(n); \
static std::vector<std::string> ename##Strings = StringifyEnum::SplitEnumArgs(#__VA_ARGS__, MAX_NUMBER_OF_##ename); \
inline static std::string ename##ToString(ename e) { \
return ename##Strings.at((int)e); \
} \
inline static ename StringTo##ename(const std::string& en) { \
const auto it = std::find(ename##Strings.begin(), ename##Strings.end(), en); \
if (it != ename##Strings.end()) \
return (ename) std::distance(ename##Strings.begin(), it); \
throw std::runtime_error("Could not resolve string enum value"); \
}
Это расширенная версия перечисления для расширенного класса... она не добавляет никаких других значений перечисления, кроме предоставленных.
Использование: DECLARE_ENUM_SEQ(CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)
То, что я сделал, является комбинацией того, что я видел здесь и в подобных вопросах на этом сайте. Я сделал это 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 внутри класса.
Если вы можете обойти это. Я думаю, особенно, как это использовать, это красиво и скудно. Преимущества:
- Легко использовать.
- Не требуется разделения строк во время выполнения.
- Отдельные строки доступны во время компиляции.
- Легко читать. Первому набору макросов может потребоваться дополнительная секунда, но на самом деле это не так сложно.
Чистое решение этой проблемы:
#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>";
}
Преимущество этого решения в том, что оно простое, а также создание функции может быть легко выполнено с помощью копирования и замены. Обратите внимание, что если вы собираетесь выполнять много преобразований и у вашего перечисления слишком много возможных значений, это решение может потребовать значительных ресурсов процессора.