简体   繁体   English

如何将枚举类型变量转换为字符串?

[英]How to convert an enum type variable to a string?

How to make printf to show the values of variables which are of an enum type?如何让 printf 显示枚举类型变量的值? For instance:例如:

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

and what I need is something like我需要的是类似

printenum(OS_type, "My OS is %s", myOS);

which must show a string "Linux", not an integer.它必须显示字符串“Linux”,而不是 integer。

I suppose, first I have to create a value-indexed array of strings.我想,首先我必须创建一个值索引的字符串数组。 But I don't know if that is the most beautiful way to do it.但我不知道这是否是最美丽的方式。 Is it possible at all?有可能吗?

The naive solution, of course, is to write a function for each enumeration that performs the conversion to string:当然,最简单的解决方案是为每个枚举编写一个函数来执行到字符串的转换:

enum OS_type { Linux, Apple, Windows };

inline const char* ToString(OS_type v)
{
    switch (v)
    {
        case Linux:   return "Linux";
        case Apple:   return "Apple";
        case Windows: return "Windows";
        default:      return "[Unknown OS_type]";
    }
}

This, however, is a maintenance disaster.然而,这是一场维护灾难。 With the help of the Boost.Preprocessor library, which can be used with both C and C++ code, you can easily take advantage of the preprocessor and let it generate this function for you.借助可用于 C 和 C++ 代码的 Boost.Preprocessor 库,您可以轻松利用预处理器并让它为您生成此函数。 The generation macro is as follows:生成宏如下:

#include <boost/preprocessor.hpp>

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

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }

The first macro (beginning with X_ ) is used internally by the second.第一个宏(以X_开头)由第二个在内部使用。 The second macro first generates the enumeration, then generates a ToString function that takes an object of that type and returns the enumerator name as a string (this implementation, for obvious reasons, requires that the enumerators map to unique values).第二个宏首先生成枚举,然后生成一个ToString函数,该函数采用该类型的对象并将枚举器名称作为字符串返回(出于显而易见的原因,此实现要求枚举器映射到唯一值)。

In C++ you could implement the ToString function as an operator<< overload instead, but I think it's a bit cleaner to require an explicit " ToString " to convert the value to string form.在 C++ 中,您可以将ToString函数实现为operator<<重载,但我认为需要显式“ ToString ”将值转换为字符串形式会更简洁一些。

As a usage example, your OS_type enumeration would be defined as follows:作为使用示例,您的OS_type枚举将定义如下:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))

While the macro looks at first like it is a lot of work, and the definition of OS_type looks rather foreign, remember that you have to write the macro once, then you can use it for every enumeration.虽然宏一开始看起来很繁重,而且OS_type的定义看起来很陌生,但请记住,您必须编写一次宏,然后才能在每次枚举时使用它。 You can add additional functionality to it (eg, a string-form to enum conversion) without too much trouble, and it completely solves the maintenance problem, since you only have to provide the names once, when you invoke the macro.您可以向它添加附加功能(例如,将字符串形式转换为枚举)而不会带来太多麻烦,并且它完全解决了维护问题,因为您只需在调用宏时提供一次名称。

The enumeration can then be used as if it were defined normally:然后可以像正常定义一样使用枚举:

#include <iostream>

int main()
{
    OS_type t = Windows;
    std::cout << ToString(t) << " " << ToString(Apple) << std::endl;
}

The code snippets in this post, beginning with the #include <boost/preprocessor.hpp> line, can be compiled as posted to demonstrate the solution.这篇文章中的代码片段以#include <boost/preprocessor.hpp>行开头,可以按照发布的方式进行编译以演示解决方案。

This particular solution is for C++ as it uses C++-specific syntax (eg, no typedef enum ) and function overloading, but it would be straightforward to make this work with C as well.这个特殊的解决方案适用于 C++,因为它使用 C++ 特定的语法(例如,没有typedef enum )和函数重载,但也可以直接使用 C 来实现它。

There really is no beautiful way of doing this.真的没有漂亮的方法可以做到这一点。 Just set up an array of strings indexed by the enum.只需设置一个由枚举索引的字符串数组。

If you do a lot of output, you can define an operator<< that takes an enum parameter and does the lookup for you.如果您进行大量输出,则可以定义一个运算符<<,它接受一个枚举参数并为您进行查找。

This is the pre processor block这是预处理器块

#ifndef GENERATE_ENUM_STRINGS
    #define DECL_ENUM_ELEMENT( element ) element
    #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME
    #define END_ENUM( ENUM_NAME ) ENUM_NAME; \
            char* getString##ENUM_NAME(enum tag##ENUM_NAME index);
#else
    #define DECL_ENUM_ELEMENT( element ) #element
    #define BEGIN_ENUM( ENUM_NAME ) char* gs_##ENUM_NAME [] =
    #define END_ENUM( ENUM_NAME ) ; char* getString##ENUM_NAME(enum \
            tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; }
#endif

Enum definition枚举定义

BEGIN_ENUM(OsType)
{
    DECL_ENUM_ELEMENT(WINBLOWS),
    DECL_ENUM_ELEMENT(HACKINTOSH),
} END_ENUM(OsType)

Call using呼叫使用

getStringOsType(WINBLOWS); getStringOsType(WINBLOWS);

Taken from here .取自这里 How cool is that ?多么酷啊 ? :) :)

I have combined the James' , Howard's and Éder's solutions and created a more generic implementation:我结合了JamesHowardÉder 的解决方案并创建了一个更通用的实现:

  • int value and custom string representation can be optionally defined for each enum element可以为每个枚举元素选择定义 int 值和自定义字符串表示
  • "enum class" is used使用“枚举类”

The full code is written bellow (use "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" for defining an enum) ( online demo ).完整代码如下(使用“DEFINE_ENUM_CLASS_WITH_ToString_METHOD”定义枚举)(在线演示)。

#include <boost/preprocessor.hpp>
#include <iostream>

// ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ implementation is taken from:
// http://lists.boost.org/boost-users/2012/09/76055.php
//
// This macro do the following:
// input:
//      (Element1, "Element 1 string repr", 2) (Element2) (Element3, "Element 3 string repr")
// output:
//      ((Element1, "Element 1 string repr", 2)) ((Element2)) ((Element3, "Element 3 string repr"))
#define HELPER1(...) ((__VA_ARGS__)) HELPER2
#define HELPER2(...) ((__VA_ARGS__)) HELPER1
#define HELPER1_END
#define HELPER2_END
#define ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(sequence) BOOST_PP_CAT(HELPER1 sequence,_END)


// CREATE_ENUM_ELEMENT_IMPL works in the following way:
//  if (elementTuple.GetSize() == 4) {
//      GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)),
//  } else {
//      GENERATE: elementTuple.GetElement(0),
//  }
// Example 1:
//      CREATE_ENUM_ELEMENT_IMPL((Element1, "Element 1 string repr", 2, _))
//  generates:
//      Element1 = 2,
//
// Example 2:
//      CREATE_ENUM_ELEMENT_IMPL((Element2, _))
//  generates:
//      Element1,
#define CREATE_ENUM_ELEMENT_IMPL(elementTuple)                                          \
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 4),                       \
    BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple),        \
    BOOST_PP_TUPLE_ELEM(0, elementTuple)                                                \
),

// we have to add a dummy element at the end of a tuple in order to make 
// BOOST_PP_TUPLE_ELEM macro work in case an initial tuple has only one element.
// if we have a tuple (Element1), BOOST_PP_TUPLE_ELEM(2, (Element1)) macro won't compile.
// It requires that a tuple with only one element looked like (Element1,).
// Unfortunately I couldn't find a way to make this transformation, so
// I just use BOOST_PP_TUPLE_PUSH_BACK macro to add a dummy element at the end
// of a tuple, in this case the initial tuple will look like (Element1, _) what
// makes it compatible with BOOST_PP_TUPLE_ELEM macro
#define CREATE_ENUM_ELEMENT(r, data, elementTuple)                                      \
    CREATE_ENUM_ELEMENT_IMPL(BOOST_PP_TUPLE_PUSH_BACK(elementTuple, _))

#define DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, element)                                        \
    case enumName::element : return BOOST_PP_STRINGIZE(element);
#define DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, element, stringRepresentation)  \
    case enumName::element : return stringRepresentation;

// GENERATE_CASE_FOR_SWITCH macro generates case for switch operator.
// Algorithm of working is the following
//  if (elementTuple.GetSize() == 1) {
//      DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, elementTuple.GetElement(0))
//  } else {
//      DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, elementTuple.GetElement(0), elementTuple.GetElement(1))
//  }
//
// Example 1:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element1, "Element 1 string repr", 2))
//  generates:
//      case EnumName::Element1 : return "Element 1 string repr";
//
// Example 2:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element2))
//  generates:
//      case EnumName::Element2 : return "Element2";
#define GENERATE_CASE_FOR_SWITCH(r, enumName, elementTuple)                                                                                                 \
    BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 1),                                                                                       \
        DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple)),                                                          \
        DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple), BOOST_PP_TUPLE_ELEM(1, elementTuple))     \
    )


// DEFINE_ENUM_CLASS_WITH_ToString_METHOD final macro witch do the job
#define DEFINE_ENUM_CLASS_WITH_ToString_METHOD(enumName, enumElements)          \
enum class enumName {                                                           \
    BOOST_PP_SEQ_FOR_EACH(                                                      \
        CREATE_ENUM_ELEMENT,                                                    \
        0,                                                                      \
        ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)                     \
    )                                                                           \
};                                                                              \
inline const char* ToString(const enumName element) {                           \
        switch (element) {                                                      \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                GENERATE_CASE_FOR_SWITCH,                                       \
                enumName,                                                       \
                ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)             \
            )                                                                   \
            default: return "[Unknown " BOOST_PP_STRINGIZE(enumName) "]";       \
        }                                                                       \
}

DEFINE_ENUM_CLASS_WITH_ToString_METHOD(Elements,
(Element1)
(Element2, "string representation for Element2 ")
(Element3, "Element3 string representation", 1000)
(Element4, "Element 4 string repr")
(Element5, "Element5", 1005)
(Element6, "Element6 ")
(Element7)
)
// Generates the following:
//      enum class Elements {
//          Element1, Element2, Element3 = 1000, Element4, Element5 = 1005, Element6,
//      };
//      inline const char* ToString(const Elements element) {
//          switch (element) {
//              case Elements::Element1: return "Element1";
//              case Elements::Element2: return "string representation for Element2 ";
//              case Elements::Element3: return "Element3 string representation";
//              case Elements::Element4: return "Element 4 string repr";
//              case Elements::Element5: return "Element5";
//              case Elements::Element6: return "Element6 ";
//              case Elements::Element7: return "Element7";
//              default: return "[Unknown " "Elements" "]";
//          }
//      }

int main() {
    std::cout << ToString(Elements::Element1) << std::endl;
    std::cout << ToString(Elements::Element2) << std::endl;
    std::cout << ToString(Elements::Element3) << std::endl;
    std::cout << ToString(Elements::Element4) << std::endl;
    std::cout << ToString(Elements::Element5) << std::endl;
    std::cout << ToString(Elements::Element6) << std::endl;
    std::cout << ToString(Elements::Element7) << std::endl;

    return 0;
}

Use std::map<OS_type, std::string> and populate it with enum as key, and string representation as values, then you can do these:使用std::map<OS_type, std::string>并使用 enum 作为键填充它,并将字符串表示作为值填充它,然后您可以执行以下操作:

printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;

Did you try this:你试过这个吗:

#define stringify( name ) # name

enum enMyErrorValue
  {
  ERROR_INVALIDINPUT = 0,
  ERROR_NULLINPUT,
  ERROR_INPUTTOOMUCH,
  ERROR_IAMBUSY
  };

const char* enMyErrorValueNames[] = 
  {
  stringify( ERROR_INVALIDINPUT ),
  stringify( ERROR_NULLINPUT ),
  stringify( ERROR_INPUTTOOMUCH ),
  stringify( ERROR_IAMBUSY )
  };

void vPrintError( enMyErrorValue enError )
  {
  cout << enMyErrorValueNames[ enError ] << endl;
  }

int main()
  {
  vPrintError((enMyErrorValue)1);
  }

The stringify() macro can be used to turn any text in your code into a string, but only the exact text between the parentheses. stringify()宏可用于将代码中的任何文本转换为字符串,但仅限括号之间的确切文本。 There are no variable dereferencing or macro substitutions or any other sort of thing done.没有变量解引用或宏替换或任何其他类型的事情。

http://www.cplusplus.com/forum/general/2949/ http://www.cplusplus.com/forum/general/2949/

The problem with C enums is that it's not a type of it's own, like it is in C++. C 枚举的问题在于它不是它自己的类型,就像在 C++ 中一样。 An enum in C is a way to map identifiers to integral values. C 中的枚举是一种将标识符映射到整数值的方法。 Just that.只是。 That's why an enum value is interchangeable with integer values.这就是枚举值可以与整数值互换的原因。

As you guess correctly, a good way is to create a mapping between the enum value and a string.正如您猜对的那样,一个好方法是在枚举值和字符串之间创建映射。 For example:例如:

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};

There are a lot of good answers here, but I thought some people might find mine useful.这里有很多很好的答案,但我认为有些人可能会觉得我的有用。 I like it because the interface that you use to define the macro is about as simple as it can get.我喜欢它,因为您用来定义宏的接口非常简单。 It's also handy because you don't have to include any extra libraries - it all comes with C++ and it doesn't even require a really late version.它也很方便,因为您不必包含任何额外的库 - 它都随 C++ 一起提供,甚至不需要非常晚的版本。 I pulled pieces from various places online so I can't take credit for all of it, but I think it's unique enough to warrant a new answer.我从网上的各个地方提取了一些作品,所以我不能把所有的功劳都归功于它,但我认为它的独特性足以保证一个新的答案。

First make a header file... call it EnumMacros.h or something like that, and put this in it:首先制作一个头文件...将其命名为 EnumMacros.h 或类似的东西,然后将其放入其中:

// Search and remove whitespace from both ends of the string
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { rit++; }
    return std::string(it, rit.base());
}

static void SplitEnumArgs(const char* szArgs, std::string Array[], int nMax)
{
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        Array[nIdx] = TrimEnumString(strSub);
        nIdx++;
    }
};
// This will to define an enum that is wrapped in a namespace of the same name along with ToString(), FromString(), and COUNT
#define DECLARE_ENUM(ename, ...) \
    namespace ename { \
        enum ename { __VA_ARGS__, COUNT }; \
        static std::string _Strings[COUNT]; \
        static const char* ToString(ename e) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            return _Strings[e].c_str(); \
        } \
        static ename FromString(const std::string& strEnum) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            for (int i = 0; i < COUNT; i++) { if (_Strings[i] == strEnum) { return (ename)i; } } \
            return COUNT; \
        } \
    }

Then, in your main program you can do this...然后,在您的主程序中,您可以执行此操作...

#include "EnumMacros.h"
DECLARE_ENUM(OsType, Windows, Linux, Apple)

void main() {
    OsType::OsType MyOs = OSType::Apple;
    printf("The value of '%s' is: %d of %d\n", OsType::ToString(MyOs), (int)OsType::FromString("Apple"), OsType::COUNT);
}

Where the output would be >> The value of 'Apple' is: 2 of 4输出的位置 >> 'Apple' 的值是:2 of 4

Enjoy!享受!

This simple example worked for me.这个简单的例子对我有用。 Hope this helps.希望这可以帮助。

#include <iostream>
#include <string>

#define ENUM_TO_STR(ENUM) std::string(#ENUM)

enum DIRECTION{NORTH, SOUTH, WEST, EAST};

int main()
{
  std::cout << "Hello, " << ENUM_TO_STR(NORTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(SOUTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(EAST) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(WEST) << "!\n";
}

My own preference is to minimize both repetitive typing and hard to understand macros and to avoid introducing macro definitions into the general compiler space.我自己的偏好是尽量减少重复输入和难以理解的宏,并避免将宏定义引入通用编译器空间。

So, in the header file:所以,在头文件中:

enum Level{
        /**
        * zero reserved for internal use
        */
        verbose = 1,
        trace,
        debug,
        info,
        warn,
        fatal
    };

static Level readLevel(const char *);

and the cpp implementation is:和 cpp 实现是:

 Logger::Level Logger::readLevel(const char *in) { 
 #  define MATCH(x) if (strcmp(in,#x) ==0) return x; 
    MATCH(verbose);
    MATCH(trace);
    MATCH(debug);
    MATCH(info);
    MATCH(warn);
    MATCH(fatal);
 # undef MATCH
    std::string s("No match for logging level ");
    s += in;
    throw new std::domain_error(s);
 }

Note the #undef of the macro as soon we're done with it.完成后,请注意宏的#undef。

Assuming that your enum is already defined, you can create an array of pairs:假设您的枚举已经定义,您可以创建一个数组对:

std::pair<QTask::TASK, QString> pairs [] = {
std::pair<OS_type, string>(Linux, "Linux"),
std::pair<OS_type, string>(Windows, "Windows"),
std::pair<OS_type, string>(Apple, "Apple"),
};

Now, you can create a map:现在,您可以创建地图:

std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));

Now, you can use the map.现在,您可以使用地图了。 If your enum is changed, you have to add/remove pair from array pairs[].如果您的枚举被更改,您必须从数组对 [] 中添加/删除对。 I thinkk that it is the most elegant way to obtain a string from enum in C++.我认为这是从 C++ 中的枚举中获取字符串的最优雅的方式。

For C99 there is P99_DECLARE_ENUM in P99 that lets you simply declare enum like this:对于 C99, P99_DECLARE_ENUMP99中可以让您简单地像这样声明enum

P99_DECLARE_ENUM(color, red, green, blue);

and then use color_getname(A) to obtain a string with the color name.然后使用color_getname(A)获取带有颜色名称的字符串。

My solution, not using boost:我的解决方案,不使用 boost:

#ifndef EN2STR_HXX_
#define EN2STR_HXX_

#define MAKE_STRING_1(str     ) #str
#define MAKE_STRING_2(str, ...) #str, MAKE_STRING_1(__VA_ARGS__)
#define MAKE_STRING_3(str, ...) #str, MAKE_STRING_2(__VA_ARGS__)
#define MAKE_STRING_4(str, ...) #str, MAKE_STRING_3(__VA_ARGS__)
#define MAKE_STRING_5(str, ...) #str, MAKE_STRING_4(__VA_ARGS__)
#define MAKE_STRING_6(str, ...) #str, MAKE_STRING_5(__VA_ARGS__)
#define MAKE_STRING_7(str, ...) #str, MAKE_STRING_6(__VA_ARGS__)
#define MAKE_STRING_8(str, ...) #str, MAKE_STRING_7(__VA_ARGS__)

#define PRIMITIVE_CAT(a, b) a##b
#define MAKE_STRING(N, ...) PRIMITIVE_CAT(MAKE_STRING_, N)     (__VA_ARGS__)


#define PP_RSEQ_N() 8,7,6,5,4,3,2,1,0
#define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
#define PP_NARG( ...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N())

#define MAKE_ENUM(NAME, ...) enum NAME { __VA_ARGS__ };            \
  struct NAME##_str {                                              \
    static const char * get(const NAME et) {                       \
      static const char* NAME##Str[] = {                           \
                MAKE_STRING(PP_NARG(__VA_ARGS__), __VA_ARGS__) };  \
      return NAME##Str[et];                                        \
      }                                                            \
    };

#endif /* EN2STR_HXX_ */

And here is how to use it这是如何使用它

int main()
  {
  MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d);
  pippo c = d;
  cout << pippo_str::get(c) << "\n";
  return 0;
  }

Here is my C++ code:这是我的 C++ 代码:

/* 
 * File:   main.cpp
 * Author: y2k1234
 *
 * Created on June 14, 2013, 9:50 AM
 */

#include <cstdlib>
#include <stdio.h>

using namespace std;


#define MESSAGE_LIST(OPERATOR)                          \
                                       OPERATOR(MSG_A), \
                                       OPERATOR(MSG_B), \
                                       OPERATOR(MSG_C)
#define GET_LIST_VALUE_OPERATOR(msg)   ERROR_##msg##_VALUE
#define GET_LIST_SRTING_OPERATOR(msg)  "ERROR_"#msg"_NAME"

enum ErrorMessagesEnum
{
   MESSAGE_LIST(GET_LIST_VALUE_OPERATOR)
};
static const char* ErrorMessagesName[] = 
{
   MESSAGE_LIST(GET_LIST_SRTING_OPERATOR)
};

int main(int argc, char** argv) 
{

    int totalMessages = sizeof(ErrorMessagesName)/4;

    for (int i = 0; i < totalMessages; i++)
    {
        if (i == ERROR_MSG_A_VALUE)
        {
                printf ("ERROR_MSG_A_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_B_VALUE)
        {
                printf ("ERROR_MSG_B_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_C_VALUE)
        {
                printf ("ERROR_MSG_C_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else
        {
                printf ("??? => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
    }   

    return 0;
}

Output:

ERROR_MSG_A_VALUE => [0]=[ERROR_MSG_A_NAME]

ERROR_MSG_B_VALUE => [1]=[ERROR_MSG_B_NAME]

ERROR_MSG_C_VALUE => [2]=[ERROR_MSG_C_NAME]

RUN SUCCESSFUL (total time: 126ms)

A little late to the party, but here's my C++11 solution:聚会有点晚了,但这是我的 C++11 解决方案:

namespace std {
    template<> struct hash<enum_one> {
        std::size_t operator()(const enum_one & e) const {
            return static_cast<std::size_t>(e);
        }
    };
    template<> struct hash<enum_two> { //repeat for each enum type
        std::size_t operator()(const enum_two & e) const {
            return static_cast<std::size_t>(e);
        }
    };
}

const std::string & enum_name(const enum_one & e) {
    static const std::unordered_map<enum_one, const std::string> names = {
    #define v_name(n) {enum_one::n, std::string(#n)}
        v_name(value1),
        v_name(value2),
        v_name(value3)
    #undef v_name
    };
    return names.at(e);
}

const std::string & enum_name(const enum_two & e) { //repeat for each enum type
    .................
}

Another late to the party, using the preprocessor:另一个迟到的派对,使用预处理器:

 1  #define MY_ENUM_LIST \
 2      DEFINE_ENUM_ELEMENT(First) \
 3      DEFINE_ENUM_ELEMENT(Second) \
 4      DEFINE_ENUM_ELEMENT(Third) \
 5  
 6  //--------------------------------------
 7  #define DEFINE_ENUM_ELEMENT(name) , name
 8  enum MyEnum {
 9      Zeroth = 0
10      MY_ENUM_LIST
11  };
12  #undef DEFINE_ENUM_ELEMENT
13 
14  #define DEFINE_ENUM_ELEMENT(name) , #name
15  const char* MyEnumToString[] = {
16      "Zeroth"
17      MY_ENUM_LIST
18  };
19  #undef DEFINE_ENUM_ELEMENT
20
21  #define DEFINE_ENUM_ELEMENT(name) else if (strcmp(s, #name)==0) return name;
22  enum MyEnum StringToMyEnum(const char* s){
23      if (strcmp(s, "Zeroth")==0) return Zeroth;
24      MY_ENUM_LIST
25      return NULL;
26  }
27  #undef DEFINE_ENUM_ELEMENT

(I just put in line numbers so it's easier to talk about.) Lines 1-4 are what you edit to define the elements of the enum. (我只是输入了行号,以便于讨论。)第 1-4 行是您编辑以定义枚举元素的内容。 (I have called it a "list macro", because it's a macro that makes a list of things. @Lundin informs me these are a well-known technique called X-macros.) (我称它为“列表宏”,因为它是一个列出事物列表的宏。@Lundin 告诉我这些是一种众所周知的技术,称为 X-macros。)

Line 7 defines the inner macro so as to fill in the actual enum declaration in lines 8-11.第 7 行定义了内部宏,以填充第 8-11 行中的实际枚举声明。 Line 12 undefines the inner macro (just to silence the compiler warning).第 12 行取消定义内部宏(只是为了消除编译器警告)。

Line 14 defines the inner macro so as to create a string version of the enum element name.第 14 行定义了内部宏,以便创建枚举元素名称的字符串版本。 Then lines 15-18 generate an array that can convert an enum value to the corresponding string.然后第 15-18 行生成一个可以将枚举值转换为相应字符串的数组。

Lines 21-27 generate a function that converts a string to the enum value, or returns NULL if the string doesn't match any.第 21-27 行生成一个将字符串转换为枚举值的函数,如果字符串不匹配,则返回 NULL。

This is a little cumbersome in the way it handles the 0th element.这在处理第 0 个元素的方式上有点麻烦。 I've actually worked around that in the past.我过去实际上已经解决了这个问题。

I admit this technique bothers people who don't want to think the preprocessor itself can be programmed to write code for you.我承认这种技术会困扰那些不想认为预处理器本身可以被编程为您编写代码的人。 I think it strongly illustrates the difference between readability and maintainability .我认为它有力地说明了可读性可维护性之间的区别。 The code is difficult to read, but if the enum has a few hundred elements, you can add, remove, or rearrange elements and still be sure the generated code has no errors.代码难以阅读,但如果枚举有几百个元素,您可以添加、删除或重新排列元素,并且仍然确保生成的代码没有错误。

There are lots of good answers already, but magic_enum is worth a look.已经有很多好的答案,但magic_enum值得一看。

It describes itself as -它把自己描述为——

Static reflection for enums (to string, from string, iteration) for modern C++, work with any enum type without any macro or boilerplate code.现代 C++ 的枚举(到字符串、从字符串、迭代)的静态反射,适用于任何枚举类型,无需任何宏或样板代码。

Header-only C++17 library provides static reflection for enums, work with any enum type without any macro or boilerplate code.仅标头的 C++17 库为枚举提供静态反射,无需任何宏或样板代码即可处理任何枚举类型。

Example usage示例用法

enum Color { RED = 2, BLUE = 4, GREEN = 8 };


Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"

std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name);
if (color.has_value()) {
  // color.value() -> Color::GREEN
}

Here's the Old Skool method (used to be used extensively in gcc) using just the C pre-processor.这是仅使用 C 预处理器的 Old Skool 方法(曾在 gcc 中广泛使用)。 Useful if you're generating discrete data structures but need to keep the order consistent between them.如果您正在生成离散数据结构但需要保持它们之间的顺序一致,则很有用。 The entries in mylist.tbl can of course be extended to something much more complex. mylist.tbl 中的条目当然可以扩展到更复杂的内容。

test.cpp:测试.cpp:

enum {
#undef XX
#define XX(name, ignore) name ,
#include "mylist.tbl"
  LAST_ENUM
};

char * enum_names [] = {
#undef XX
#define XX(name, ignore) #name ,
#include "mylist.tbl"
   "LAST_ENUM"
};

And then mylist.tbl:然后是 mylist.tbl:

/*    A = enum                  */
/*    B = some associated value */
/*     A        B   */
  XX( enum_1 , 100)
  XX( enum_2 , 100 )
  XX( enum_3 , 200 )
  XX( enum_4 , 900 )
  XX( enum_5 , 500 )

Personally, I would go for something simple and use an operator to do so.就个人而言,我会做一些简单的事情并使用运算符来做到这一点。

Considering the following enum:考虑以下枚举:

enum WeekDay { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };

We can create an operator to output the result in an std::ostream .我们可以创建一个运算符来将结果输出到std::ostream

std::ostream &operator<<(std::ostream &stream, const WeekDay day) {
  switch (day) {
    case MONDAY:
      stream << "Monday";
      break;
    case TUESDAY:
      stream << "Tuesday";
      break;
    case WEDNESDAY:
      stream << "Wednesday";
      break;
    case THURSDAY:
      stream << "Thursday";
      break;
    case FRIDAY:
      stream << "Friday";
      break;
    case SATURDAY:
      stream << "Saturday";
      break;
    case SUNDAY:
      stream << "Sunday";
      break;
  }

  return stream;
}

The boilerplate code is indeed pretty big compared to some other methods presented in this thread.与此线程中提供的其他一些方法相比,样板代码确实相当大。 Still, it has the avantage of being pretty straightforward and easy to use.尽管如此,它的优点是非常简单且易于使用。

std::cout << "First day of the week is " << WeekDay::Monday << std::endl;
#include <EnumString.h>

from http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C and after来自http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C及之后

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

insert插入

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

Works fine if values in the enum are not duplicate.如果枚举中的值不重复,则工作正常。

Sample code for converting an enum value to string:将枚举值转换为字符串的示例代码:

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

Sample code for just the opposite:正好相反的示例代码:

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

Thanks James for your suggestion.谢谢詹姆斯的建议。 It was very useful so I implemented the other way around to contribute in some way.它非常有用,所以我以另一种方式实现以某种方式做出贡献。

#include <iostream>
#include <boost/preprocessor.hpp>

using namespace std;

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

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF(r, data, elem) \
    if (BOOST_PP_SEQ_TAIL(data) ==                                     \
            BOOST_PP_STRINGIZE(elem)) return                           \
            static_cast<int>(BOOST_PP_SEQ_HEAD(data)::elem); else

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)         \
    enum class name {                                                  \
        BOOST_PP_SEQ_ENUM(enumerators)                                 \
    };                                                                 \
                                                                       \
    inline const char* ToString(name v)                                \
    {                                                                  \
        switch (v)                                                     \
        {                                                              \
            BOOST_PP_SEQ_FOR_EACH(                                     \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,   \
                name,                                                  \
                enumerators                                            \
            )                                                          \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";  \
        }                                                              \
    }                                                                  \
                                                                       \
    inline int ToEnum(std::string s)                                   \
    {                                                                  \
        BOOST_PP_SEQ_FOR_EACH(                                         \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF,       \
                (name)(s),                                             \
                enumerators                                            \
            )                                                          \
        return -1;                                                     \
    }


DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows));

int main(void)
{
    OS_type t = OS_type::Windows;

    cout << ToString(t) << " " << ToString(OS_type::Apple) << " " << ToString(OS_type::Linux) << endl;

    cout << ToEnum("Windows") << " " << ToEnum("Apple") << " " << ToEnum("Linux") << endl;

    return 0;
}

To extend James' answer, someone want some example code to support enum define with int value, I also have this requirement, so here is my way:为了扩展 James 的答案,有人想要一些示例代码来支持带有 int 值的枚举定义,我也有这个要求,所以这是我的方法:

First one the is internal use macro, which is used by FOR_EACH:第一个是内部使用宏,由 FOR_EACH 使用:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE(r, data, elem)         \
    BOOST_PP_IF(                                                                \
        BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elem), 2),                           \
        BOOST_PP_TUPLE_ELEM(0, elem) = BOOST_PP_TUPLE_ELEM(1, elem),            \
        BOOST_PP_TUPLE_ELEM(0, elem) ),

And, here is the define macro:而且,这里是定义宏:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                  \
    enum name {                                                                 \
        BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \
                              0, enumerators) };

So when using it, you may like to write like this:所以在使用的时候,你可能喜欢这样写:

DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum,
    ((FIRST, 1))
    ((SECOND))
    ((MAX, SECOND)) )

which will expand to:这将扩展到:

enum MyEnum
{
    FIRST = 1,
    SECOND,
    MAX = SECOND,
};

The basic idea is to define a SEQ, which every element is a TUPLE, so we can put addition value for enum member.基本思想是定义一个 SEQ,其中每个元素都是一个 TUPLE,这样我们就可以为 enum 成员添加值。 In FOR_EACH loop, check the item TUPLE size, if the size is 2, expand the code to KEY = VALUE, else just keep the first element of TUPLE.在 FOR_EACH 循环中,检查项 TUPLE 大小,如果大小为 2,则将代码展开为 KEY = VALUE,否则保留 TUPLE 的第一个元素。

Because the input SEQ is actually TUPLEs, so if you want to define STRINGIZE functions, you may need to pre-process the input enumerators first, here is the macro to do the job:因为输入的 SEQ 实际上是 TUPLE,所以如果你想定义 STRINGIZE 函数,你可能需要先对输入的枚举数进行预处理,这里是执行这项工作的宏:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM(r, data, elem)           \
    BOOST_PP_TUPLE_ELEM(0, elem),

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ(enumerators)         \
    BOOST_PP_SEQ_SUBSEQ(                                                        \
        BOOST_PP_TUPLE_TO_SEQ(                                                  \
            (BOOST_PP_SEQ_FOR_EACH(                                             \
                DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM, 0, enumerators) \
            )),                                                                 \
            0,                                                                  \
            BOOST_PP_SEQ_SIZE(enumerators))

The macro DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ will only keep the first element in every TUPLE, and later convert to SEQ, now modify James' code, you will have the full power.DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ只会保留每个 TUPLE 中的第一个元素,稍后转换为 SEQ,现在修改 James 的代码,您将拥有全部权力。

My implementation maybe not the simplest one, so if you do not find any clean code, mine for your reference.我的实现可能不是最简单的,所以如果你没有找到任何干净的代码,我的供你参考。

Clean, safe solution in pure standard C:纯标准 C 中的清洁、安全溶液:

#include <stdio.h>

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

/* list of enum constants */
#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N
} test_t;

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

int main()
{  
  _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
                 "Incorrect number of items in enum or look-up table");

  printf("%d %s\n", hello, test_str[hello]);
  printf("%d %s\n", world, test_str[world]);
  test_t x = world;
  printf("%d %s\n", x, test_str[x]);

  return 0;
}

Output输出

0 hello
1 world
1 world

Rationale基本原理

When solving the core problem "have enum constants with corresponding strings", a sensible programmer will come up with the following requirements:在解决核心问题“有对应字符串的枚举常量”时,明智的程序员会提出以下要求:

  • Avoid code repetition ("DRY" principle).避免代码重复(“DRY”原则)。
  • The code must be scalable, maintainable and safe even if items are added or removed inside the enum.即使在枚举中添加或删除项目,代码也必须是可扩展、可维护和安全的。
  • All code should be of high quality: easy to read, easy to maintain.所有代码都应该是高质量的:易于阅读、易于维护。

The first requirement, and maybe also the second, can be fulfilled with various messy macro solutions such as the infamous "x macro" trick, or other forms of macro magic.第一个要求,也许还有第二个要求,可以通过各种凌乱的宏解决方案来满足,例如臭名昭​​著的“x 宏”技巧或其他形式的宏魔法。 The problem with such solutions is that they leave you with a completely unreadable mess of mysterious macros - they don't meet the third requirement above.此类解决方案的问题在于,它们会给您留下一堆完全无法阅读的神秘宏——它们不满足上述第三个要求。

The only thing needed here is actually to have a string look-up table, which we can access by using the enum variable as index.这里唯一需要的是实际上有一个字符串查找表,我们可以通过使用枚举变量作为索引来访问它。 Such a table must naturally correspond directly to the enum and vice versa.这样的表自然必须直接对应于枚举,反之亦然。 When one of them is updated, the other has to be updated too, or it will not work.当其中一个更新时,另一个也必须更新,否则将无法工作。


Explanation of the code代码说明

Suppose we have an enum like假设我们有一个像

typedef enum
{
  hello,
  world
} test_t;

This can be changed to这可以更改为

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

With the advantage that these macro constants can now be used elsewhere, to for example generate a string look-up table.这些宏常量的优点是现在可以在其他地方使用,例如生成字符串查找表。 Converting a pre-processor constant to a string can be done with a "stringify" macro:可以使用“stringify”宏将预处理器常量转换为字符串:

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

And that's it.就是这样。 By using hello , we get the enum constant with value 0. By using test_str[hello] we get the string "hello".通过使用hello ,我们得到值为 0 的枚举常量。通过使用test_str[hello]我们得到字符串“hello”。

To make the enum and look-up table correspond directly, we have to ensure that they contain the very same amount of items.为了使枚举和查找表直接对应,我们必须确保它们包含相同数量的项目。 If someone would maintain the code and only change the enum, and not the look-up table, or vice versa, this method won't work.如果有人维护代码并且只更改枚举,而不更改查找表,反之亦然,则此方法将不起作用。

The solution is to have the enum to tell you how many items it contains.解决方案是让枚举告诉您它包含多少项。 There is a commonly-used C trick for this, simply add an item at the end, which only fills the purpose of telling how many items the enum has:对此有一个常用的C技巧,只需在最后添加一个项目,这只是为了告诉枚举有多少项目:

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N  // will have value 2, there are 2 enum constants in this enum
} test_t;

Now we can check at compile time that the number of items in the enum is as many as the number of items in the look-up table, preferably with a C11 static assert:现在我们可以在编译时检查枚举中的项数是否与查找表中的项数一样多,最好使用 C11 静态断言:

_Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
               "Incorrect number of items in enum or look-up table");

(There are ugly but fully-functional ways to create static asserts in older versions of the C standard too, if someone insists on using dinosaur compilers. As for C++, it supports static asserts too.) (如果有人坚持使用恐龙编译器,在旧版本的 C 标准中也有丑陋但功能齐全的方法来创建静态断言。至于 C++,它也支持静态断言。)


As a side note, in C11 we can also achieve higher type safety by changing the stringify macro:作为旁注,在 C11 中,我们还可以通过更改 stringify 宏来实现更高的类型安全性:

#define STRINGIFY(x) _Generic((x), int : STRF(x))

( int because enumeration constants are actually of type int , not test_t ) int因为枚举常量实际上是int类型,而不是test_t

This will prevent code like STRINGIFY(random_stuff) from compiling.这将阻止编译STRINGIFY(random_stuff)类的代码。

What I made is a combination of what I have seen here and in similar questions on this site.我所做的是结合我在这里看到的以及在这个网站上的类似问题。 I made this is Visual Studio 2013. I have not tested it with other compilers.我做的是 Visual Studio 2013。我没有用其他编译器测试过它。

First of all I define a set of macros that will do the tricks.首先,我定义了一组可以实现这些技巧的宏。

// concatenation macros
#define CONCAT_(A, B) A ## B
#define CONCAT(A, B)  CONCAT_(A, B)

// generic expansion and stringification macros
#define EXPAND(X)           X
#define STRINGIFY(ARG)      #ARG
#define EXPANDSTRING(ARG)   STRINGIFY(ARG)        

// number of arguments macros
#define NUM_ARGS_(X100, X99, X98, X97, X96, X95, X94, X93, X92, X91, X90, X89, X88, X87, X86, X85, X84, X83, X82, X81, X80, X79, X78, X77, X76, X75, X74, X73, X72, X71, X70, X69, X68, X67, X66, X65, X64, X63, X62, X61, X60, X59, X58, X57, X56, X55, X54, X53, X52, X51, X50, X49, X48, X47, X46, X45, X44, X43, X42, X41, X40, X39, X38, X37, X36, X35, X34, X33, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, ...) N
#define NUM_ARGS(...) EXPAND(NUM_ARGS_(__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1))

// argument extraction macros
#define FIRST_ARG(ARG, ...) ARG
#define REST_ARGS(ARG, ...) __VA_ARGS__

// arguments to strings macros
#define ARGS_STR__(N, ...)  ARGS_STR_##N(__VA_ARGS__)
#define ARGS_STR_(N, ...)   ARGS_STR__(N, __VA_ARGS__)
#define ARGS_STR(...)       ARGS_STR_(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

#define ARGS_STR_1(ARG)     EXPANDSTRING(ARG)
#define ARGS_STR_2(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_1(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_3(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_2(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_4(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_3(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_5(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_4(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_6(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_5(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_7(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_6(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_8(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_7(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_9(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_8(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_10(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_9(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_11(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_10(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_12(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_11(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_13(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_12(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_14(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_13(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_15(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_14(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_16(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_15(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_17(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_16(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_18(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_17(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_19(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_18(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_20(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_19(EXPAND(REST_ARGS(__VA_ARGS__)))
// expand until _100 or as much as you need

Next define a single macro that will create the enum class and the functions to get the strings.接下来定义一个宏来创建枚举类和获取字符串的函数。

#define ENUM(NAME, ...)                                                                                             \
    enum class NAME                                                                                                 \
    {                                                                                                               \
        __VA_ARGS__                                                                                                 \
    };                                                                                                              \
                                                                                                                    \
    static const std::array<std::string, NUM_ARGS(__VA_ARGS__)> CONCAT(NAME, Strings) = { ARGS_STR(__VA_ARGS__) };  \
                                                                                                                    \
    inline const std::string& ToString(NAME value)                                                                  \
    {                                                                                                               \
        return CONCAT(NAME, Strings)[static_cast<std::underlying_type<NAME>::type>(value)];                         \
    }                                                                                                               \
                                                                                                                    \
    inline std::ostream& operator<<(std::ostream& os, NAME value)                                                   \
    {                                                                                                               \
        os << ToString(value);                                                                                      \
        return os;                                                                                                  \
    }

Now defining an enum type and have strings for it becomes really easy.现在定义一个枚举类型并为其添加字符串变得非常容易。 All you need to do is:您需要做的就是:

ENUM(MyEnumType, A, B, C);

The following lines can be used to test it.以下几行可用于测试它。

int main()
{
    std::cout << MyEnumTypeStrings.size() << std::endl;

    std::cout << ToString(MyEnumType::A) << std::endl;
    std::cout << ToString(MyEnumType::B) << std::endl;
    std::cout << ToString(MyEnumType::C) << std::endl;

    std::cout << MyEnumType::A << std::endl;
    std::cout << MyEnumType::B << std::endl;
    std::cout << MyEnumType::C << std::endl;

    auto myVar = MyEnumType::A;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::B;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::C;
    std::cout << myVar << std::endl;

    return 0;
}

This will output:这将输出:

3
A
B
C
A
B
C
A
B
C

I believe it is very clean and easy to use.我相信它非常干净且易于使用。 There are some limitations:有一些限制:

  • You cannot assign values to the enum members.您不能为枚举成员赋值。
  • The enum member's values are used as index, but that should be fine, because everything is defined in a single macro.枚举成员的值用作索引,但这应该没问题,因为一切都在单个宏中定义。
  • You cannot use it to define an enum type inside a class.您不能使用它在类中定义枚举类型。

If you can work around this.如果你能解决这个问题。 I think, especially how to use it, this is nice and lean.我认为,尤其是如何使用它,这很好,很精简。 Advantages:好处:

  • Easy to use.便于使用。
  • No string splitting at runtime required.不需要在运行时拆分字符串。
  • Separate strings are available at compile time.编译时可以使用单独的字符串。
  • Easy to read.易于阅读。 The first set of macros may need an extra second, but aren't really that complicated.第一组宏可能需要额外的一秒钟,但实际上并没有那么复杂。

A clean solution to this problem would be:这个问题的一个干净的解决方案是:

#define RETURN_STR(val, e) {if (val == e) {return #e;}}

std::string conv_dxgi_format_to_string(int value) {
    RETURN_STR(value, DXGI_FORMAT_UNKNOWN);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_FLOAT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_UINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_SINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_FLOAT);

    /* ... */

    return "<UNKNOWN>";
}

The good thing about this solution is that it is simple and also constructing the function can be done easily via copy and replace.这个解决方案的好处是它很简单,并且可以通过复制和替换轻松构建函数。 Note that if you are going to do a lot of conversions and your enum has too many possible values, this solution might become CPU intensive.请注意,如果您要进行大量转换并且您的枚举有太多可能的值,则此解决方案可能会占用大量 CPU。

I'm a bit late but here's my solution using g++ and only standard libraries.我有点晚了,但这是我使用 g++ 和仅标准库的解决方案。 I've tried to minimise namespace pollution and remove any need to re-typing enum names.我试图尽量减少命名空间污染并消除重新键入枚举名称的任何需要。

The header file "my_enum.hpp" is:头文件“my_enum.hpp”是:

#include <cstring>

namespace ENUM_HELPERS{
    int replace_commas_and_spaces_with_null(char* string){
        int i, N;
        N = strlen(string);
        for(i=0; i<N; ++i){
            if( isspace(string[i]) || string[i] == ','){
                string[i]='\0';
            }
        }
        return(N);
    }

    int count_words_null_delim(char* string, int tot_N){
        int i;
        int j=0;
        char last = '\0';
        for(i=0;i<tot_N;++i){
            if((last == '\0') && (string[i]!='\0')){
                ++j;
            }
            last = string[i];
        }
        return(j);
    }

    int get_null_word_offsets(char* string, int tot_N, int current_w){
        int i;
        int j=0;
        char last = '\0';
        for(i=0; i<tot_N; ++i){
            if((last=='\0') && (string[i]!='\0')){
                if(j == current_w){
                    return(i);
                }
                ++j;
            }
            last = string[i];
        }
        return(tot_N); //null value for offset
    }

    int find_offsets(int* offsets, char* string, int tot_N, int N_words){
        int i;
        for(i=0; i<N_words; ++i){
            offsets[i] = get_null_word_offsets(string, tot_N, i);
        }
        return(0);
    }
}


#define MAKE_ENUM(NAME, ...)                                            \
namespace NAME{                                                         \
    enum ENUM {__VA_ARGS__};                                            \
    char name_holder[] = #__VA_ARGS__;                                  \
    int name_holder_N =                                                 \
        ENUM_HELPERS::replace_commas_and_spaces_with_null(name_holder); \
    int N =                                                             \
        ENUM_HELPERS::count_words_null_delim(                           \
            name_holder, name_holder_N);                                \
    int offsets[] = {__VA_ARGS__};                                      \
    int ZERO =                                                          \
        ENUM_HELPERS::find_offsets(                                     \
            offsets, name_holder, name_holder_N, N);                    \
    char* tostring(int i){                                              \
       return(&name_holder[offsets[i]]);                                \
    }                                                                   \
}

Example of use:使用示例:

#include <cstdio>
#include "my_enum.hpp"

MAKE_ENUM(Planets, MERCURY, VENUS, EARTH, MARS)

int main(int argc, char** argv){    
    Planets::ENUM a_planet = Planets::EARTH;
    printf("%s\n", Planets::tostring(Planets::MERCURY));
    printf("%s\n", Planets::tostring(a_planet));
}

This will output:这将输出:

MERCURY
EARTH

You only have to define everything once, your namespace shouldn't be polluted, and all of the computation is only done once (the rest is just lookups).你只需要定义一次,你的命名空间不应该被污染,所有的计算只完成一次(其余的只是查找)。 However, you don't get the type-safety of enum classes (they are still just short integers), you cannot assign values to the enums, you have to define enums somewhere you can define namespaces (eg globally).但是,您没有获得枚举类的类型安全性(它们仍然只是短整数),您无法为枚举分配值,您必须在可以定义名称空间的地方(例如全局)定义枚举。

I'm not sure how good the performance on this is, or if it's a good idea (I learnt C before C++ so my brain still works that way).我不确定这方面的表现有多好,或者这是否是一个好主意(我在 C++ 之前学习了 C,所以我的大脑仍然以这种方式工作)。 If anyone knows why this is a bad idea feel free to point it out.如果有人知道为什么这是一个坏主意,请随时指出。

It's 2017 but the question is still alive现在是 2017 年,但问题仍然存在

Yet another way:还有一种方式:

#include <iostream>

#define ERROR_VALUES \
ERROR_VALUE(NO_ERROR, 0, "OK") \
ERROR_VALUE(FILE_NOT_FOUND, 1, "Not found") \
ERROR_VALUE(LABEL_UNINITIALISED, 2, "Uninitialized usage")

enum Error
{
#define ERROR_VALUE(NAME, VALUE, TEXT) NAME = VALUE,
    ERROR_VALUES
#undef ERROR_VALUE
};

inline std::ostream& operator<<(std::ostream& os, Error err)
{
    int errVal = static_cast<int>(err);
    switch (err)
    {
#define ERROR_VALUE(NAME, VALUE, TEXT) case NAME: return os << "[" << errVal << "]" << #NAME << ", " << TEXT;
    ERROR_VALUES
#undef ERROR_VALUE
    default:
        // If the error value isn't found (shouldn't happen)
        return os << errVal;
    }
}

int main() {
    std::cout << "Error: " << NO_ERROR << std::endl;
    std::cout << "Error: " << FILE_NOT_FOUND << std::endl;
    std::cout << "Error: " << LABEL_UNINITIALISED << std::endl;
    return 0;
}

Outputs:输出:

Error: [0]NO_ERROR, OK
Error: [1]FILE_NOT_FOUND, Not found
Error: [2]LABEL_UNINITIALISED, Uninitialized usage
#pragma once

#include <string>
#include <vector>
#include <sstream>
#include <algorithm>

namespace StringifyEnum
{
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { ++rit; }
    return std::string(it, rit.base());
}

static std::vector<std::string> SplitEnumArgs(const char* szArgs, int     nMax)
{
    std::vector<std::string> enums;
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        enums.push_back(StringifyEnum::TrimEnumString(strSub));
        ++nIdx;
    }
    return std::move(enums);
}    
}

#define DECLARE_ENUM_SEQ(ename, n, ...) \
    enum class ename { __VA_ARGS__ }; \
    const int MAX_NUMBER_OF_##ename(n); \
    static std::vector<std::string> ename##Strings = StringifyEnum::SplitEnumArgs(#__VA_ARGS__, MAX_NUMBER_OF_##ename); \
    inline static std::string ename##ToString(ename e) { \
        return ename##Strings.at((int)e); \
    } \
    inline static ename StringTo##ename(const std::string& en) { \
        const auto it = std::find(ename##Strings.begin(), ename##Strings.end(), en); \
        if (it != ename##Strings.end()) \
            return (ename) std::distance(ename##Strings.begin(), it); \
        throw std::runtime_error("Could not resolve string enum value");     \
    }

This is an elaborated class extended enum version...it does not add any other enum value other than those provided.这是一个精心设计的类扩展枚举版本……除了提供的值之外,它不添加任何其他枚举值。

Usage: DECLARE_ENUM_SEQ(CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)用法:DECLARE_ENUM_SEQ(CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)

In c++ like this:在 C++ 中是这样的:

enum OS_type{Linux, Apple, Windows};

std::string ToString( const OS_type v )
{
  const std::map< OS_type, std::string > lut =
    boost::assign::map_list_of( Linux, "Linux" )(Apple, "Apple )( Windows,"Windows");
  std::map< OS_type, std::string >::const_iterator it = lut.find( v );
  if ( lut.end() != it )
    return it->second;
  return "NOT FOUND";
}

I needed this to work in both directions AND I frequently embed my enums inside a containing class, and so I started with the solution by James McNellis way, way at the top of these answers, but I made this solution.我需要它在两个方向上工作,并且我经常将我的枚举嵌入到一个包含类中,所以我从 James McNellis 的解决方案开始,在这些答案的顶部,但我做了这个解决方案。 Note also I prefer enum class rather than just enum, which complicates the answer somewhat.另请注意,我更喜欢 enum class 而不仅仅是 enum,这使答案有些复杂。

#define X_DEFINE_ENUMERATION(r, datatype, elem) case datatype::elem : return BOOST_PP_STRINGIZE(elem);

// The data portion of the FOR_EACH should be (variable type)(value)
#define X_DEFINE_ENUMERATION2(r, dataseq, elem) \
    if (BOOST_PP_SEQ_ELEM(1, dataseq) == BOOST_PP_STRINGIZE(elem) ) return BOOST_PP_SEQ_ELEM(0, dataseq)::elem;

#define DEFINE_ENUMERATION_MASTER(modifier, name, toFunctionName, enumerators)    \
    enum class name {                                                         \
        Undefined,                                                            \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    modifier const char* ToString(const name & v)                               \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUMERATION,                                         \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }                                                                         \
                                                                              \
    modifier const name toFunctionName(const std::string & value)               \
    {                                                                         \
        BOOST_PP_SEQ_FOR_EACH(                                                \
            X_DEFINE_ENUMERATION2,                                            \
            (name)(value),                                                    \
            enumerators                                                       \
        )                                                                     \
        return name::Undefined;                                               \
    }

#define DEFINE_ENUMERATION(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(inline, name, toFunctionName, enumerators)

#define DEFINE_ENUMERATION_INSIDE_CLASS(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(static, name, toFunctionName, enumerators)

To use it inside a class, you could do something like this:要在类中使用它,您可以执行以下操作:

class ComponentStatus {
public:
    /** This is a simple bad, iffy, and good status. See other places for greater details. */
    DEFINE_ENUMERATION_INSIDE_CLASS(Status, toStatus, (RED)(YELLOW)(GREEN)
}

And I wrote a CppUnit test, which demonstrates how to use it:我写了一个 CppUnit 测试,它演示了如何使用它:

void
ComponentStatusTest::testSimple() {
    ComponentStatus::Status value = ComponentStatus::Status::RED;

    const char * valueStr = ComponentStatus::ToString(value);

    ComponentStatus::Status convertedValue = ComponentStatus::toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

DEFINE_ENUMERATION(Status, toStatus, (RED)(YELLOW)(GREEN))

void
ComponentStatusTest::testOutside() {
    Status value = Status::RED;

    const char * valueStr = ToString(value);

    Status convertedValue = toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

You have to pick which macro to use, either DEFINE_ENUMERATION or DEFINE_ENUMERATION_INSIDE_CLASS.您必须选择要使用的宏,DEFINE_ENUMERATION 或 DEFINE_ENUMERATION_INSIDE_CLASS。 You'll see I used the latter when defining ComponentStatus::Status but I used the former when just defining Status.你会看到我在定义 ComponentStatus::Status 时使用了后者,但我在定义 Status 时使用了前者。 The difference is simple.区别很简单。 Inside a class, I prefix the to/from methods as "static" and if not in a class, I use "inline".在类中,我将 to/from 方法作为“静态”前缀,如果不在类中,则使用“内联”。 Trivial differences, but necessary.微不足道的差异,但必要的。

Unfortunately, I don't think there's a clean way to avoid having to do this:不幸的是,我认为没有一种干净的方法可以避免这样做:

const char * valueStr = ComponentStatus::ToString(value);

although you could manually create an inline method after your class definition that simply chains to the class method, something like:尽管您可以在类定义之后手动创建一个简单地链接到类方法的内联方法,例如:

inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }

How to make printf to show the values of variables which are of an enum type?如何使printf显示枚举类型的变量的值? For instance:例如:

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

and what I need is something like我需要的是

printenum(OS_type, "My OS is %s", myOS);

which must show a string "Linux", not an integer.其中必须显示字符串“ Linux”,而不是整数。

I suppose, first I have to create a value-indexed array of strings.我想,首先,我必须创建一个值索引的字符串数组。 But I don't know if that is the most beautiful way to do it.但是我不知道这是否是最美丽的方式。 Is it possible at all?有可能吗?

How to make printf to show the values of variables which are of an enum type?如何使printf显示枚举类型的变量的值? For instance:例如:

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

and what I need is something like我需要的是

printenum(OS_type, "My OS is %s", myOS);

which must show a string "Linux", not an integer.其中必须显示字符串“ Linux”,而不是整数。

I suppose, first I have to create a value-indexed array of strings.我想,首先,我必须创建一个值索引的字符串数组。 But I don't know if that is the most beautiful way to do it.但是我不知道这是否是最美丽的方式。 Is it possible at all?有可能吗?

Yet another dependency-free standard-compilant preprocessor-based solution, which will generate constexpr to_string founction for a given enum (and doesn't require any code changes for existing enum) is here .另一个基于无依赖标准编译预处理器的解决方案,它将为给定的枚举生成 constexpr to_string函数(并且不需要对现有枚举进行任何代码更改)

Usage:用法:

#include "generate_to_string.hpp"

enum class test_enum
{
    value1,
    value2
};


GENERATE_TO_STRING(test_enum, value1, value2) // emits warning if not all values are listed

const char* foo(test_enum v)
{
    return to_string(v);
}

A full code taken from the gist to copy&paste:摘自要复制和粘贴的完整代码:

// workaround for old msvc preprocessor:
// https://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly
#define ETS_EXP(x) x

#define ETS_CASE(e, x)                                                         \
    case e::x:                                                                 \
        return #x;

#define ETS_FE_0(e)
#define ETS_FE_1(e, x) ETS_CASE(e, x)
#define ETS_FE_2(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_1(e, __VA_ARGS__))
#define ETS_FE_3(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_2(e, __VA_ARGS__))
#define ETS_FE_4(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_3(e, __VA_ARGS__))
#define ETS_FE_5(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_4(e, __VA_ARGS__))
#define ETS_FE_6(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_5(e, __VA_ARGS__))
#define ETS_FE_7(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_6(e, __VA_ARGS__))
#define ETS_FE_8(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_7(e, __VA_ARGS__))
#define ETS_FE_9(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_8(e, __VA_ARGS__))
#define ETS_FE_10(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_9(e, __VA_ARGS__))
#define ETS_FE_11(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_10(e, __VA_ARGS__))
#define ETS_FE_12(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_11(e, __VA_ARGS__))
#define ETS_FE_13(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_12(e, __VA_ARGS__))
#define ETS_FE_14(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_13(e, __VA_ARGS__))
#define ETS_FE_15(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_14(e, __VA_ARGS__))
#define ETS_FE_16(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_15(e, __VA_ARGS__))
#define ETS_FE_17(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_16(e, __VA_ARGS__))
#define ETS_FE_18(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_17(e, __VA_ARGS__))
#define ETS_FE_19(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_18(e, __VA_ARGS__))
#define ETS_FE_20(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_19(e, __VA_ARGS__))
#define ETS_FE_21(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_20(e, __VA_ARGS__))
#define ETS_FE_22(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_21(e, __VA_ARGS__))
#define ETS_FE_23(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_22(e, __VA_ARGS__))
#define ETS_FE_24(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_23(e, __VA_ARGS__))
#define ETS_FE_25(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_24(e, __VA_ARGS__))
#define ETS_FE_26(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_25(e, __VA_ARGS__))
#define ETS_FE_27(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_26(e, __VA_ARGS__))
#define ETS_FE_28(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_27(e, __VA_ARGS__))
#define ETS_FE_29(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_28(e, __VA_ARGS__))
#define ETS_FE_30(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_29(e, __VA_ARGS__))
#define ETS_FE_31(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_30(e, __VA_ARGS__))
#define ETS_FE_32(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_31(e, __VA_ARGS__))
#define ETS_FE_33(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_32(e, __VA_ARGS__))
#define ETS_FE_34(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_33(e, __VA_ARGS__))
#define ETS_FE_35(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_34(e, __VA_ARGS__))
#define ETS_FE_36(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_35(e, __VA_ARGS__))
#define ETS_FE_37(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_36(e, __VA_ARGS__))
#define ETS_FE_38(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_37(e, __VA_ARGS__))
#define ETS_FE_39(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_38(e, __VA_ARGS__))
#define ETS_FE_40(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_39(e, __VA_ARGS__))
#define ETS_FE_41(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_40(e, __VA_ARGS__))
#define ETS_FE_42(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_41(e, __VA_ARGS__))
#define ETS_FE_43(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_42(e, __VA_ARGS__))
#define ETS_FE_44(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_43(e, __VA_ARGS__))
#define ETS_FE_45(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_44(e, __VA_ARGS__))
#define ETS_FE_46(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_45(e, __VA_ARGS__))
#define ETS_FE_47(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_46(e, __VA_ARGS__))
#define ETS_FE_48(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_47(e, __VA_ARGS__))
#define ETS_FE_49(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_48(e, __VA_ARGS__))
#define ETS_FE_50(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_49(e, __VA_ARGS__))
#define ETS_FE_51(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_50(e, __VA_ARGS__))
#define ETS_FE_52(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_51(e, __VA_ARGS__))
#define ETS_FE_53(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_52(e, __VA_ARGS__))
#define ETS_FE_54(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_53(e, __VA_ARGS__))
#define ETS_FE_55(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_54(e, __VA_ARGS__))
#define ETS_FE_56(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_55(e, __VA_ARGS__))
#define ETS_FE_57(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_56(e, __VA_ARGS__))
#define ETS_FE_58(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_57(e, __VA_ARGS__))
#define ETS_FE_59(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_58(e, __VA_ARGS__))
#define ETS_FE_60(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_59(e, __VA_ARGS__))
#define ETS_FE_61(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_60(e, __VA_ARGS__))
#define ETS_FE_62(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_61(e, __VA_ARGS__))
#define ETS_FE_63(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_62(e, __VA_ARGS__))
#define ETS_FE_64(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_63(e, __VA_ARGS__))
#define ETS_FE_65(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_64(e, __VA_ARGS__))
#define ETS_FE_66(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_65(e, __VA_ARGS__))
#define ETS_FE_67(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_66(e, __VA_ARGS__))
#define ETS_FE_68(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_67(e, __VA_ARGS__))
#define ETS_FE_69(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_68(e, __VA_ARGS__))
#define ETS_FE_70(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_69(e, __VA_ARGS__))
#define ETS_FE_71(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_70(e, __VA_ARGS__))
#define ETS_FE_72(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_71(e, __VA_ARGS__))
#define ETS_FE_73(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_72(e, __VA_ARGS__))
#define ETS_FE_74(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_73(e, __VA_ARGS__))
#define ETS_FE_75(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_74(e, __VA_ARGS__))
#define ETS_FE_76(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_75(e, __VA_ARGS__))
#define ETS_FE_77(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_76(e, __VA_ARGS__))
#define ETS_FE_78(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_77(e, __VA_ARGS__))
#define ETS_FE_79(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_78(e, __VA_ARGS__))
#define ETS_FE_80(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_79(e, __VA_ARGS__))
#define ETS_FE_81(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_80(e, __VA_ARGS__))
#define ETS_FE_82(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_81(e, __VA_ARGS__))
#define ETS_FE_83(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_82(e, __VA_ARGS__))
#define ETS_FE_84(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_83(e, __VA_ARGS__))
#define ETS_FE_85(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_84(e, __VA_ARGS__))
#define ETS_FE_86(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_85(e, __VA_ARGS__))
#define ETS_FE_87(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_86(e, __VA_ARGS__))
#define ETS_FE_88(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_87(e, __VA_ARGS__))
#define ETS_FE_89(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_88(e, __VA_ARGS__))
#define ETS_FE_90(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_89(e, __VA_ARGS__))
#define ETS_FE_91(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_90(e, __VA_ARGS__))
#define ETS_FE_92(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_91(e, __VA_ARGS__))
#define ETS_FE_93(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_92(e, __VA_ARGS__))
#define ETS_FE_94(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_93(e, __VA_ARGS__))
#define ETS_FE_95(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_94(e, __VA_ARGS__))
#define ETS_FE_96(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_95(e, __VA_ARGS__))
#define ETS_FE_97(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_96(e, __VA_ARGS__))
#define ETS_FE_98(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_97(e, __VA_ARGS__))
#define ETS_FE_99(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_98(e, __VA_ARGS__))
#define ETS_FE_100(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_99(e, __VA_ARGS__))

#define ETS_MATCH_ARGS(                                                        \
    ign0, ign1, ign2, ign3, ign4, ign5, ign6, ign7, ign8, ign9, ign10, ign11,  \
    ign12, ign13, ign14, ign15, ign16, ign17, ign18, ign19, ign20, ign21,      \
    ign22, ign23, ign24, ign25, ign26, ign27, ign28, ign29, ign30, ign31,      \
    ign32, ign33, ign34, ign35, ign36, ign37, ign38, ign39, ign40, ign41,      \
    ign42, ign43, ign44, ign45, ign46, ign47, ign48, ign49, ign50, ign51,      \
    ign52, ign53, ign54, ign55, ign56, ign57, ign58, ign59, ign60, ign61,      \
    ign62, ign63, ign64, ign65, ign66, ign67, ign68, ign69, ign70, ign71,      \
    ign72, ign73, ign74, ign75, ign76, ign77, ign78, ign79, ign80, ign81,      \
    ign82, ign83, ign84, ign85, ign86, ign87, ign88, ign89, ign90, ign91,      \
    ign92, ign93, ign94, ign95, ign96, ign97, ign98, ign99, ign100, name, ...) \
    name

#define ETS_FOR_EACH(e, ...)                                                   \
    ETS_EXP(ETS_MATCH_ARGS(                                                    \
        _0, __VA_ARGS__, ETS_FE_100, ETS_FE_99, ETS_FE_98, ETS_FE_97,          \
        ETS_FE_96, ETS_FE_95, ETS_FE_94, ETS_FE_93, ETS_FE_92, ETS_FE_91,      \
        ETS_FE_90, ETS_FE_89, ETS_FE_88, ETS_FE_87, ETS_FE_86, ETS_FE_85,      \
        ETS_FE_84, ETS_FE_83, ETS_FE_82, ETS_FE_81, ETS_FE_80, ETS_FE_79,      \
        ETS_FE_78, ETS_FE_77, ETS_FE_76, ETS_FE_75, ETS_FE_74, ETS_FE_73,      \
        ETS_FE_72, ETS_FE_71, ETS_FE_70, ETS_FE_69, ETS_FE_68, ETS_FE_67,      \
        ETS_FE_66, ETS_FE_65, ETS_FE_64, ETS_FE_63, ETS_FE_62, ETS_FE_61,      \
        ETS_FE_60, ETS_FE_59, ETS_FE_58, ETS_FE_57, ETS_FE_56, ETS_FE_55,      \
        ETS_FE_54, ETS_FE_53, ETS_FE_52, ETS_FE_51, ETS_FE_50, ETS_FE_49,      \
        ETS_FE_48, ETS_FE_47, ETS_FE_46, ETS_FE_45, ETS_FE_44, ETS_FE_43,      \
        ETS_FE_42, ETS_FE_41, ETS_FE_40, ETS_FE_39, ETS_FE_38, ETS_FE_37,      \
        ETS_FE_36, ETS_FE_35, ETS_FE_34, ETS_FE_33, ETS_FE_32, ETS_FE_31,      \
        ETS_FE_30, ETS_FE_29, ETS_FE_28, ETS_FE_27, ETS_FE_26, ETS_FE_25,      \
        ETS_FE_24, ETS_FE_23, ETS_FE_22, ETS_FE_21, ETS_FE_20, ETS_FE_19,      \
        ETS_FE_18, ETS_FE_17, ETS_FE_16, ETS_FE_15, ETS_FE_14, ETS_FE_13,      \
        ETS_FE_12, ETS_FE_11, ETS_FE_10, ETS_FE_9, ETS_FE_8, ETS_FE_7,         \
        ETS_FE_6, ETS_FE_5, ETS_FE_4, ETS_FE_3, ETS_FE_2, ETS_FE_1,            \
        ETS_FE_0)(e, __VA_ARGS__))

#define GENERATE_TO_STRING(enum_type, ...)                                     \
    constexpr const char* to_string(enum_type val)                             \
    {                                                                          \
        switch (val)                                                           \
        {                                                                      \
            ETS_FOR_EACH(enum_type, __VA_ARGS__)                               \
        }                                                                      \
        return "<unknown>";                                                    \
    }

We hope C++ built-in reflection solution that rule them all will finally get into C++26 (now we know it won't happen as for C++23).我们希望统治它们的 C++ 内置反射解决方案最终将进入 C++26(现在我们知道它不会像 C++23 那样发生)。

Expanding on @Reno's answer, here is a working example,扩展@Reno 的答案,这是一个工作示例,

#include <stdio.h>

//debug macro, keep it defined or undefine
#define DEBUG 
//#undef DEBUG

#ifndef DEBUG
        #define DECL_ENUM_ELEMENT( element ) element
        #define BEGIN_ENUM( ENUM_NAME ) typedef enum
        #define END_ENUM( ENUM_NAME )  ENUM_NAME;
#else
        #define DECL_ENUM_ELEMENT( element ) #element
        #define BEGIN_ENUM( ENUM_NAME ) const char* gs_##ENUM_NAME [] =
        #define END_ENUM( ENUM_NAME ); \
                                int gs_##ENUM_NAME##size = sizeof(gs_##ENUM_NAME)/sizeof(gs_##ENUM_NAME[0]); \
                                const char* MatchEnumToString##ENUM_NAME(int  index) { \
                                if (index > (gs_##ENUM_NAME##size - 1) || index < 0) \
                                { \
                                       return "ERR: invalid"; \
                                }  \
                                else \
                                       return gs_##ENUM_NAME [index]; \
                                }
#endif                            

BEGIN_ENUM(Days)
{
    DECL_ENUM_ELEMENT(sunday),
    DECL_ENUM_ELEMENT(monday),
    DECL_ENUM_ELEMENT(tuesday),
    DECL_ENUM_ELEMENT(wednesday),
    DECL_ENUM_ELEMENT(thursday),
    DECL_ENUM_ELEMENT(friday),
    DECL_ENUM_ELEMENT(saturday)
}
END_ENUM(Days)

BEGIN_ENUM(fruit)
{
    DECL_ENUM_ELEMENT(apple),
    DECL_ENUM_ELEMENT(orange),
    DECL_ENUM_ELEMENT(mango)
}
END_ENUM(fruit)

void match_etos( int index )
{
#ifdef DEBUG
            printf("Day is %s ,", MatchEnumToStringDays(index) );
            printf("Fruit is %s\n", MatchEnumToStringfruit(index) );
#else
            printf("disabled match_etos, index: %d\n", index);
#endif                      
}

int main()
{
        match_etos(0);
        match_etos(1);
        match_etos(2);
        match_etos(3);
        match_etos(4);
        match_etos(5);
        match_etos(6);
        match_etos(-43);
#ifdef DEBUG        
        printf("gs_Dayssize %d, gs_fruitsize %d\n", gs_Dayssize, gs_fruitsize);
#endif
        return 0;
}

Compile above example with编译上面的例子

g++ <savedfilename>.cpp
./a.out

my output when DEBUG is defined,定义 DEBUG 时,我的 output,

Day is sunday ,Fruit is apple
Day is monday ,Fruit is orange
Day is tuesday ,Fruit is mango
Day is wednesday ,Fruit is ERR: invalid
Day is thursday ,Fruit is ERR: invalid
Day is friday ,Fruit is ERR: invalid
Day is saturday ,Fruit is ERR: invalid
Day is ERR: invalid ,Fruit is ERR: invalid
gs_Dayssize 7, gs_fruitsize 3

When DEBUG is not defined,未定义 DEBUG 时,

disabled match_etos, index: 0
disabled match_etos, index: 1
disabled match_etos, index: 2
disabled match_etos, index: 3
disabled match_etos, index: 4
disabled match_etos, index: 5
disabled match_etos, index: 6
disabled match_etos, index: -43

if DEBUG macro is defined it will create an array, if its not defined original enum will be created after pre-processing.如果定义了 DEBUG 宏,它将创建一个数组,如果未定义其原始枚举将在预处理后创建。

I have added one answer, which was not supporting enumeration values, now added support which supports enumeration value assignment as well. 我添加了一个不支持枚举值的答案,现在添加了也支持枚举值分配的支持。 Like in previous solution this one uses minimum define magic. 像以前的解决方案一样,此方法使用了最小定义魔术。

Here is header file: 这是头文件:

#pragma once
#include <string>
#include <map>
#include <regex>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
    static std::map<std::string, int> enum2int;
    static std::map<int, std::string> int2enum;

    static void EnsureEnumMapReady( const char* enumsInfo )
    {
        if (*enumsInfo == 0 || enum2int.size() != 0 )
            return;

        // Should be called once per each enumeration.
        std::string senumsInfo(enumsInfo);
        std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
        std::smatch sm;
        int value = 0;

        for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
        {
            string enumName = sm[1].str();
            string enumValue = sm[2].str();

            if (enumValue.length() != 0)
                value = atoi(enumValue.c_str());

            enum2int[enumName] = value;
            int2enum[value] = enumName;
        }
    }
};

template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;

template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;


#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name>: public EnumReflectBase<##name> {         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

        printf( EnumToString(enumValue3).c_str() );

    From string to enumeration:

       enumName value;

       if( !StringToEnum("enumValue4", value) )
            printf("Conversion failed...");
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& int2enum = EnumReflect<T>::int2enum;
    auto it = int2enum.find(t);

    if (it == int2enum.end())
        return "";

    return it->second;
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& enum2int = EnumReflect<T>::enum2int;
    auto it = enum2int.find(enumName);

    if (it == enum2int.end())
        return false;

    t = (T) it->second;
    return true;
}

And here is example test application: 这是示例测试应用程序:

DECLARE_ENUM(TestEnum,
    ValueOne,
    ValueTwo,
    ValueThree = 5,
    ValueFour = 7
);

DECLARE_ENUM(TestEnum2,
    ValueOne2 = -1,
    ValueTwo2,
    ValueThree2 = -4,
    ValueFour2
);

void main(void)
{
    string sName1 = EnumToString(ValueOne);
    string sName2 = EnumToString(ValueTwo);
    string sName3 = EnumToString(ValueThree);
    string sName4 = EnumToString(ValueFour);

    TestEnum t1, t2, t3, t4, t5 = ValueOne;
    bool b1 = StringToEnum(sName1.c_str(), t1);
    bool b2 = StringToEnum(sName2.c_str(), t2);
    bool b3 = StringToEnum(sName3.c_str(), t3);
    bool b4 = StringToEnum(sName4.c_str(), t4);
    bool b5 = StringToEnum("Unknown", t5);

    string sName2_1 = EnumToString(ValueOne2);
    string sName2_2 = EnumToString(ValueTwo2);
    string sName2_3 = EnumToString(ValueThree2);
    string sName2_4 = EnumToString(ValueFour2);

    TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
    bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
    bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
    bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
    bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
    bool b2_5 = StringToEnum("Unknown", t2_5);

Updated version of same header file will be kept here: 相同头文件的更新版本将保留在这里:

https://github.com/tapika/cppreflect/blob/master/cppreflect/enumreflect.h https://github.com/tapika/cppreflect/blob/master/cppreflect/enumreflect.h

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM