Мне нужно было, чтобы это работало в обоих направлениях, и я часто встраивал свои перечисления в содержащий класс, и поэтому я начал с решения Джеймсом МакНеллисом способом, в верхней части этих ответов, но я нашел это решение. Заметьте также, что я предпочитаю 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); }