如何轻松地将 C++ 枚举映射到字符串
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/207976/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to easily map c++ enums to strings
提问by Roddy
I have a bunch of enum types in some library header files that I'm using, and I want to have a way of converting enum values to user strings - and vice-versa.
我在使用的一些库头文件中有一堆枚举类型,我想有一种方法将枚举值转换为用户字符串 - 反之亦然。
RTTI won't do it for me, because the 'user strings' need to be a bit more readable than the enumerations.
RTTI 不会为我做这件事,因为“用户字符串”需要比枚举更具可读性。
A brute force solution would be a bunch of functions like this, but I feel that's a bit too C-like.
一个蛮力解决方案将是一堆这样的函数,但我觉得这有点太像 C 了。
enum MyEnum {VAL1, VAL2,VAL3};
String getStringFromEnum(MyEnum e)
{
switch e
{
case VAL1: return "Value 1";
case VAL2: return "Value 2";
case VAL1: return "Value 3";
default: throw Exception("Bad MyEnum");
}
}
I have a gut feeling that there's an elegant solution using templates, but I can't quite get my head round it yet.
我有一种直觉,即使用模板有一个优雅的解决方案,但我还不能完全理解它。
UPDATE:Thanks for suggestions - I should have made clear that the enums are defined in a third-party library header, so I don't want to have to change the definition of them.
更新:感谢您的建议 - 我应该明确指出枚举是在第三方库头文件中定义的,所以我不想更改它们的定义。
My gut feeling now is to avoid templates and do something like this:
我现在的直觉是避免使用模板并执行以下操作:
char * MyGetValue(int v, char *tmp); // implementation is trivial
#define ENUM_MAP(type, strings) char * getStringValue(const type &T) \
{ \
return MyGetValue((int)T, strings); \
}
; enum eee {AA,BB,CC}; - exists in library header file
; enum fff {DD,GG,HH};
ENUM_MAP(eee,"AA|BB|CC")
ENUM_MAP(fff,"DD|GG|HH")
// To use...
eee e;
fff f;
std::cout<< getStringValue(e);
std::cout<< getStringValue(f);
回答by MSalters
If you want the enum names themselves as strings, see this post.
Otherwise, a std::map<MyEnum, char const*>
will work nicely. (No point in copying your string literals to std::strings in the map)
如果您希望枚举名称本身为字符串,请参阅此帖子。否则, astd::map<MyEnum, char const*>
会很好地工作。(将字符串文字复制到地图中的 std::strings 没有意义)
For extra syntactic sugar, here's how to write a map_init class. The goal is to allow
对于额外的语法糖,这里是如何编写 map_init 类。目标是让
std::map<MyEnum, const char*> MyMap;
map_init(MyMap)
(eValue1, "A")
(eValue2, "B")
(eValue3, "C")
;
The function template <typename T> map_init(T&)
returns a map_init_helper<T>
.
map_init_helper<T>
stores a T&, and defines the trivial map_init_helper& operator()(typename T::key_type const&, typename T::value_type const&)
. (Returning *this
from operator()
allows the chaining of operator()
, like operator<<
on std::ostream
s)
该函数template <typename T> map_init(T&)
返回一个map_init_helper<T>
.
map_init_helper<T>
存储一个 T&,并定义了平凡的map_init_helper& operator()(typename T::key_type const&, typename T::value_type const&)
. (*this
从返回operator()
允许链接operator()
,就像operator<<
在std::ostream
s 上一样)
template<typename T> struct map_init_helper
{
T& data;
map_init_helper(T& d) : data(d) {}
map_init_helper& operator() (typename T::key_type const& key, typename T::mapped_type const& value)
{
data[key] = value;
return *this;
}
};
template<typename T> map_init_helper<T> map_init(T& item)
{
return map_init_helper<T>(item);
}
Since the function and helper class are templated, you can use them for any map, or map-like structure. I.e. it can also add entries to std::unordered_map
由于函数和助手类是模板化的,您可以将它们用于任何地图或类似地图的结构。即它也可以添加条目std::unordered_map
If you don't like writing these helpers, boost::assign offers the same functionality out of the box.
如果您不喜欢编写这些帮助程序,boost::assign 提供了开箱即用的相同功能。
回答by Alastair
MSalters solution is a good one but basically re-implements boost::assign::map_list_of
. If you have boost, you can use it directly:
MSalters 解决方案是一个很好的解决方案,但基本上重新实现了boost::assign::map_list_of
. 如果有boost,可以直接使用:
#include <boost/assign/list_of.hpp>
#include <boost/unordered_map.hpp>
#include <iostream>
using boost::assign::map_list_of;
enum eee { AA,BB,CC };
const boost::unordered_map<eee,const char*> eeeToString = map_list_of
(AA, "AA")
(BB, "BB")
(CC, "CC");
int main()
{
std::cout << " enum AA = " << eeeToString.at(AA) << std::endl;
return 0;
}
回答by jfs
Auto-generate one form from another.
从另一种形式自动生成一种形式。
Source:
来源:
enum {
VALUE1, /* value 1 */
VALUE2, /* value 2 */
};
Generated:
生成:
const char* enum2str[] = {
"value 1", /* VALUE1 */
"value 2", /* VALUE2 */
};
If enum values are large then a generated form could use unordered_map<> or templates as suggested by Constantin.
如果枚举值很大,则生成的表单可以使用康斯坦丁建议的 unordered_map<> 或模板。
Source:
来源:
enum State{
state0 = 0, /* state 0 */
state1 = 1, /* state 1 */
state2 = 2, /* state 2 */
state3 = 4, /* state 3 */
state16 = 0x10000, /* state 16 */
};
Generated:
生成:
template <State n> struct enum2str { static const char * const value; };
template <State n> const char * const enum2str<n>::value = "error";
template <> struct enum2str<state0> { static const char * const value; };
const char * const enum2str<state0>::value = "state 0";
Example:
例子:
#include <iostream>
int main()
{
std::cout << enum2str<state16>::value << std::endl;
return 0;
}
回答by Debdatta Basu
I remember having answered this elsewhere on StackOverflow. Repeating it here. Basically it's a solution based on variadic macros, and is pretty easy to use:
我记得在 StackOverflow 上的其他地方回答过这个问题。在这里重复一遍。基本上它是基于可变参数宏的解决方案,并且非常易于使用:
#define AWESOME_MAKE_ENUM(name, ...) enum class name { __VA_ARGS__, __COUNT}; \
inline std::ostream& operator<<(std::ostream& os, name value) { \
std::string enumName = #name; \
std::string str = #__VA_ARGS__; \
int len = str.length(); \
std::vector<std::string> strings; \
std::ostringstream temp; \
for(int i = 0; i < len; i ++) { \
if(isspace(str[i])) continue; \
else if(str[i] == ',') { \
strings.push_back(temp.str()); \
temp.str(std::string());\
} \
else temp<< str[i]; \
} \
strings.push_back(temp.str()); \
os << enumName << "::" << strings[static_cast<int>(value)]; \
return os;}
To use it in your code, simply do:
要在您的代码中使用它,只需执行以下操作:
AWESOME_MAKE_ENUM(Animal,
DOG,
CAT,
HORSE
);
auto dog = Animal::DOG;
std::cout<<dog;
回答by David Allan Finch
I suggest a mix of using X-macros are the best solutionand the following template functions:
我建议混合使用X-macros 是最好的解决方案和以下模板函数:
To borrow off marcinkoziukmyopenidcomand extended
enum Colours {
# define X(a) a,
# include "colours.def"
# undef X
ColoursCount
};
char const* const colours_str[] = {
# define X(a) #a,
# include "colours.def"
# undef X
0
};
template <class T> T str2enum( const char* );
template <class T> const char* enum2str( T );
#define STR2ENUM(TYPE,ARRAY) \
template <> \
TYPE str2enum<TYPE>( const char* str ) \
{ \
for( int i = 0; i < (sizeof(ARRAY)/sizeof(ARRAY[0])); i++ ) \
if( !strcmp( ARRAY[i], str ) ) \
return TYPE(i); \
return TYPE(0); \
}
#define ENUM2STR(TYPE,ARRAY) \
template <> \
const char* enum2str<TYPE>( TYPE v ) \
{ \
return ARRAY[v]; \
}
#define ENUMANDSTR(TYPE,ARRAY)\
STR2ENUM(TYPE,ARRAY) \
ENUM2STR(TYPE,ARRAY)
ENUMANDSTR(Colours,colours_str)
colour.def
颜色文件
X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)
回答by Juan Gonzalez Burgos
I use thissolution which I reproduce below:
我使用我在下面重现的这个解决方案:
#define MACROSTR(k) #k
#define X_NUMBERS \
X(kZero ) \
X(kOne ) \
X(kTwo ) \
X(kThree ) \
X(kFour ) \
X(kMax )
enum {
#define X(Enum) Enum,
X_NUMBERS
#undef X
} kConst;
static char *kConstStr[] = {
#define X(String) MACROSTR(String),
X_NUMBERS
#undef X
};
int main(void)
{
int k;
printf("Hello World!\n\n");
for (k = 0; k < kMax; k++)
{
printf("%s\n", kConstStr[k]);
}
return 0;
}
回答by Constantin
If you want to get string representations of MyEnum
variables, then templates won't cut it. Template can be specialized on integral values known at compile-time.
如果你想获得MyEnum
variables 的字符串表示,那么模板不会削减它。模板可以专门用于编译时已知的整数值。
However, if that's what you want then try:
但是,如果这是您想要的,请尝试:
#include <iostream>
enum MyEnum { VAL1, VAL2 };
template<MyEnum n> struct StrMyEnum {
static char const* name() { return "Unknown"; }
};
#define STRENUM(val, str) \
template<> struct StrMyEnum<val> { \
static char const* name() { return str; }};
STRENUM(VAL1, "Value 1");
STRENUM(VAL2, "Value 2");
int main() {
std::cout << StrMyEnum<VAL2>::name();
}
This is verbose, but will catch errors like the one you made in question - your case VAL1
is duplicated.
这是冗长的,但会捕获像您所提出的错误一样的错误 - 您case VAL1
被复制了。
回答by jose.angel.jimenez
I have spent more time researching this topic that I'd like to admit. Luckily there are great open source solutions in the wild.
我花了更多时间研究这个我想承认的话题。幸运的是,有很多很棒的开源解决方案。
These are two great approaches, even if not well known enough (yet),
这是两种很好的方法,即使还不够广为人知(还),
- Standalone smart enum library for C++11/14/17. It supports all of the standard functionality that you would expect from a smart enum class in C++.
- Limitations: requires at least C++11.
- C++11/14/17 的独立智能枚举库。它支持您期望从 C++ 中的智能枚举类获得的所有标准功能。
- 限制:至少需要 C++11。
- Reflective compile-time enum library with clean syntax, in a single header file, and without dependencies.
- Limitations: based on macros, can't be used inside a class.
- 反射编译时枚举库,语法清晰,在单个头文件中,并且没有依赖项。
- 限制:基于宏,不能在类中使用。
回答by Valentin Heinitz
I've required this functionality several times for debugging/analyzing code from others.
For this, I've written a Perl script which generates a class with several overloaded toString
methods. Each toString
method takes an Enum
as an argument and returns const char*
.
我多次需要此功能来调试/分析其他人的代码。为此,我编写了一个 Perl 脚本,它生成一个具有多个重载toString
方法的类。每个toString
方法都将 anEnum
作为参数并返回const char*
。
Of course, the script doesn't parse C++ for enums itself, but uses ctags for generating symbol table.
当然,该脚本本身不会为枚举解析 C++,而是使用 ctags 来生成符号表。
The Perl script is here: http://heinitz-it.de/download/enum2string/enum2string.pl.html
Perl 脚本在这里:http: //heinitz-it.de/download/enum2string/enum2string.pl.html
回答by muqker
Your answers inspired me to write some macros myself. My requirements were the following:
你的回答激励我自己写一些宏。我的要求如下:
only write each value of the enum once, so there are no double lists to maintain
don't keep the enum values in a separate file that is later #included, so I can write it wherever I want
don't replace the enum itself, I still want to have the enum type defined, but in addition to it I want to be able to map every enum name to the corresponding string (to not affect legacy code)
the searching should be fast, so preferably no switch-case, for those huge enums
只写枚举的每个值一次,所以没有双重列表要维护
不要将枚举值保存在以后 #included 的单独文件中,这样我就可以将它写在任何我想要的地方
不要替换枚举本身,我仍然希望定义枚举类型,但除此之外,我希望能够将每个枚举名称映射到相应的字符串(以不影响遗留代码)
对于那些巨大的枚举,搜索应该很快,所以最好不要切换案例
This code creates a classic enum with some values. In addition it creates as std::map which maps each enum value to it's name (i.e. map[E_SUNDAY] = "E_SUNDAY", etc.)
这段代码创建了一个带有一些值的经典枚举。此外,它创建为 std::map 将每个枚举值映射到它的名称(即 map[E_SUNDAY] = "E_SUNDAY" 等)
Ok, here is the code now:
好的,这是现在的代码:
EnumUtilsImpl.h:
EnumUtilsImpl.h:
map<int, string> & operator , (map<int, string> & dest,
const pair<int, string> & keyValue) {
dest[keyValue.first] = keyValue.second;
return dest;
}
#define ADD_TO_MAP(name, value) pair<int, string>(name, #name)
EnumUtils.h// this is the file you want to include whenever you need to do this stuff, you will use the macros from it:
EnumUtils.h// 这是您在需要执行此操作时要包含的文件,您将使用其中的宏:
#include "EnumUtilsImpl.h"
#define ADD_TO_ENUM(name, value) \
name value
#define MAKE_ENUM_MAP_GLOBAL(values, mapName) \
int __makeMap##mapName() {mapName, values(ADD_TO_MAP); return 0;} \
int __makeMapTmp##mapName = __makeMap##mapName();
#define MAKE_ENUM_MAP(values, mapName) \
mapName, values(ADD_TO_MAP);
MyProjectCodeFile.h// this is an example of how to use it to create a custom enum:
MyProjectCodeFile.h// 这是如何使用它来创建自定义枚举的示例:
#include "EnumUtils.h*
#define MyEnumValues(ADD) \
ADD(val1, ), \
ADD(val2, ), \
ADD(val3, = 100), \
ADD(val4, )
enum MyEnum {
MyEnumValues(ADD_TO_ENUM)
};
map<int, string> MyEnumStrings;
// this is how you initialize it outside any function
MAKE_ENUM_MAP_GLOBAL(MyEnumValues, MyEnumStrings);
void MyInitializationMethod()
{
// or you can initialize it inside one of your functions/methods
MAKE_ENUM_MAP(MyEnumValues, MyEnumStrings);
}
Cheers.
干杯。