与其他类似的问题不同,这个问题是关于如何使用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++代码……


当前回答

只要你愿意为每个可查询枚举编写单独的.h/.cpp对,这个解决方案的语法和功能与常规的c++枚举几乎相同:

// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if

enum MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = AAA + BBB
};

.cpp文件是3行样板文件:

// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>

使用示例:

for (MyEnum value : EnumTraits<MyEnum>::GetValues())
    std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;

Code

该解决方案需要2个源文件:

// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>

#define ETRAITS
#define EDECL(x) x

template <class ENUM>
class EnumTraits
{
public:
    static const std::vector<ENUM>& GetValues()
    {
        return values;
    }

    static ENUM GetValue(const char* name)
    {
        auto match = valueMap.find(name);
        return (match == valueMap.end() ? ENUM() : match->second);
    }

    static const char* GetName(ENUM value)
    {
        auto match = nameMap.find(value);
        return (match == nameMap.end() ? nullptr : match->second);
    }

public:
    EnumTraits() = delete;

    using vector_type = std::vector<ENUM>;
    using name_map_type = std::unordered_map<ENUM, const char*>;
    using value_map_type = std::unordered_map<std::string, ENUM>;

private:
    static const vector_type values;
    static const name_map_type nameMap;
    static const value_map_type valueMap;
};

struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;

#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

解释

此实现利用了这样一个事实,即枚举定义的带括号元素列表也可以用作类成员初始化的带括号初始化列表。

当ETRAITS在enumtrait .inl的上下文中计算时, 它展开为EnumTraits<>类的静态成员定义。

EDECL宏将每个枚举成员转换为初始化列表值,这些值随后被传递到成员构造函数中,以填充枚举信息。

EnumInitGuard类被设计为使用枚举初始化式值,然后折叠——留下一个纯枚举数据列表。

好处

c++式的语法 对枚举和枚举类的工作相同(*几乎) 适用于具有任何数字基础类型的enum类型 适用于具有自动、显式和分段初始化值的enum类型 大规模重命名工作(智能感知链接保留) 只有5个预处理器符号(3个全局的)

*与枚举相反,枚举类类型中引用同一枚举中的其他值的初始化式必须完全限定这些值

不利

每个可查询enum需要一个单独的.h/.cpp对 取决于错综复杂的宏和包括魔术 小的语法错误会演变成大得多的错误 定义类或命名空间作用域的枚举不是简单的 没有编译时初始化

评论

当打开EnumTraits时,智能感知会抱怨一些私有成员访问。Inl,但由于扩展的宏实际上是定义类成员,这实际上不是一个问题。

头文件顶部的#ifndef ENUM_INCLUDE_MULTI块是一个小麻烦,可能会缩小到宏或其他内容中,但它足够小,可以接受当前的大小。

声明命名空间作用域的枚举要求首先在其命名空间作用域内向前声明枚举,然后在全局命名空间中定义枚举。此外,任何使用相同枚举值的枚举初始化器必须完全限定这些值。

namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}

其他回答

我不确定这种方法是否已经包含在其他答案中(实际上是,见下文)。我遇到过这个问题很多次,但没有找到不使用混淆宏或第三方库的解决方案。因此,我决定编写自己的模糊宏版本。

我想启用的是等价的

enum class test1 { ONE, TWO = 13, SIX };

std::string toString(const test1& e) { ... }

int main() {
    test1 x;
    std::cout << toString(x) << "\n";
    std::cout << toString(test1::TWO) << "\n";
    std::cout << static_cast<std::underlying_type<test1>::type>(test1::TWO) << "\n";
    //std::cout << toString(123);// invalid
}

应该打印

ONE
TWO
13

我不是宏的粉丝。然而,除非c++本身支持将枚举转换为字符串,否则必须使用某种代码生成和/或宏(我怀疑这种情况不会很快发生)。我正在使用x宏:

// x_enum.h
#include <string>
#include <map>
#include <type_traits>
#define x_begin enum class x_name {
#define x_val(X) X
#define x_value(X,Y) X = Y
#define x_end };
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end

#define x_begin inline std::string toString(const x_name& e) { \
                static std::map<x_name,std::string> names = { 
#define x_val(X)      { x_name::X , #X }
#define x_value(X,Y)  { x_name::X , #X }
#define x_end }; return names[e]; }
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#undef x_name
#undef x_enum_def

其中大部分是定义和取消定义符号,用户将通过include将这些符号作为参数传递给X-marco。用法是这样的

#define x_name test1
#define x_enum_def x_begin x_val(ONE) , \
                           x_value(TWO,13) , \
                           x_val(SIX) \
                   x_end
#include "x_enum.h"

现场演示

注意,我还没有包括选择基础类型。到目前为止,我还不需要它,但它应该是直接修改代码来启用它。

写完这篇文章后,我才意识到这和eferion的答案很相似。也许我以前读过,也许它是灵感的主要来源。我总是不能理解x -宏,直到我写了自己的;)。

你可以滥用用户定义的文字来达到想要的结果:

enum
{
  AAA = "AAA"_h8,
  BB = "BB"_h8,
};
   
std::cout << h8::to_string(AAA) << std::endl;
std::cout << h8::to_string(BB) << std::endl;

这将一个字符串打包成一个整数,这是可逆的。请看这里的例子。

如果你的枚举看起来像

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++元素,但它可以用来生成大量代码,而不需要太多的工作和维护。

我的意见,虽然这和行动要求的不完全相符。以下是有关参考资料。

namespace enums
{

template <typename T, T I, char ...Chars>
struct enums : std::integral_constant<T, I>
{
  static constexpr char const chars[sizeof...(Chars)]{Chars...};
};

template <typename T, T X, typename S, std::size_t ...I>
constexpr auto make(std::index_sequence<I...>) noexcept
{
  return enums<T, X, S().chars[I]...>();
}

#define ENUM(s, n) []() noexcept{\
  struct S { char const (&chars)[sizeof(s)]{s}; };\
  return enums::make<decltype(n), n, S>(\
    std::make_index_sequence<sizeof(s)>());}()

#define ENUM_T(s, n)\
  static constexpr auto s ## _tmp{ENUM(#s, n)};\
  using s ## _enum_t = decltype(s ## _tmp)

template <typename T, typename ...A, std::size_t N>
inline auto map(char const (&s)[N]) noexcept
{
  constexpr auto invalid(~T{});

  auto r{invalid};

  return
    (
      (
        invalid == r ?
          r = std::strncmp(A::chars, s, N) ? invalid : A{} :
          r
      ),
      ...
    );
}

}

int main()
{
  ENUM_T(echo, 0);
  ENUM_T(cat, 1);
  ENUM_T(ls, 2);

  std::cout << echo_enum_t{} << " " << echo_enum_t::chars << std::endl;

  std::cout << enums::map<int, echo_enum_t, cat_enum_t, ls_enum_t>("ls")) << std::endl;

  return 0;
}

你生成了一个类型,你可以把它转换成整数或者字符串。

嗯,还有另一个选择。一个典型的用例是,您需要为HTTP谓词使用常量,并使用其字符串版本值。

示例:

int main () {

  VERB a = VERB::GET;
  VERB b = VERB::GET;
  VERB c = VERB::POST;
  VERB d = VERB::PUT;
  VERB e = VERB::DELETE;


  std::cout << a.toString() << std::endl;

  std::cout << a << std::endl;

  if ( a == VERB::GET ) {
    std::cout << "yes" << std::endl;
  }

  if ( a == b ) {
    std::cout << "yes" << std::endl;
  }

  if ( a != c ) {
    std::cout << "no" << std::endl;
  }

}

VERB类:

// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {

private:

  // private constants
  enum Verb {GET_=0, POST_, PUT_, DELETE_};

  // private string values
  static const std::string theStrings[];

  // private value
  const Verb value;
  const std::string text;

  // private constructor
  VERB (Verb v) :
  value(v), text (theStrings[v])
  {
    // std::cout << " constructor \n";
  }

public:

  operator const char * ()  const { return text.c_str(); }

  operator const std::string ()  const { return text; }

  const std::string toString () const { return text; }

  bool operator == (const VERB & other) const { return (*this).value == other.value; }

  bool operator != (const VERB & other) const { return ! ( (*this) == other); }

  // ---

  static const VERB GET;
  static const VERB POST;
  static const VERB PUT;
  static const VERB DELETE;

};

const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};

const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file