Наивное решение, конечно, состоит в том, чтобы написать функцию для каждого перечисления, которая выполняет преобразование в строку:
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.