Contrary to all other similar questions, this question is about using the new C++ features.
- 2008 c Is there a simple way to convert C++ enum to string?
- 2008 c Easy way to use variables of enum types as string in C?
- 2008 c++ How to easily map c++ enums to strings
- 2008 c++ Making something both a C identifier and a string?
- 2008 c++ Is there a simple script to convert C++ enum to string?
- 2009 c++ How to use enums as flags in C++?
- 2011 c++ How to convert an enum type variable to a string?
- 2011 c++ Enum to String C++
- 2011 c++ How to convert an enum type variable to a string?
- 2012 c How to convert enum names to string in c
- 2013 c Stringifying an conditionally compiled enum in C
After reading many answers, I did not yet find any:
- Elegant way using C++11, C++14 or C++17 new features
- Or something ready-to-use in Boost
- Else something planned for C++20
Example
An example is often better than a long explanation.
You can compile and run this snippet on Coliru.
(Another former example is also available)
#include <map>
#include <iostream>
struct MyClass
{
enum class MyEnum : char {
AAA = -8,
BBB = '8',
CCC = AAA + BBB
};
};
// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
{ MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
{ MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
{ MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
};
auto it = MyEnumStrings.find(e);
return it == MyEnumStrings.end() ? "Out of range" : it->second;
}
int main()
{
std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}
Constraints
- Please no invaluable duplication of other answers or basic link.
- Please avoid bloat macro-based answer, or try to reduce the
#define
overhead as minimum as possible. - Please no manual
enum
->string
mapping.
Nice to have
- Support
enum
values starting from a number different from zero - Support negative
enum
values - Support fragmented
enum
values - Support
class enum
(C++11) - Support
class enum : <type>
having any allowed<type>
(C++11) - Compile-time (not run-time) conversions to a string,
or at least fast execution at run-time (e.g.std::map
is not a great idea...) constexpr
(C++11, relaxed in C++14)noexcept
(C++11)- snippet C++14/C++17 friendly
- C++ State of the art
One possible idea could be using the C++ compiler capabilities to generate C++ code at compilation-time using meta-programming tricks based on variadic template class
and constexpr
functions...
I had the same problem a couple of days ago. I couldn't find any C++ solution without some weird macro magic, so I decided to write a CMake code generator to generate simple switch case statements.
Usage:
The function searches the include files in the filesystem (uses the include directories provided with the include_directories command), reads them and does some regex to generate the class and the function(s).
NOTE: constexpr implies inline in C++, so using the USE_CONSTEXPR option will generate a header only class!
Example:
./includes/a.h:
./CMakeLists.txt:
Generates:
./enum2Str.hpp:
./enum2Str.cpp:
Update:
The script now also supports scoped enumerations (enum class|struct) and I moved it to a seperate repo with some other scripts I often use: https://github.com/mensinda/cmakeBuildTools
As long as you are okay with writing a separate
.h/.cpp
pair for each queryable enum, this solution works with nearly the same syntax and capabilities as a regular c++ enum:The
.cpp
file is 3 lines of boilerplate:Example usage:
Code
This solution requires 2 source files:
...and
Explanation
This implementation exploits the fact that the braced list of elements of an enum definition can also be used as a braced initializer list for class member initialization.
When
ETRAITS
is evaluated in the context ofEnumTraits.inl
, it expands out to a static member definition for theEnumTraits<>
class.The
EDECL
macro transforms each enum member into initializer list values which subsequently get passed into the member constructor in order to populate the enum info.The
EnumInitGuard
class is designed to consume the enum initializer values and then collapse - leaving a pure list of enum data.Benefits
c++
-like syntaxenum
andenum class
(*almost)enum
types with any numeric underlying typeenum
types with automatic, explicit, and fragmented initializer values*
In contrast toenums
, initializers inenum class
types that reference other values from the same enum must have those values fully qualifiedDisbenefits
.h/.cpp
pair for each queryableenum
macro
andinclude
magicclass
ornamespace
scoped enums is nontrivialComments
Intellisense will complain a bit about private member access when opening up
EnumTraits.inl
, but since the expanded macros are actually defining class members, that isn't actually a problem.The
#ifndef ENUM_INCLUDE_MULTI
block at the top of the header file is a minor annoyance that could probably be shrunken down into a macro or something, but it's small enough to live with at its current size.Declaring a namespace scoped enum requires that the enum first be forward declared inside its namespace scope, then defined in the global namespace. Additionally, any enum initializers using values of the same enum must have those values fully qualified.
Solutions using enum within class/struct (struct defaults with public members) and overloaded operators:
From the outside it looks nearly exactly like a class enum:
This will output "red 1 2". You could possibly overload << to make blue output a string (although it might cause ambiguity so not possible), but it wouldn't work with Color::GREEN since it doesn't automatically convert to Color.
The purpose of having an implicit convert to Enum (which implicitly converts to int or type given) is to be able to do:
This works, but it also means that this work too:
With an enum class it wouldn't compile. You ought to be careful if you overload two functions taking the enum and an integer, or remove the implicit conversion...
Another solution would involve using an actual enum class and static members:
It possibly takes more space, and is longer to make, but causes a compile error for implicit int conversions. I'd use this one because of that!
There's surely overhead with this though, but I think it's just simpler and looks better than other code I've seen. There's also potential for adding functionality, which could all be scoped within the class.
Edit: this works and most can be compiled before execution:
As per request from the OP, here a stripped down version of the ugly macro solution based on Boost Preprosessor and Variadic Macros.
It allows for a simple list like syntax of the enumerator elements along with setting values for specific elements so that
expands to
Alongside with the necessary functions to output and do some conversion back. This macro has been around here for ages, and I am not totally sure that its the most efficient way, or that it is a conforming way, but it has ever since been working
The complete code can be seen in action at both Ideone and Coliru.
Its gargantuan ugliness is above; I would have put it behind spoilers to protect your eyes, if I knew how, but markdown doesn't like me.
The library (merged within one single header file)
Usage
Compilation (copy paste header within
main.cpp
)Output
I have been frustrated by this problem for a long time too, along with the problem of getting a type converted to string in a proper way. However, for the last problem, I was surprised by the solution explained in Is it possible to print a variable's type in standard C++?, using the idea from Can I obtain C++ type names in a constexpr way?. Using this technique, an analogous function can be constructed for getting an enum value as string:
The code above has only been tested on Clang (see https://ideone.com/je5Quv) and VS2015, but should be adaptable to other compilers by fiddling a bit with the integer constants. Of course, it still uses macros under the hood, but at least one doesn't need access to the enum implementation.
I took the idea from @antron and implemented it differently: generating a true enum class.
This implementation meets all the requirements listed in original question but currently has only one real limitation: it assumes the enum values are either not provided or, if provided, must start with 0 and go up sequentially without gaps.
This is not an intrinsic limitation - simply that I don't use ad-hoc enum values. If this is needed, one can replace vector lookup with traditional switch/case implementation.
The solution uses some c++17 for inline variables but this can be easily avoided if needed. It also uses boost:trim because of simplicity.
Most importantly, it takes only 30 lines of code and no black magic macros. The code is below. It's meant to be put in header and included in multiple compilation modules.
It can be used the same way as was suggested earlier in this thread:
Pls let me know if this is useful and how it can be improved further.