I don't know if you're going to like this or not, I'm not pretty happy with this solution but it is a C++14 friendly approach because it is using template variables and abusing template specialization:
enum class MyEnum : std::uint_fast8_t {
AAA,
BBB,
CCC,
};
template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";
int main()
{
// Prints "AAA"
std::cout << MyEnumName<MyEnum::AAA> << '\n';
// Prints "Invalid MyEnum value"
std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
// Well... in fact it prints "Invalid MyEnum value" for any value
// different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.
return 0;
}
The worst about this approach is that is a pain to maintain, but it is also a pain to maintain some of other similar aproaches, aren't they?
Good points about this aproach:
Misterious user673679 you're right; the C++14 variable template approach doesn't handles the runtime case, it was my fault to forget it :(
But we can still use some modern C++ features and variable template plus variadic template trickery to achieve a runtime translation from enum value to string... it is as bothersome as the others but still worth to mention.
Let's start using a template alias to shorten the access to a enum-to-string map:
// enum_map contains pairs of enum value and value string for each enum
// this shortcut allows us to use enum_map<whatever>.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;
// This variable template will create a map for each enum type which is
// instantiated with.
template <typename ENUM>
enum_map<ENUM> enum_values{};
Then, the variadic template trickery:
template <typename ENUM>
void initialize() {}
template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
enum_values<ENUM>.emplace(value, name);
initialize<ENUM>(tail ...);
}
The "best trick" here is the use of variable template for the map which contains the values and names of each enum entry; this map will be the same in each translation unit and have the same name everywhere so is pretty straightforward and neat, if we call the initialize
function like this:
initialize
(
MyEnum::AAA, "AAA",
MyEnum::BBB, "BBB",
MyEnum::CCC, "CCC"
);
We are asigning names to each MyEnum
entry and can be used in runtime:
std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';
But can be improved with SFINAE and overloading <<
operator:
template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
std::ostream &operator <<(std::ostream &o, const ENUM value)
{
static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
auto found = enum_values<ENUM>.find(value);
return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
}
With the correct operator <<
now we can use the enum this way:
std::cout << MyEnum::AAA << '\n';
This is also bothersome to maintain and can be improved, but hope you get the idea.