有没有一种简单的方法将C++枚举转换为字符串?

139

假设我们有一些命名的枚举:

enum MyEnum {
      FOO,
      BAR = 0x50
};

我所需要的是一个脚本(任何语言),可以扫描我的项目中的所有标题,并生成一个包含每个枚举值的函数的标题。
char* enum_to_string(MyEnum t);

以下是类似于此的实现:

char* enum_to_string(MyEnum t){
      switch(t){
         case FOO:
            return "FOO";
         case BAR:
            return "BAR";
         default:
            return "INVALID ENUM";
      }
 }

问题出在typedef枚举和未命名的C风格枚举上。有没有人对此有解决方案?
编辑:解决方案不应该修改我的源代码,除了生成的函数。这些枚举在API中,因此目前提出的解决方案都不可行。

关于基于宏的工厂的答案已经移动到https://dev59.com/-XVC5IYBdhLWcg3w51ry - 在问题更新后,它在这里不再相关。 - Suma
35个回答

2
这是一款未发布的软件,但似乎 Frank Laub 的 BOOST_ENUM 可以胜任。我喜欢它的部分原因是您可以在类的范围内定义枚举类型,而大多数基于宏的枚举类型通常不允许这样做。您可以在 Boost Vault 中找到该软件: http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=& 该软件自2006年以来没有得到任何开发,因此我不知道它是否能够与新的 Boost 版本编译兼容。在 libs/test 下查看使用示例。

2
我使用单独的并排枚举包装类来实现此功能,这些类是通过宏生成的。有几个优点:
  • 可以为我未定义的枚举(例如:操作系统平台头文件枚举)生成它们
  • 可以将范围检查合并到包装类中
  • 可以对位字段枚举进行“更智能”的格式化
当然,缺点是我需要在格式化程序类中复制枚举值,并且我没有任何脚本来生成它们。除此之外,它似乎工作得很好。
以下是我的代码库中一个枚举的示例,不包含实现宏和模板的所有框架代码,但您可以了解其思路:
enum EHelpLocation
{
    HELP_LOCATION_UNKNOWN   = 0, 
    HELP_LOCAL_FILE         = 1, 
    HELP_HTML_ONLINE        = 2, 
};
class CEnumFormatter_EHelpLocation : public CEnumDefaultFormatter< EHelpLocation >
{
public:
    static inline CString FormatEnum( EHelpLocation eValue )
    {
        switch ( eValue )
        {
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCATION_UNKNOWN );
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCAL_FILE );
            ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_HTML_ONLINE );
        default:
            return FormatAsNumber( eValue );
        }
    }
};
DECLARE_RANGE_CHECK_CLASS( EHelpLocation, CRangeInfoSequential< HELP_HTML_ONLINE > );
typedef ESmartEnum< EHelpLocation, HELP_LOCATION_UNKNOWN, CEnumFormatter_EHelpLocation, CRangeInfo_EHelpLocation > SEHelpLocation;

那么,您可以使用SEHelpLocation来代替EHelpLocation,两者的工作方式相同,但是使用SEHelpLocation可以获得范围检查和在枚举变量本身上使用“Format()”方法。如果您需要格式化一个独立的值,可以使用CEnumFormatter_EHelpLocation::FormatEnum(...)。
希望这对您有所帮助。我意识到这也没有解决关于实际生成其他类的脚本的原始问题,但我希望这个结构能够帮助那些试图解决相同问题或编写这样的脚本的人。

2
这是我使用BOOST的解决方案:
#include <boost/preprocessor.hpp>

#define X_STR_ENUM_TOSTRING_CASE(r, data, elem)                                 \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define X_ENUM_STR_TOENUM_IF(r, data, elem)                                     \
    else if(data == BOOST_PP_STRINGIZE(elem)) return elem;

#define STR_ENUM(name, enumerators)                                             \
    enum name {                                                                 \
        BOOST_PP_SEQ_ENUM(enumerators)                                          \
    };                                                                          \
                                                                                \
    inline const QString enumToStr(name v)                                      \
    {                                                                           \
        switch (v)                                                              \
        {                                                                       \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                X_STR_ENUM_TOSTRING_CASE,                                       \
                name,                                                           \
                enumerators                                                     \
            )                                                                   \
                                                                                \
            default:                                                            \
                return "[Unknown " BOOST_PP_STRINGIZE(name) "]";                \
        }                                                                       \
    }                                                                           \
                                                                                \
    template <typename T>                                                       \
    inline const T strToEnum(QString v);                                        \
                                                                                \
    template <>                                                                 \
    inline const name strToEnum(QString v)                                      \
    {                                                                           \
        if(v=="")                                                               \
            throw std::runtime_error("Empty enum value");                       \
                                                                                \
        BOOST_PP_SEQ_FOR_EACH(                                                  \
            X_ENUM_STR_TOENUM_IF,                                               \
            v,                                                                  \
            enumerators                                                         \
        )                                                                       \
                                                                                \
        else                                                                    \
            throw std::runtime_error(                                           \
                        QString("[Unknown value %1 for enum %2]")               \
                            .arg(v)                                             \
                            .arg(BOOST_PP_STRINGIZE(name))                      \
                                .toStdString().c_str());                        \
    }

创建枚举类型,需要声明如下内容:
STR_ENUM
(
    SERVICE_RELOAD,
        (reload_log)
        (reload_settings)
        (reload_qxml_server)
)

用于转换:

SERVICE_RELOAD serviceReloadEnum = strToEnum<SERVICE_RELOAD>("reload_log");
QString serviceReloadStr = enumToStr(reload_log);

1

答案为0的问题在于枚举二进制值不一定从0开始,也不一定连续。

当我需要这个时,通常会:

  • 将枚举定义拉入我的源代码
  • 编辑它以获取仅名称
  • 使用宏将名称更改为问题中的case子句,尽管通常只有一行:case foo: return "foo";
  • 添加switch、default和其他语法使其合法

1
以下的 Ruby 脚本尝试解析标头并构建所需的源代码,以及原始的标头。
#! /usr/bin/env ruby

# Let's "parse" the headers
# Note that using a regular expression is rather fragile
# and may break on some inputs

GLOBS = [
  "toto/*.h",
  "tutu/*.h",
  "tutu/*.hxx"
]

enums = {}
GLOBS.each { |glob|
  Dir[glob].each { |header|
    enums[header] = File.open(header, 'rb') { |f|
      f.read
    }.scan(/enum\s+(\w+)\s+\{\s*([^}]+?)\s*\}/m).collect { |enum_name, enum_key_and_values|
      [
        enum_name, enum_key_and_values.split(/\s*,\s*/).collect { |enum_key_and_value|
          enum_key_and_value.split(/\s*=\s*/).first
        }
      ]
    }
  }
}


# Now we build a .h and .cpp alongside the parsed headers
# using the template engine provided with ruby
require 'erb'

template_h = ERB.new <<-EOS
#ifndef <%= enum_name %>_to_string_h_
#define <%= enum_name %>_to_string_h_ 1

#include "<%= header %>"
char* enum_to_string(<%= enum_name %> e);

#endif
EOS

template_cpp = ERB.new <<-EOS
#include "<%= enum_name %>_to_string.h"

char* enum_to_string(<%= enum_name %> e)
{
  switch (e)
  {<% enum_keys.each do |enum_key| %>
    case <%= enum_key %>: return "<%= enum_key %>";<% end %>
    default: return "INVALID <%= enum_name %> VALUE";
  }
}
EOS

enums.each { |header, enum_name_and_keys|
  enum_name_and_keys.each { |enum_name, enum_keys|
    File.open("#{File.dirname(header)}/#{enum_name}_to_string.h", 'wb') { |built_h|
      built_h.write(template_h.result(binding))
    }

    File.open("#{File.dirname(header)}/#{enum_name}_to_string.cpp", 'wb') { |built_cpp|
      built_cpp.write(template_cpp.result(binding))
    }
  }
}

使用正则表达式使得这个“解析器”非常脆弱,它可能无法优雅地处理您特定的标头。

假设您有一个名为toto/a.h的标头,其中包含枚举MyEnum和MyEnum2的定义。脚本将构建:

toto/MyEnum_to_string.h
toto/MyEnum_to_string.cpp
toto/MyEnum2_to_string.h
toto/MyEnum2_to_string.cpp

更加健壮的解决方案可能包括:

  • 从另一个源构建定义枚举及其操作的所有源。这意味着您将在 XML/YML/任何易于解析的文件中定义枚举,而不是在 C/C++ 中。
  • 使用像 Avdi 建议的真正编译器。
  • 使用预处理器宏,带或不带模板。

0

请将此代码库放置在某个仓库中(github、google code或bitbucket),然后在这里发布链接,而不是使用mediafire。这会帮助那些想要了解它的人 :) - Edu Felipe

0

这个问题是一个重复的问题,

然而,在所有的问题中,我都找不到好的答案。

在深入研究这个主题后,我发现了两个很棒的开源解决方案:

wise_enum

  • C++11/14/17独立智能枚举库。它支持你在C++中期望从智能枚举类中获得的所有标准功能。
  • 限制:需要至少C++11。

Better Enums

  • 反射式编译时枚举库,具有清晰的语法,在单个头文件中,没有依赖关系。
  • 限制:基于宏,不能在类内使用。
注意:我在这里重复推荐。这个问题有很多流量/浏览量,真的需要将解决方案列在上面。

0

这基本上是唯一的方法(字符串数组也可以)。

问题在于,一旦编译C程序,枚举的二进制值就是所使用的全部内容,名称已经消失了。


0

使用复合三元语句可以在枚举元素较少时(一行代码)显得相当优雅。表达式的长度随元素数量的增加仅呈近似线性增长。

以下是一个很好的使用案例:

enum log_level {INFO, WARNING, ERROR};
...
void logger::write(const std::string log, const log_level l) {
    ...
    std::string s = (l == INFO) ? "INFO" : 
                    (l == WARNING) ? "WARNING" : 
                    (l == ERROR) ? "ERROR" : "UNKNOWN";
    ...
}
...

当然,这只是另一个开关/ if语句块,但它是一条单行语句。就简洁性和简单性而言,它在中间达到了平衡点。作为常量表达式,它也可以很容易地转换成内联函数。

0
作为替代方案,使用简单的库 > http://codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C 在代码中
#include <EnumString.h>

enum FORM {
    F_NONE = 0,
    F_BOX,
    F_CUBE,
    F_SPHERE,
};

添加行

Begin_Enum_String( FORM )
{
    Enum_String( F_NONE );
    Enum_String( F_BOX );
    Enum_String( F_CUBE );
    Enum_String( F_SPHERE );
}
End_Enum_String;

如果枚举中的值不重复,则可以正常工作。

使用示例:

enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );

相反亦然

assert( EnumString< FORM >::To( f, str ) );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接