Простой способ использовать переменные типов перечисления как строку в C?
-
02-07-2019 - |
Вопрос
Вот что я пытаюсь сделать:
typedef enum { ONE, TWO, THREE } Numbers;
Я пытаюсь написать функцию, которая выполняла бы случай переключения, аналогичный следующему:
char num_str[10];
int process_numbers_str(Numbers num) {
switch(num) {
case ONE:
case TWO:
case THREE:
{
strcpy(num_str, num); //some way to get the symbolic constant name in here?
} break;
default:
return 0; //no match
return 1;
}
Вместо определения в каждом случае, есть ли способ установить его с помощью переменной перечисления, как я пытаюсь сделать выше?
Решение
Нет встроенного решения.Самый простой способ — использовать массив char*
где значение int перечисления указывает на строку, содержащую описательное имя этого перечисления.Если у вас есть редкий enum
(тот, который не начинается с 0 или имеет пробелы в нумерации), где некоторые из int
сопоставления достаточно велики, чтобы сделать сопоставление на основе массива непрактичным, тогда вместо этого вы можете использовать хеш-таблицу.
Другие советы
Техника от Сделать что-то одновременно и идентификатором C, и строкой? можно использовать здесь.
Как обычно бывает с такими препроцессорами, написание и понимание части препроцессора может быть сложной задачей и включает в себя передачу макросов другим макросам и использование операторов # и ##, но использовать ее очень просто.Я считаю этот стиль очень полезным для длинных перечислений, где сохранение одного и того же списка дважды может быть очень затруднительным.
Заводской код — вводится только один раз, обычно скрыт в заголовке:
перечислениеFactory.h:
// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,
// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;
// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;
/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
enum EnumType { \
ENUM_DEF(ENUM_VALUE) \
}; \
const char *GetString(EnumType dummy); \
EnumType Get##EnumType##Value(const char *string); \
/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
const char *GetString(EnumType value) \
{ \
switch(value) \
{ \
ENUM_DEF(ENUM_CASE) \
default: return ""; /* handle input error */ \
} \
} \
EnumType Get##EnumType##Value(const char *str) \
{ \
ENUM_DEF(ENUM_STRCMP) \
return (EnumType)0; /* handle input error */ \
} \
Заводской б/у
someEnum.h:
#include "enumFactory.h"
#define SOME_ENUM(XX) \
XX(FirstValue,) \
XX(SecondValue,) \
XX(SomeOtherValue,=50) \
XX(OneMoreValue,=100) \
DECLARE_ENUM(SomeEnum,SOME_ENUM)
someEnum.cpp:
#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)
Этот метод можно легко расширить, чтобы макросы XX принимали больше аргументов, а также можно было подготовить больше макросов для замены XX для различных нужд, аналогично трем, которые я предоставил в этом примере.
Сравнение с X-макросами с использованием #include/#define/#undef
Хотя это похоже на X-Macros, о которых упоминали другие, я думаю, что это решение более элегантно, поскольку оно не требует ничего #undefing, что позволяет вам скрыть более сложные вещи, находящиеся на заводе: файл заголовка - файл заголовка это то, что вы вообще не трогаете, когда вам нужно определить новое перечисление, поэтому новое определение перечисления намного короче и понятнее.
// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
ENUM(ONE),
ENUM(TWO),
ENUM(FOUR)
ENUM_END( Numbers )
// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };
// Now in one and only one .c file, redefine the ENUM macros and reinclude
// the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED // whatever you need to do to enable reinclusion
#include "numbers.h"
// Now you can do exactly what you want to do, with no retyping, and for any
// number of enumerated types defined with the ENUM macro family
// Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
switch(num) {
case ONE:
case TWO:
case THREE:
{
strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
} break;
default:
return 0; //no match
return 1;
}
// Sweet no ? After being frustrated by this for years, I finally came up
// with this solution for my most recent project and plan to reuse the idea
// forever
Определенно есть способ сделать это - используйте X() макросы.Эти макросы используют препроцессор C для создания перечислений, массивов и блоков кода из списка исходных данных.Вам нужно только добавить новые элементы в #define, содержащие макрос X().Оператор переключения будет автоматически расширяться.
Ваш пример можно записать следующим образом:
// Source data -- Enum, String
#define X_NUMBERS \
X(ONE, "one") \
X(TWO, "two") \
X(THREE, "three")
...
// Use preprocessor to create the Enum
typedef enum {
#define X(Enum, String) Enum,
X_NUMBERS
#undef X
} Numbers;
...
// Use Preprocessor to expand data into switch statement cases
switch(num)
{
#define X(Enum, String) \
case Enum: strcpy(num_str, String); break;
X_NUMBERS
#undef X
default: return 0; break;
}
return 1;
Есть более эффективные способы (т.использование X-макросов для создания массива строк и индекса перечисления), но это самая простая демонстрация.
Я знаю, что у вас есть пара хороших и убедительных ответов, но знаете ли вы об операторе # в препроцессоре C?
Это позволяет вам сделать это:
#define MACROSTR(k) #k
typedef enum {
kZero,
kOne,
kTwo,
kThree
} kConst;
static char *kConstStr[] = {
MACROSTR(kZero),
MACROSTR(kOne),
MACROSTR(kTwo),
MACROSTR(kThree)
};
static void kConstPrinter(kConst k)
{
printf("%s", kConstStr[k]);
}
ЦЕЛОВАТЬ.Вы будете делать со своими перечислениями множество других операций с переключателями/регистрами, так почему же печать должна быть другой?Забыть дело во время печати не так уж и сложно, если учесть, что существует около 100 других мест, где можно забыть дело.Просто скомпилируйте -Wall, который будет предупреждать о неполных совпадениях регистра.Не используйте «по умолчанию», потому что это сделает переключение исчерпывающим и вы не получите предупреждений.Вместо этого позвольте коммутатору выйти и разобраться со случаем по умолчанию следующим образом...
const char *myenum_str(myenum e)
{
switch(e) {
case ONE: return "one";
case TWO: return "two";
}
return "invalid";
}
C или C++ не предоставляют такой функциональности, хотя она мне часто требовалась.
Следующий код работает, хотя он лучше всего подходит для неразреженных перечислений.
typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);
Под неразреженным я имею в виду не такой формы
typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;
поскольку в нем есть огромные пробелы.
Преимущество этого метода в том, что определения перечислений и строк помещаются рядом друг с другом;наличие оператора переключения в функции разделяет их.Это означает, что у вас меньше шансов изменить одно без другого.
Пытаться Преобразование перечислений C++ в строки.А Комментарии есть улучшения, которые решают проблему, когда элементы перечисления имеют произвольные значения.
Использование повышение::препроцессор делает возможным элегантное решение, подобное следующему:
Шаг 1:включите заголовочный файл:
#include "EnumUtilities.h"
Шаг 2:объявите объект перечисления, используя следующий синтаксис:
MakeEnum( TestData,
(x)
(y)
(z)
);
Шаг 3:используйте ваши данные:
Получение количества элементов:
td::cout << "Number of Elements: " << TestDataCount << std::endl;
Получение связанной строки:
std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl;
std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl;
std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl;
Получение значения перечисления из связанной строки:
std::cout << "Value of x is " << TestData2Enum("x") << std::endl;
std::cout << "Value of y is " << TestData2Enum("y") << std::endl;
std::cout << "Value of z is " << TestData2Enum("z") << std::endl;
Это выглядит чистым и компактным, без дополнительных файлов.Код, который я написал в EnumUtilities.h, выглядит следующим образом:
#include <boost/preprocessor/seq/for_each.hpp>
#include <string>
#define REALLY_MAKE_STRING(x) #x
#define MAKE_STRING(x) REALLY_MAKE_STRING(x)
#define MACRO1(r, data, elem) elem,
#define MACRO1_STRING(r, data, elem) case elem: return REALLY_MAKE_STRING(elem);
#define MACRO1_ENUM(r, data, elem) if (REALLY_MAKE_STRING(elem) == eStrEl) return elem;
#define MakeEnum(eName, SEQ) \
enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \
last_##eName##_enum}; \
const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
static std::string eName##2String(const enum eName eel) \
{ \
switch (eel) \
{ \
BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \
default: return "Unknown enumerator value."; \
}; \
}; \
static enum eName eName##2Enum(const std::string eStrEl) \
{ \
BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \
return (enum eName)0; \
};
Есть некоторые ограничения, т.е.те из boost::preprocessor.В этом случае список констант не может содержать более 64 элементов.
Следуя той же логике, вы также можете подумать о создании разреженного перечисления:
#define EnumName(Tuple) BOOST_PP_TUPLE_ELEM(2, 0, Tuple)
#define EnumValue(Tuple) BOOST_PP_TUPLE_ELEM(2, 1, Tuple)
#define MACRO2(r, data, elem) EnumName(elem) EnumValue(elem),
#define MACRO2_STRING(r, data, elem) case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem));
#define MakeEnumEx(eName, SEQ) \
enum eName { \
BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \
last_##eName##_enum }; \
const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
static std::string eName##2String(const enum eName eel) \
{ \
switch (eel) \
{ \
BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \
default: return "Unknown enumerator value."; \
}; \
};
В этом случае синтаксис следующий:
MakeEnumEx(TestEnum,
((x,))
((y,=1000))
((z,))
);
Использование аналогично описанному выше (за исключением функции eName##2Enum, которую вы можете попытаться экстраполировать из предыдущего синтаксиса).
Я тестировал его на Mac и Linux, но имейте в виду, что boost::preprocessor может быть не полностью переносимым.
Объединив некоторые из приведенных здесь техник, я получил простейшую форму:
#define MACROSTR(k) #k
#define X_NUMBERS \
X(kZero ) \
X(kOne ) \
X(kTwo ) \
X(kThree ) \
X(kFour ) \
X(kMax )
enum {
#define X(Enum) Enum,
X_NUMBERS
#undef X
} kConst;
static char *kConstStr[] = {
#define X(String) MACROSTR(String),
X_NUMBERS
#undef X
};
int main(void)
{
int k;
printf("Hello World!\n\n");
for (k = 0; k < kMax; k++)
{
printf("%s\n", kConstStr[k]);
}
return 0;
}
Если вы используете gcc, можно использовать:
const char * enum_to_string_map[]={ [enum1]='string1', [enum2]='string2'};
Тогда просто позвоните, например
enum_to_string_map[enum1]
Ознакомьтесь с идеями на Исследовательские лаборатории Mu Dynamics — Архив блога.Я нашел это ранее в этом году — я забыл точный контекст, в котором я это встретил — и адаптировал его в этот код.Мы можем обсуждать преимущества добавления буквы E спереди;он применим к конкретной решаемой проблеме, но не является частью общего решения.Я спрятал это в папке «виньетки», где храню интересные фрагменты кода на случай, если они мне понадобятся позже.Мне неловко признаться, что я не записал, откуда в тот момент пришла эта идея.
Заголовок:вставить1.h
/*
@(#)File: $RCSfile: paste1.h,v $
@(#)Version: $Revision: 1.1 $
@(#)Last changed: $Date: 2008/05/17 21:38:05 $
@(#)Purpose: Automated Token Pasting
*/
#ifndef JLSS_ID_PASTE_H
#define JLSS_ID_PASTE_H
/*
* Common case when someone just includes this file. In this case,
* they just get the various E* tokens as good old enums.
*/
#if !defined(ETYPE)
#define ETYPE(val, desc) E##val,
#define ETYPE_ENUM
enum {
#endif /* ETYPE */
ETYPE(PERM, "Operation not permitted")
ETYPE(NOENT, "No such file or directory")
ETYPE(SRCH, "No such process")
ETYPE(INTR, "Interrupted system call")
ETYPE(IO, "I/O error")
ETYPE(NXIO, "No such device or address")
ETYPE(2BIG, "Arg list too long")
/*
* Close up the enum block in the common case of someone including
* this file.
*/
#if defined(ETYPE_ENUM)
#undef ETYPE_ENUM
#undef ETYPE
ETYPE_MAX
};
#endif /* ETYPE_ENUM */
#endif /* JLSS_ID_PASTE_H */
Пример источника:
/*
@(#)File: $RCSfile: paste1.c,v $
@(#)Version: $Revision: 1.2 $
@(#)Last changed: $Date: 2008/06/24 01:03:38 $
@(#)Purpose: Automated Token Pasting
*/
#include "paste1.h"
static const char *sys_errlist_internal[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) desc,
#include "paste1.h"
0
#undef ETYPE
};
static const char *xerror(int err)
{
if (err >= ETYPE_MAX || err <= 0)
return "Unknown error";
return sys_errlist_internal[err];
}
static const char*errlist_mnemonics[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) [E ## val] = "E" #val,
#include "paste1.h"
#undef ETYPE
};
#include <stdio.h>
int main(void)
{
int i;
for (i = 0; i < ETYPE_MAX; i++)
{
printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
}
return(0);
}
Не обязательно самое чистое в мире использование препроцессора C, но оно предотвращает многократную запись материала.
Если индекс перечисления начинается с 0, вы можете поместить имена в массив char* и проиндексировать их значением перечисления.
#define stringify( name ) # name
enum MyEnum {
ENUMVAL1
};
...stuff...
stringify(EnumName::ENUMVAL1); // Returns MyEnum::ENUMVAL1
Я создал простой шаблонный класс streamable_enum
который использует потоковые операторы <<
и >>
и основан на std::map<Enum, std::string>
:
#ifndef STREAMABLE_ENUM_HPP
#define STREAMABLE_ENUM_HPP
#include <iostream>
#include <string>
#include <map>
template <typename E>
class streamable_enum
{
public:
typedef typename std::map<E, std::string> tostr_map_t;
typedef typename std::map<std::string, E> fromstr_map_t;
streamable_enum()
{}
streamable_enum(E val) :
Val_(val)
{}
operator E() {
return Val_;
}
bool operator==(const streamable_enum<E>& e) {
return this->Val_ == e.Val_;
}
bool operator==(const E& e) {
return this->Val_ == e;
}
static const tostr_map_t& to_string_map() {
static tostr_map_t to_str_(get_enum_strings<E>());
return to_str_;
}
static const fromstr_map_t& from_string_map() {
static fromstr_map_t from_str_(reverse_map(to_string_map()));
return from_str_;
}
private:
E Val_;
static fromstr_map_t reverse_map(const tostr_map_t& eToS) {
fromstr_map_t sToE;
for (auto pr : eToS) {
sToE.emplace(pr.second, pr.first);
}
return sToE;
}
};
template <typename E>
streamable_enum<E> stream_enum(E e) {
return streamable_enum<E>(e);
}
template <typename E>
typename streamable_enum<E>::tostr_map_t get_enum_strings() {
// \todo throw an appropriate exception or display compile error/warning
return {};
}
template <typename E>
std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) {
auto& mp = streamable_enum<E>::to_string_map();
auto res = mp.find(e);
if (res != mp.end()) {
os << res->second;
} else {
os.setstate(std::ios_base::failbit);
}
return os;
}
template <typename E>
std::istream& operator>>(std::istream& is, streamable_enum<E>& e) {
std::string str;
is >> str;
if (str.empty()) {
is.setstate(std::ios_base::failbit);
}
auto& mp = streamable_enum<E>::from_string_map();
auto res = mp.find(str);
if (res != mp.end()) {
e = res->second;
} else {
is.setstate(std::ios_base::failbit);
}
return is;
}
#endif
Использование:
#include "streamable_enum.hpp"
using std::cout;
using std::cin;
using std::endl;
enum Animal {
CAT,
DOG,
TIGER,
RABBIT
};
template <>
streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() {
return {
{ CAT, "Cat"},
{ DOG, "Dog" },
{ TIGER, "Tiger" },
{ RABBIT, "Rabbit" }
};
}
int main(int argc, char* argv []) {
cout << "What animal do you want to buy? Our offering:" << endl;
for (auto pr : streamable_enum<Animal>::to_string_map()) { // Use from_string_map() and pr.first instead
cout << " " << pr.second << endl; // to have them sorted in alphabetical order
}
streamable_enum<Animal> anim;
cin >> anim;
if (!cin) {
cout << "We don't have such animal here." << endl;
} else if (anim == Animal::TIGER) {
cout << stream_enum(Animal::TIGER) << " was a joke..." << endl;
} else {
cout << "Here you are!" << endl;
}
return 0;
}
Вот решение с использованием макросов со следующими функциями:
записывайте каждое значение перечисления только один раз, чтобы не было необходимости поддерживать двойные списки
не храните значения перечисления в отдельном файле, который позже #include, чтобы я мог записать его куда захочу.
не заменяйте само перечисление, я все равно хочу определить тип перечисления, но в дополнение к этому я хочу иметь возможность сопоставлять каждое имя перечисления с соответствующей строкой (чтобы не влиять на устаревший код)
поиск должен быть быстрым, поэтому желательно без переключения регистров для этих огромных перечислений
Я подумал, что такое решение, как Boost.Fusion, для адаптации структур и классов, было бы неплохо, в какой-то момент у них даже было такое решение, чтобы использовать перечисления в качестве последовательности слияния.
Поэтому я сделал всего несколько небольших макросов для генерации кода для печати перечислений.Это не идеально и не имеет ничего общего с шаблонным кодом, сгенерированным Boost.Fusion, но может использоваться как макросы Boost Fusion.Я действительно хочу сгенерировать типы, необходимые Boost.Fusion для интеграции в эту инфраструктуру, которая позволяет печатать имена членов структуры, но это произойдет позже, а пока это просто макросы:
#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#include <swissarmyknife/detail/config.hpp>
#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C( \
R, unused, ENUMERATION_ENTRY) \
case ENUMERATION_ENTRY: \
return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY); \
break;
/**
* \brief Adapts ENUM to reflectable types.
*
* \param ENUM_TYPE To be adapted
* \param ENUMERATION_SEQ Sequence of enum states
*/
#define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ) \
inline std::string to_string(const ENUM_TYPE& enum_value) { \
switch (enum_value) { \
BOOST_PP_SEQ_FOR_EACH( \
SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C, \
unused, ENUMERATION_SEQ) \
default: \
return BOOST_PP_STRINGIZE(ENUM_TYPE); \
} \
} \
\
inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
os << to_string(value); \
return os; \
}
#endif
Старый ответ ниже довольно плох, пожалуйста, не используйте его.:)
Старый ответ:
Я искал способ решить эту проблему, не слишком сильно меняя синтаксис объявления перечислений.Я пришел к решению, которое использует препроцессор для извлечения строки из объявления строкового перечисления.
Я могу определить неразреженные перечисления следующим образом:
SMART_ENUM(State,
enum State {
RUNNING,
SLEEPING,
FAULT,
UNKNOWN
})
И взаимодействовать с ними я могу по-разному:
// With a stringstream
std::stringstream ss;
ss << State::FAULT;
std::string myEnumStr = ss.str();
//Directly to stdout
std::cout << State::FAULT << std::endl;
//to a string
std::string myStr = State::to_string(State::FAULT);
//from a string
State::State myEnumVal = State::from_string(State::FAULT);
На основании следующих определений:
#define SMART_ENUM(enumTypeArg, ...) \
namespace enumTypeArg { \
__VA_ARGS__; \
std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) { \
os << swissarmyknife::enums::to_string(#__VA_ARGS__, val); \
return os; \
} \
\
std::string to_string(const enumTypeArg& val) { \
return swissarmyknife::enums::to_string(#__VA_ARGS__, val); \
} \
\
enumTypeArg from_string(const std::string &str) { \
return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str); \
} \
} \
namespace swissarmyknife { namespace enums {
static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) {
size_t begin = completeEnumDeclaration.find_first_of('{');
size_t end = completeEnumDeclaration.find_last_of('}');
const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );
size_t count = 0;
size_t found = 0;
do {
found = identifiers.find_first_of(",}", found+1);
if (enumVal == count) {
std::string identifiersSubset = identifiers.substr(0, found);
size_t beginId = identifiersSubset.find_last_of("{,");
identifiersSubset = identifiersSubset.substr(beginId+1);
boost::algorithm::trim(identifiersSubset);
return identifiersSubset;
}
++count;
} while (found != std::string::npos);
throw std::runtime_error("The enum declaration provided doesn't contains this state.");
}
template <typename EnumType>
static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) {
size_t begin = completeEnumDeclaration.find_first_of('{');
size_t end = completeEnumDeclaration.find_last_of('}');
const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );
size_t count = 0;
size_t found = 0;
do {
found = identifiers.find_first_of(",}", found+1);
std::string identifiersSubset = identifiers.substr(0, found);
size_t beginId = identifiersSubset.find_last_of("{,");
identifiersSubset = identifiersSubset.substr(beginId+1);
boost::algorithm::trim(identifiersSubset);
if (identifiersSubset == enumStr) {
return static_cast<EnumType>(count);
}
++count;
} while (found != std::string::npos);
throw std::runtime_error("No valid enum value for the provided string");
}
}}
Когда мне понадобится поддержка разреженного перечисления и когда у меня будет больше времени, я улучшу нанизывать и from_string реализации с boost::xpressive, но это будет стоить времени компиляции из-за важного выполнения шаблонов, и сгенерированный исполняемый файл, вероятно, будет действительно больше.Но у этого есть то преимущество, что его будет легче читать и поддерживать, чем этот уродливый код ручной манипуляции со строками. :D
В противном случае я всегда использовал boost::bimap для выполнения таких сопоставлений между значением перечисления и строкой, но его нужно поддерживать вручную.
Поскольку я предпочитаю не использовать макросы по всем обычным причинам, я использовал более ограниченное решение для макросов, преимуществом которого является сохранение макроса объявления перечисления свободным.К недостаткам относится необходимость копировать определение макроса для каждого перечисления и необходимость явного добавления вызова макроса при добавлении значений в перечисление.
std::ostream& operator<<(std::ostream& os, provenance_wrapper::CaptureState cs)
{
#define HANDLE(x) case x: os << #x; break;
switch (cs) {
HANDLE(CaptureState::UNUSED)
HANDLE(CaptureState::ACTIVE)
HANDLE(CaptureState::CLOSED)
}
return os;
#undef HANDLE
}