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 const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName[] = "AAA";
template<> const char MyEnumName[] = "BBB";
template<> const char MyEnumName[] = "CCC";
int main()
{
// Prints "AAA"
std::cout << MyEnumName << '\n';
// Prints "Invalid MyEnum value"
std::cout << MyEnumName(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:
Live example
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.
template
using enum_map = std::map;
// This variable template will create a map for each enum type which is
// instantiated with.
template
enum_map enum_values{};
Then, the variadic template trickery:
template
void initialize() {}
template
void initialize(const ENUM value, const char *name, args ... tail)
{
enum_values.emplace(value, name);
initialize(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::AAA] << '\n';
But can be improved with SFINAE and overloading <<
operator:
template::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.find(value);
return o << (found == enum_values.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.
Live example