Я думал, что решение типа 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
Старый ответ ниже довольно плох, пожалуйста, не используйте это. :)
Старый ответ:
Я искал способ, который решает эту проблему, не слишком меняя синтаксис объявления перечислений. Я пришел к решению, которое использует препроцессор для извлечения строки из строкового объявления enum.
Я могу определить не разреженные перечисления следующим образом:
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");
}
}}
Когда мне понадобится поддержка разреженного enum и когда у меня будет больше времени, я улучшу реализации to_string и from_string с boost :: xpressive, но это будет затраты на компиляцию из-за важности выполненных шаблонов и сгенерированного исполняемого файла, вероятно, будут действительно больше. Но у этого есть преимущество в том, что он будет более читабельным и удобным для пользователя, чем этот уродливый код для ручной обработки строк. : D
В противном случае я всегда использовал boost :: bimap для выполнения таких сопоставлений между значением enums и строкой, но это нужно поддерживать вручную.