使用预处理器从 C++ 中的字符串获取枚举
Get enum from string in C++ using preprocessor
有很多解决方案如何使用 C++ 处理器(例如 this topic)将枚举的文本表示形式获取为字符串。
但是,我想以相反的方向进行转换,以模拟字符串的开关。
例如 - 给定以下枚举:
typedef enum { RIGHT, LEFT, FORWARD, BACKWARD} direction;
对于小型枚举,可以定义字符串数组并使用枚举值来获取适当的字符串,但这不利于维护。
我想要一些可以定义函数 "direction FromString(char * str)" 的宏,如果我有一个字符串 "RIGHT".
,那么 return RIGHT
其他编程语言有很多解决方案,例如 C# or Java,所以我认为这不是一个糟糕的做法。
有一个 similar question 可以挖,但没有使用预处理器。
我目前的解决方案(基于one of the answers to another question)是这样的:
#include <boost/preprocessor.hpp>
#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM(r, data, elem) \
if (!strcmp(s, BOOST_PP_STRINGIZE( elem ))) return elem;
#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \
enum name { \
BOOST_PP_SEQ_ENUM(enumerators) \
}; \
inline const name FromString(char * s) { \
BOOST_PP_SEQ_FOR_EACH( \
X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM, \
name, \
enumerators \
) \
return ERR; \
}
可以通过以下方式使用它:
DEFINE_ENUM_WITH_STRING_CONVERSIONS(direction, (RIGHT)(LEFT)(...)(ERR))
缺点是必须包含 ERR 来处理字符串与任何枚举名称都不匹配的情况。如何避免这种情况?
我在想,也许更优雅的解决方案是使用
Boost Bimap.
您可以创建一张地图如下:
#define RIGHT_DIR "RIGHT"
#define LEFT_DIR "LEFT"
_directionActionMap[RIGHT_DIR] = RIGHT;
_directionActionMap[LEFT_DIR] = LEFT;
...
然后当你想从字符串中检索枚举时
int GetDirectionENUMFromString(const string& str)
{
int directionEnum;
std::map<string,int>::iterator iter;
iter = _directionActionMap.find(str);
if (iter != _directionActionMap.end())
{
directionEnum = (*iter).second;
}
else
{
directionEnum = -1;
}
return directionEnum;
}
用法:
int directionEnum = GetDirectionENUMFromString("RIGHT");
switch( directionEnum )
{
case RIGHT:
break;
case LEFT:
break;
...
}
我通常使用数组来存储字符串(需要 C++11)
enum colors : int
{
RED,
GREEN,
BLUE,
MAX // MAX must be always at the end
};
static const char* const colorsString[] =
{
"RED",
"GREEN",
"BLUE",
};
// Ensure your two "lists" size are matching
static_assert(MAX == sizeof(colorsString) / sizeof(colorsString[0]), "Colors must have the same number of elements.");
如果您使用 X-macros 定义您的枚举,您可以轻松地生成一个实现开关的函数:
#include <string>
#define MOUSE_BUTTONS \
X(LeftButton, 1) \
X(MiddleButton, 2) \
X(RightButton, 4)
enum MouseButton {
None = 0
#define X(name, value) ,name = value
MOUSE_BUTTONS
#undef X
};
static MouseButton stringAsMouseButton( const std::string &s )
{
#define X(name, value) if ( s == #name ) return name;
MOUSE_BUTTONS
#undef X
return None;
}
你也许可以
#define stringify(name) # name
direction FromString(char * s) {
if (!strcmp(s, stringify(RIGHT)) return RIGHT;
if (!strcmp(s, stringify(LEFT)) return LEFT;
if (!strcmp(s, stringify(FORWARD)) return FORWARD;
if (!strcmp(s, stringify(BACKWARD)) return BACKWARD;
return -1;
}
你可以试试这个库(免责声明——我是作者):https://github.com/aantron/better-enums
相关方法为_from_string (link is to docs). That method raises an exception in case of error. There is another method that returns an option,类似boost::optional
.
如果您想实现自己的版本,Stack Overflow 上的 中有对方法的描述。该答案中的代码包括 _from_string
.
的实现
有很多解决方案如何使用 C++ 处理器(例如 this topic)将枚举的文本表示形式获取为字符串。
但是,我想以相反的方向进行转换,以模拟字符串的开关。
例如 - 给定以下枚举:
typedef enum { RIGHT, LEFT, FORWARD, BACKWARD} direction;
对于小型枚举,可以定义字符串数组并使用枚举值来获取适当的字符串,但这不利于维护。
我想要一些可以定义函数 "direction FromString(char * str)" 的宏,如果我有一个字符串 "RIGHT".
,那么 return RIGHT其他编程语言有很多解决方案,例如 C# or Java,所以我认为这不是一个糟糕的做法。
有一个 similar question 可以挖,但没有使用预处理器。
我目前的解决方案(基于one of the answers to another question)是这样的:
#include <boost/preprocessor.hpp>
#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM(r, data, elem) \
if (!strcmp(s, BOOST_PP_STRINGIZE( elem ))) return elem;
#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \
enum name { \
BOOST_PP_SEQ_ENUM(enumerators) \
}; \
inline const name FromString(char * s) { \
BOOST_PP_SEQ_FOR_EACH( \
X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM, \
name, \
enumerators \
) \
return ERR; \
}
可以通过以下方式使用它:
DEFINE_ENUM_WITH_STRING_CONVERSIONS(direction, (RIGHT)(LEFT)(...)(ERR))
缺点是必须包含 ERR 来处理字符串与任何枚举名称都不匹配的情况。如何避免这种情况?
我在想,也许更优雅的解决方案是使用 Boost Bimap.
您可以创建一张地图如下:
#define RIGHT_DIR "RIGHT"
#define LEFT_DIR "LEFT"
_directionActionMap[RIGHT_DIR] = RIGHT;
_directionActionMap[LEFT_DIR] = LEFT;
...
然后当你想从字符串中检索枚举时
int GetDirectionENUMFromString(const string& str)
{
int directionEnum;
std::map<string,int>::iterator iter;
iter = _directionActionMap.find(str);
if (iter != _directionActionMap.end())
{
directionEnum = (*iter).second;
}
else
{
directionEnum = -1;
}
return directionEnum;
}
用法:
int directionEnum = GetDirectionENUMFromString("RIGHT");
switch( directionEnum )
{
case RIGHT:
break;
case LEFT:
break;
...
}
我通常使用数组来存储字符串(需要 C++11)
enum colors : int
{
RED,
GREEN,
BLUE,
MAX // MAX must be always at the end
};
static const char* const colorsString[] =
{
"RED",
"GREEN",
"BLUE",
};
// Ensure your two "lists" size are matching
static_assert(MAX == sizeof(colorsString) / sizeof(colorsString[0]), "Colors must have the same number of elements.");
如果您使用 X-macros 定义您的枚举,您可以轻松地生成一个实现开关的函数:
#include <string>
#define MOUSE_BUTTONS \
X(LeftButton, 1) \
X(MiddleButton, 2) \
X(RightButton, 4)
enum MouseButton {
None = 0
#define X(name, value) ,name = value
MOUSE_BUTTONS
#undef X
};
static MouseButton stringAsMouseButton( const std::string &s )
{
#define X(name, value) if ( s == #name ) return name;
MOUSE_BUTTONS
#undef X
return None;
}
你也许可以
#define stringify(name) # name
direction FromString(char * s) {
if (!strcmp(s, stringify(RIGHT)) return RIGHT;
if (!strcmp(s, stringify(LEFT)) return LEFT;
if (!strcmp(s, stringify(FORWARD)) return FORWARD;
if (!strcmp(s, stringify(BACKWARD)) return BACKWARD;
return -1;
}
你可以试试这个库(免责声明——我是作者):https://github.com/aantron/better-enums
相关方法为_from_string (link is to docs). That method raises an exception in case of error. There is another method that returns an option,类似boost::optional
.
如果您想实现自己的版本,Stack Overflow 上的 _from_string
.