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:
// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if
enum MyEnum : int ETRAITS
{
EDECL(AAA) = -8,
EDECL(BBB) = '8',
EDECL(CCC) = AAA + BBB
};
The .cpp
file is 3 lines of boilerplate:
// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>
Example usage:
for (MyEnum value : EnumTraits<MyEnum>::GetValues())
std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;
This solution requires 2 source files:
// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
#define ETRAITS
#define EDECL(x) x
template <class ENUM>
class EnumTraits
{
public:
static const std::vector<ENUM>& GetValues()
{
return values;
}
static ENUM GetValue(const char* name)
{
auto match = valueMap.find(name);
return (match == valueMap.end() ? ENUM() : match->second);
}
static const char* GetName(ENUM value)
{
auto match = nameMap.find(value);
return (match == nameMap.end() ? nullptr : match->second);
}
public:
EnumTraits() = delete;
using vector_type = std::vector<ENUM>;
using name_map_type = std::unordered_map<ENUM, const char*>;
using value_map_type = std::unordered_map<std::string, ENUM>;
private:
static const vector_type values;
static const name_map_type nameMap;
static const value_map_type valueMap;
};
struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }
...and
// EnumTraits.inl
#define ENUM_INCLUDE_MULTI
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;
#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
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 of EnumTraits.inl
,
it expands out to a static member definition for the EnumTraits<>
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.
c++
-like syntaxenum
and enum class
(*almost)enum
types with any numeric underlying typeenum
types with automatic, explicit, and fragmented initializer values*
In contrast to enums
, initializers in enum class
types that reference other values from the same enum must have those values fully qualified
.h/.cpp
pair for each queryable enum
macro
and include
magicclass
or namespace
scoped enums is nontrivialIntellisense 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.
namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
EDECL(AAA) = -8,
EDECL(BBB) = '8',
EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}
I wrote a library for solving this problem, everything happens in compiling time, except for getting the message.
Use macro DEF_MSG
to define a macro and message pair:
DEF_MSG(CODE_OK, "OK!")
DEF_MSG(CODE_FAIL, "Fail!")
CODE_OK
is the macro to use, and "OK!"
is the corresponding message.
Use get_message()
or just gm()
to get the message:
get_message(CODE_FAIL); // will return "Fail!"
gm(CODE_FAIL); // works exactly the same as above
Use MSG_NUM
to find out how many macros have been defined. This will automatically increse, you don't need to do anything.
Predefined messages:
MSG_OK: OK
MSG_BOTTOM: Message bottom
Project: libcodemsg
The library doesn't create extra data. Everything happens in compiling time. In message_def.h
, it generates an enum
called MSG_CODE
; in message_def.c
, it generates a variable holds all the strings in static const char* _g_messages[]
.
In such case, the library is limited to create one enum
only. This is ideal for return values, for example:
MSG_CODE foo(void) {
return MSG_OK; // or something else
}
MSG_CODE ret = foo();
if (MSG_OK != ret) {
printf("%s\n", gm(ret););
}
Another thing I like this design is you can manage message definitions in different files.
I found the solution to this question looks much better.
#define ENUM_MAKE(TYPE, ...) \
enum class TYPE {__VA_ARGS__};\
struct Helper_ ## TYPE { \
static const String& toName(TYPE type) {\
int index = static_cast<int>(type);\
return splitStringVec()[index];}\
static const TYPE toType(const String& name){\
static std::unordered_map<String,TYPE> typeNameMap;\
if( typeNameMap.empty() )\
{\
const StringVector& ssVec = splitStringVec();\
for (size_t i = 0; i < ssVec.size(); ++i)\
typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\
}\
return typeNameMap[name];}\
static const StringVector& splitStringVec() {\
static StringVector typeNameVector;\
if(typeNameVector.empty()) \
{\
typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\
for (auto& name : typeNameVector)\
{\
name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \
name = String(#TYPE) + "::" + name;\
}\
}\
return typeNameVector;\
}\
};
using String = std::string;
using StringVector = std::vector<String>;
StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
{
StringVector ret;
// Pre-allocate some space for performance
ret.reserve(maxSplits ? maxSplits+1 : 10); // 10 is guessed capacity for most case
unsigned int numSplits = 0;
// Use STL methods
size_t start, pos;
start = 0;
do
{
pos = str.find_first_of(delims, start);
if (pos == start)
{
// Do nothing
start = pos + 1;
}
else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
{
// Copy the rest of the string
ret.push_back( str.substr(start) );
break;
}
else
{
// Copy up to delimiter
ret.push_back( str.substr(start, pos - start) );
if(preserveDelims)
{
// Sometimes there could be more than one delimiter in a row.
// Loop until we don't find any more delims
size_t delimStart = pos, delimPos;
delimPos = str.find_first_not_of(delims, delimStart);
if (delimPos == String::npos)
{
// Copy the rest of the string
ret.push_back( str.substr(delimStart) );
}
else
{
ret.push_back( str.substr(delimStart, delimPos - delimStart) );
}
}
start = pos + 1;
}
// parse up to next real data
start = str.find_first_not_of(delims, start);
++numSplits;
} while (pos != String::npos);
return ret;
}
example
ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)
MY_TEST s1 = MY_TEST::MY_1;
MY_TEST s2 = MY_TEST::MY_2;
MY_TEST s3 = MY_TEST::MY_3;
String z1 = Helper_MY_TEST::toName(s1);
String z2 = Helper_MY_TEST::toName(s2);
String z3 = Helper_MY_TEST::toName(s3);
MY_TEST q1 = Helper_MY_TEST::toType(z1);
MY_TEST q2 = Helper_MY_TEST::toType(z2);
MY_TEST q3 = Helper_MY_TEST::toType(z3);
automatically ENUM_MAKE macro generate 'enum class' and helper class with 'enum reflection function'.
In order to reduce mistakes, at once Everything is defined with only one ENUM_MAKE.
The advantage of this code is automatically created for reflection and a close look at macro code ,easy-to-understand code. 'enum to string' , 'string to enum' performance both is algorithm O(1).
Disadvantages is when first use , helper class for enum relection 's string vector and map is initialized. but If you want you'll also be pre-initialized. –
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:
#include <iostream>
using namespace std;
class static_string
{
const char* const p_;
const std::size_t sz_;
public:
typedef const char* const_iterator;
template <std::size_t N>
constexpr static_string(const char(&a)[N]) noexcept
: p_(a)
, sz_(N - 1)
{}
constexpr static_string(const char* p, std::size_t N) noexcept
: p_(p)
, sz_(N)
{}
constexpr const char* data() const noexcept { return p_; }
constexpr std::size_t size() const noexcept { return sz_; }
constexpr const_iterator begin() const noexcept { return p_; }
constexpr const_iterator end() const noexcept { return p_ + sz_; }
constexpr char operator[](std::size_t n) const
{
return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
}
};
inline std::ostream& operator<<(std::ostream& os, static_string const& s)
{
return os.write(s.data(), s.size());
}
/// \brief Get the name of a type
template <class T>
static_string typeName()
{
#ifdef __clang__
static_string p = __PRETTY_FUNCTION__;
return static_string(p.data() + 30, p.size() - 30 - 1);
#elif defined(_MSC_VER)
static_string p = __FUNCSIG__;
return static_string(p.data() + 37, p.size() - 37 - 7);
#endif
}
namespace details
{
template <class Enum>
struct EnumWrapper
{
template < Enum enu >
static static_string name()
{
#ifdef __clang__
static_string p = __PRETTY_FUNCTION__;
static_string enumType = typeName<Enum>();
return static_string(p.data() + 73 + enumType.size(), p.size() - 73 - enumType.size() - 1);
#elif defined(_MSC_VER)
static_string p = __FUNCSIG__;
static_string enumType = typeName<Enum>();
return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7);
#endif
}
};
}
/// \brief Get the name of an enum value
template <typename Enum, Enum enu>
static_string enumName()
{
return details::EnumWrapper<Enum>::template name<enu>();
}
enum class Color
{
Blue = 0,
Yellow = 1
};
int main()
{
std::cout << "_" << typeName<Color>() << "_" << std::endl;
std::cout << "_" << enumName<Color, Color::Blue>() << "_" << std::endl;
return 0;
}
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.