与其他类似的问题不同,这个问题是关于如何使用c++的新特性。

2008 c Is there a simple way to convert C++ enum to string? 2008 c Easy way to use variables of enum types as string in C? 2008 c++ How to easily map c++ enums to strings 2008 c++ Making something both a C identifier and a string? 2008 c++ Is there a simple script to convert C++ enum to string? 2009 c++ How to use enums as flags in C++? 2011 c++ How to convert an enum type variable to a string? 2011 c++ Enum to String C++ 2011 c++ How to convert an enum type variable to a string? 2012 c How to convert enum names to string in c 2013 c Stringifying an conditionally compiled enum in C

看了很多答案后,我还没有找到:

优雅的方式使用c++ 11、c++ 14或c++ 17的新特性 或者在Boost中使用一些现成的东西 还有一些东西计划在c++ 20中实现

例子

举例往往比冗长的解释更好。 您可以在Coliru上编译和运行这个代码片段。 (另一个前面的例子也可用)

#include <map>
#include <iostream>

struct MyClass
{
    enum class MyEnum : char {
        AAA = -8,
        BBB = '8',
        CCC = AAA + BBB
    };
};

// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
    const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
        { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
        { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
        { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
    };
    auto   it  = MyEnumStrings.find(e);
    return it == MyEnumStrings.end() ? "Out of range" : it->second;
}

int main()
{
   std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
   std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
   std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}

约束

请不要无价值的重复其他答案或基本链接。 请避免基于宏的臃肿答案,或尽量减少#define开销。 请不要手动enum ->字符串映射。

很高兴有

支持从不同于零的数字开始的enum值 支持负enum值 支持碎片enum值 支持类枚举(c++ 11) 支持类枚举:<类型>有任何允许的<类型> (c++ 11) 编译时(不是运行时)到字符串的转换, 或者至少在运行时快速执行(例如std::map不是一个好主意…) constexpr (c++ 11,然后在c++ 14/17/20中放松) noexcept (C + + 11) c++ 17/ c++ 20友好的代码片段

一个可能的想法是使用c++编译器功能,在编译时使用基于可变参数模板类和constexpr函数的元编程技巧来生成c++代码……


当前回答

您可以使用select()函数,这实际上只是一个简短的开关;这不是真正意义上的解决方案,但它让生活更容易:

enum
{
  NORMAL,
  INVALID
} state(NORMAL);

//std::cout << (state ? "INVALID" : "NORMAL") << std::endl;
std::cout << select(state, "NORMAL", "INVALID") << std::endl;

select()函数在SIMD/GPU编程中很常见。它们是三元?:运算符的推广。还可以将select()视为函数数组(实现数组数据结构的函数)。

这里有一个完整的例子。

其他回答

我不太喜欢与此相关的所有花哨的框架(宏、模板和类),因为我认为使用它们会使代码更难理解,并且会增加编译时间并隐藏错误。总的来说,我想要一个简单的解决这个问题的方法。添加额外的100行代码并不简单。

最初问题中给出的示例与我实际在生产中使用的代码非常接近。相反,我只想对原来的示例查找函数提出一些小的改进:

const std::string& magic(MyClass::MyEnum e)
{
    static const std::string OUT_OF_RANGE = "Out of range";
    #define ENTRY(v) { MyClass::MyEnum::v, "MyClass::MyEnum::" #v }
    static const std::unordered_map<MyClass::MyEnum, std::string> LOOKUP {
        ENTRY(AAA),
        ENTRY(BBB),
        ENTRY(CCC),
    };
    #undef ENTRY
    auto it  = LOOKUP.find(e);
    return ((it != LOOKUP.end()) ? it->second : OUT_OF_RANGE);
}

具体地说:

Internal data structures are now 'static' and 'const'. These are unchanging, so there is no need to construct these on every call to the function, and to do so would be very inefficient. Instead, these are constructed on the first call to the function only. Return value is now 'const std::string&'. This function will only return references to already-allocated std::string objects with 'static' lifetime, so there is no need to copy them when returning. Map type is now 'std::unordered_map' for O(1) access instead of std::map's O(log(N)) access. Use of the ENTRY macro allows somewhat more concise code and also avoids potential problems from typos made while entering names in the string literals. (If the programmer enters an invalid name, a compiler error will result.)

我写了一个库来解决这个问题,所有的事情都发生在编译时,除了获取消息。

用法:

使用宏DEF_MSG定义宏和消息对:

DEF_MSG(CODE_OK,   "OK!")
DEF_MSG(CODE_FAIL, "Fail!")

CODE_OK是要使用的宏,“OK!”是相应的消息。

使用get_message()或gm()来获取消息:

get_message(CODE_FAIL);  // will return "Fail!"
gm(CODE_FAIL);           // works exactly the same as above

使用MSG_NUM查找已经定义了多少个宏。它会自动增加,你不需要做任何事情。

预定义的消息:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

项目:libcodemsg


标准库不会创建额外的数据。一切都发生在编译时。在message_def.h中,它生成一个名为MSG_CODE的enum;在message_def.c中,它生成一个变量,保存静态const char* _g_messages[]中的所有字符串。

在这种情况下,库只能创建一个枚举。这对于返回值非常理想,例如:

MSG_CODE foo(void) {
    return MSG_OK; // or something else
}

MSG_CODE ret = foo();

if (MSG_OK != ret) {
    printf("%s\n", gm(ret););
}

我喜欢这种设计的另一个原因是,您可以在不同的文件中管理消息定义。


我发现这个问题的解看起来好多了。

如果你的枚举看起来像

enum MyEnum
{
  AAA = -8,
  BBB = '8',
  CCC = AAA + BBB
};

你可以移动枚举的内容到一个新文件:

AAA = -8,
BBB = '8',
CCC = AAA + BBB

然后这些值可以被宏包围:

// default definition
#ifned ITEM(X,Y)
#define ITEM(X,Y)
#endif

// Items list
ITEM(AAA,-8)
ITEM(BBB,'8')
ITEM(CCC,AAA+BBB)

// clean up
#undef ITEM

下一步可能是重新包含枚举中的项:

enum MyEnum
{
  #define ITEM(X,Y) X=Y,
  #include "enum_definition_file"
};

最后你可以生成关于这个枚举的实用函数:

std::string ToString(MyEnum value)
{
  switch( value )
  {
    #define ITEM(X,Y) case X: return #X;
    #include "enum_definition_file"
  }

  return "";
}

MyEnum FromString(std::string const& value)
{
  static std::map<std::string,MyEnum> converter
  {
    #define ITEM(X,Y) { #X, X },
    #include "enum_definition_file"
  };

  auto it = converter.find(value);
  if( it != converter.end() )
    return it->second;
  else
    throw std::runtime_error("Value is missing");
}

该解决方案可以应用于旧的c++标准,它不使用现代的c++元素,但它可以用来生成大量代码,而不需要太多的工作和维护。

我不知道你是否会喜欢这个,我对这个解决方案不太满意,但它是一个c++ 14友好的方法,因为它使用模板变量和滥用模板专门化:

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";

int main()
{
    // Prints "AAA"
    std::cout << MyEnumName<MyEnum::AAA> << '\n';
    // Prints "Invalid MyEnum value"
    std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
    // Well... in fact it prints "Invalid MyEnum value" for any value
    // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.

    return 0;
}

这种方法最糟糕的地方是维护起来很痛苦,但维护其他一些类似的方法也很痛苦,不是吗?

这种方法的优点:

使用可变温度(c++ 14特性) 使用模板专门化,我们可以在使用无效值时“检测”(但我不确定这是否有用)。 看起来很整洁。 名称查找在编译时完成。

生活的例子

Edit

你是对的;c++ 14变量模板方法不处理运行时情况,这是我的错,忘记了它:(

但是我们仍然可以使用一些现代c++特性和变量模板加上变进模板技巧来实现从枚举值到字符串的运行时转换…它和其他一样麻烦,但仍然值得一提。

让我们开始使用模板别名来缩短对枚举到字符串映射的访问:

// enum_map contains pairs of enum value and value string for each enum
// this shortcut allows us to use enum_map<whatever>.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;

// This variable template will create a map for each enum type which is
// instantiated with.
template <typename ENUM>
enum_map<ENUM> enum_values{};

然后,变值模板诡计:

template <typename ENUM>
void initialize() {}

template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
    enum_values<ENUM>.emplace(value, name);
    initialize<ENUM>(tail ...);
}

这里的“最佳技巧”是为map使用变量template,其中包含每个枚举条目的值和名称;这个映射在每个翻译单元中都是相同的,并且到处都有相同的名称,所以非常简单明了,如果我们像这样调用initialize函数:

initialize
(
    MyEnum::AAA, "AAA",
    MyEnum::BBB, "BBB",
    MyEnum::CCC, "CCC"
);

我们正在为每个MyEnum条目签名,并且可以在运行时使用:

std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';

但是可以通过SFINAE和重载<<运算符进行改进:

template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
std::ostream &operator <<(std::ostream &o, const ENUM value)
{
    static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
    auto found = enum_values<ENUM>.find(value);

    return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
}

使用正确的操作符<<,现在我们可以这样使用枚举:

std::cout << MyEnum::AAA << '\n';

维护这一点也很麻烦,可以改进,但希望您能理解。

生活的例子

您可以使用select()函数,这实际上只是一个简短的开关;这不是真正意义上的解决方案,但它让生活更容易:

enum
{
  NORMAL,
  INVALID
} state(NORMAL);

//std::cout << (state ? "INVALID" : "NORMAL") << std::endl;
std::cout << select(state, "NORMAL", "INVALID") << std::endl;

select()函数在SIMD/GPU编程中很常见。它们是三元?:运算符的推广。还可以将select()视为函数数组(实现数组数据结构的函数)。

这里有一个完整的例子。