How to convert an enum to string in C++
mariusbancila.ro
mariusbancila.ro
[1] https://github.com/Hirrolot/metalang99
[2] https://hirrolot.github.io/posts/pretty-printable-enumeratio...
DEF_ENUM(
EnumName,
(Name1, value1)
(Name2, value2)
...);
Is also possible. I'm not the greatest fan of PP metaprogramming, but I think this is acceptable.With additional Magic it might be possible to do separate compilation of the to_string() function.
template <typename Enum, Enum V> constexpr constexpr_string enum_name() {...}
For all possible value of V from 0 to 256.This is very compiler-specific as the actual value of __PRETTY_FUNCTION__ is different for each compiler, and even then, not really specified
(__PRETTY_FUNCTION__ is a builtin macro that expand to a string literal representing the function, for example "the_namespace::enum_name<day, day::Monday>()")
Example for GCC/Clang/MSVC: https://wandbox.org/permlink/eMt6MxecI4VoJD9m
Object Pascal had the ability to describe its own types since Delphi 1.0 under Windows 3.1, it is how object serialization works (forms, controls and other components are just object instances serialized to/from disk).
This is data the compiler already has in memory, having some method to save that data to the final executable and a few functions in the standard library to parse that data and give back meaningful results shouldn't really be that much of an effort (and still be very portable), and yet for some reason C and C++ do not provide it even though other languages did decades ago.
Mesa, Eiffel, D, Mesa/Cedar, Delphi, Oberon, Oberon-2, Active Oberon, Clipper, Visual Basic, Component Pascal, Modula-3,... are all compile to native languages with reflection, and the list isn't even exaustive.
const my_enum = enum {
one,
two,
three,
pub fn str(self: @This()) []const u8 {
return switch (self) {
inline else => |tag| @tagName(tag),
};
}
}
Inline else means the switch/case is expanded at compile time. my_enum.str() enum _ : u8 {
kMonday,
kTuesday,
...
kSize
};
constexpr const char* toString[kSize] = {
"Monday",
"Tuesday",
...
}
auto day = toString[kTuesday];
Edit: For `constexpr` replace `std::string` with `const char *`.But you had to manually code that "toString[] = {"Monday",}"
Your "simple solution" misses the point of the article which is to have the compiler automatically generating the serialized string representation of the enum without a human programmer having to manually code the switch case statements or array lookups and also avoid manually keep the enum & array in sync.
The idea is to have the enum definition itself acting as a "single source of truth" and comparing various language features or libraries to derive the code to write out enum names as strings. In other words, the author is exploring various techniques of pseudo "introspection"/"reflection" for the enum data type.
I agree, in some cases automatic generation might save you some typing.
It might be also painful to maintain, as changing a string requires to change a corresponding enum everywhere in the code.
Or string_view [1], since it's a literal.
[1] https://en.cppreference.com/w/cpp/string/basic_string_view
So instead, I've resigned myself to scripted code generation at build time, and it's one of the things about C++ that makes me feel kind of grossed out.
It was heavily used at a former employer of mine, so definitely a solid production-ready solution.
type Day is (Monday, Tuesday, Wednesday);
Var : Day := Monday;
Then I can simply do Print_Line ("Day is: " & Var'Image);
What's cool is that I can also covert a string into its corresponding enum: Var : Day := Day'Value ("Monday");This is even more important if the enums drive a serializer/deserializer and the hand-coded std::map<string,int> doesn't match the hand coded std::map<int,string> or if either doesn't match the actual enum. Or if you set an alert on the wrong error message because it's spelled differently in the code and in the time-series database.
Source: have replaced production C++ enums and arrays/string-maps with macros after the originals got out of sync after a few years.
Yes, you are doing grunt work that the machine is perfectly capable of doing and in theory has all the knowledge during the compilation stage. Sadly because of C++'s limitations it needs these monstrous macro solutions. Other languages let you use some function or intrinsic or whatever for that.
Gameplay code often has a lot of frequently changing enums. Audio IDs, events, modifier IDs (surfaces like dirt/grass/etc.), achievement IDs, entity types, resources, ... - these often eventually become tool-generated, as even xmacros style stuff gets annoying to maintain, and need to be exiled into more machine-readable and machine-manipulatable formats.
A lot of projects eventually ditch generating e.g. C++ enums for many of these IDs - the constant rebuilds get too expensive - but that also means you lose out on static compile time validation of your gameplay code...
> What is so bad about that?
Bugs start cropping up in statistically appreciable amounts when the enums grow to 1000s of entries (which might all be found in merely one of 100s of enums) - a forgotten entry here, a typo there, a blithely Ctrl+C Ctrl+Ved entry causing subtly incorrect (de)serialization in this one intractable edge case, causing bug reports that take days to wind through the entire QA pipeline before they reach an actual engineer - who will then have to hope and pray for accurate bug reproduction steps, and that they don't get too misled by their debug tools lying to them... a single bug can easily waste more time than it'd take to convert a lot of enums to macro heaven, and there won't be just a single bug.
> How much harder is it to just verify the stupid simple function version going through macros hell?
One of my open source side projects is winresult, ≈58KLOC of mostly autogenerated rust code and doc comments + ≈18KLOC of mostly autogenerated natvis files, all to handle one enum-like thing: windows HRESULTs. That... would be a lot to hand-write/review/verify/merge.
https://docs.rs/winresult/ https://github.com/MaulingMonkey/winresult/
Another project: thindx. Bindings for d3dcompiler + d3d9 + xinput have a lot of enums and flags:
• 50 results in 49 files for flags!: https://github.com/search?q=repo%3AMaulingMonkey%2Fthindx%20...
• 77 results in 69 files for enumish!: https://github.com/search?q=repo%3AMaulingMonkey%2Fthindx+en...
• I still do things by hand somtimes, for reasons that elude my recollection: https://github.com/MaulingMonkey/thindx/blob/127d75f9de91f73...
And these are baby numbers compared to an actual professional gamedev codebase. Attention to detail makes me fairly comfortable with this much hand-generated nonsense in my one man show, but bugs still crop up... and there are people I would dread handing maintainence of such a project over to that I've worked with in a professional capacity, who simply do not care to exercise the same level of care as I do when editing such stuff.
enum class days {
MONDAY = 1,
TUESDAY = 2,
...
};
constexpr const char* str_day(days d) {
switch (d) {
case days::MONDAY: return "MONDAY";
case days::TUESDAY: return "TUESDAY";
...
}
}
maybe just throw in a 'noexcept' for good measure as well.serialization || deserialization of enum values is a different concern.
it all depends on your need though, and you can spend as little or as much time as you deem necessary.