使用预处理器从 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.

的实现