[英]How to convert an enum type variable to a string?
如何讓 printf 顯示枚舉類型變量的值? 例如:
typedef enum {Linux, Apple, Windows} OS_type;
OS_type myOS = Linux;
我需要的是類似
printenum(OS_type, "My OS is %s", myOS);
它必須顯示字符串“Linux”,而不是 integer。
我想,首先我必須創建一個值索引的字符串數組。 但我不知道這是否是最美麗的方式。 有可能嗎?
當然,最簡單的解決方案是為每個枚舉編寫一個函數來執行到字符串的轉換:
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]";
}
}
然而,這是一場維護災難。 借助可用於 C 和 C++ 代碼的 Boost.Preprocessor 庫,您可以輕松利用預處理器並讓它為您生成此函數。 生成宏如下:
#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) "]"; \
} \
}
第一個宏(以X_
開頭)由第二個在內部使用。 第二個宏首先生成枚舉,然后生成一個ToString
函數,該函數采用該類型的對象並將枚舉器名稱作為字符串返回(出於顯而易見的原因,此實現要求枚舉器映射到唯一值)。
在 C++ 中,您可以將ToString
函數實現為operator<<
重載,但我認為需要顯式“ ToString
”將值轉換為字符串形式會更簡潔一些。
作為使用示例,您的OS_type
枚舉將定義如下:
DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))
雖然宏一開始看起來很繁重,而且OS_type
的定義看起來很陌生,但請記住,您必須編寫一次宏,然后才能在每次枚舉時使用它。 您可以向它添加附加功能(例如,將字符串形式轉換為枚舉)而不會帶來太多麻煩,並且它完全解決了維護問題,因為您只需在調用宏時提供一次名稱。
然后可以像正常定義一樣使用枚舉:
#include <iostream>
int main()
{
OS_type t = Windows;
std::cout << ToString(t) << " " << ToString(Apple) << std::endl;
}
這篇文章中的代碼片段以#include <boost/preprocessor.hpp>
行開頭,可以按照發布的方式進行編譯以演示解決方案。
這個特殊的解決方案適用於 C++,因為它使用 C++ 特定的語法(例如,沒有typedef enum
)和函數重載,但也可以直接使用 C 來實現它。
真的沒有漂亮的方法可以做到這一點。 只需設置一個由枚舉索引的字符串數組。
如果您進行大量輸出,則可以定義一個運算符<<,它接受一個枚舉參數並為您進行查找。
這是預處理器塊
#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
枚舉定義
BEGIN_ENUM(OsType)
{
DECL_ENUM_ELEMENT(WINBLOWS),
DECL_ENUM_ELEMENT(HACKINTOSH),
} END_ENUM(OsType)
呼叫使用
getStringOsType(WINBLOWS);
取自這里。 多么酷啊 ? :)
我結合了James 、 Howard和Éder 的解決方案並創建了一個更通用的實現:
完整代碼如下(使用“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;
}
使用std::map<OS_type, std::string>
並使用 enum 作為鍵填充它,並將字符串表示作為值填充它,然后您可以執行以下操作:
printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;
你試過這個嗎:
#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);
}
stringify()
宏可用於將代碼中的任何文本轉換為字符串,但僅限括號之間的確切文本。 沒有變量解引用或宏替換或任何其他類型的事情。
C 枚舉的問題在於它不是它自己的類型,就像在 C++ 中一樣。 C 中的枚舉是一種將標識符映射到整數值的方法。 只是。 這就是枚舉值可以與整數值互換的原因。
正如您猜對的那樣,一個好方法是在枚舉值和字符串之間創建映射。 例如:
char * OS_type_label[] = {
"Linux",
"Apple",
"Windows"
};
這里有很多很好的答案,但我認為有些人可能會覺得我的有用。 我喜歡它,因為您用來定義宏的接口非常簡單。 它也很方便,因為您不必包含任何額外的庫 - 它都隨 C++ 一起提供,甚至不需要非常晚的版本。 我從網上的各個地方提取了一些作品,所以我不能把所有的功勞都歸功於它,但我認為它的獨特性足以保證一個新的答案。
首先制作一個頭文件...將其命名為 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; \
} \
}
然后,在您的主程序中,您可以執行此操作...
#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);
}
輸出的位置 >> 'Apple' 的值是:2 of 4
享受!
這個簡單的例子對我有用。 希望這可以幫助。
#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";
}
我自己的偏好是盡量減少重復輸入和難以理解的宏,並避免將宏定義引入通用編譯器空間。
所以,在頭文件中:
enum Level{
/**
* zero reserved for internal use
*/
verbose = 1,
trace,
debug,
info,
warn,
fatal
};
static Level readLevel(const char *);
和 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);
}
完成后,請注意宏的#undef。
假設您的枚舉已經定義,您可以創建一個數組對:
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"),
};
現在,您可以創建地圖:
std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));
現在,您可以使用地圖了。 如果您的枚舉被更改,您必須從數組對 [] 中添加/刪除對。 我認為這是從 C++ 中的枚舉中獲取字符串的最優雅的方式。
對於 C99, P99_DECLARE_ENUM
在P99中可以讓您簡單地像這樣聲明enum
:
P99_DECLARE_ENUM(color, red, green, blue);
然后使用color_getname(A)
獲取帶有顏色名稱的字符串。
我的解決方案,不使用 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_ */
這是如何使用它
int main()
{
MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d);
pippo c = d;
cout << pippo_str::get(c) << "\n";
return 0;
}
這是我的 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)
聚會有點晚了,但這是我的 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
.................
}
另一個遲到的派對,使用預處理器:
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
(我只是輸入了行號,以便於討論。)第 1-4 行是您編輯以定義枚舉元素的內容。 (我稱它為“列表宏”,因為它是一個列出事物列表的宏。@Lundin 告訴我這些是一種眾所周知的技術,稱為 X-macros。)
第 7 行定義了內部宏,以填充第 8-11 行中的實際枚舉聲明。 第 12 行取消定義內部宏(只是為了消除編譯器警告)。
第 14 行定義了內部宏,以便創建枚舉元素名稱的字符串版本。 然后第 15-18 行生成一個可以將枚舉值轉換為相應字符串的數組。
第 21-27 行生成一個將字符串轉換為枚舉值的函數,如果字符串不匹配,則返回 NULL。
這在處理第 0 個元素的方式上有點麻煩。 我過去實際上已經解決了這個問題。
我承認這種技術會困擾那些不想認為預處理器本身可以被編程為您編寫代碼的人。 我認為它有力地說明了可讀性和可維護性之間的區別。 代碼難以閱讀,但如果枚舉有幾百個元素,您可以添加、刪除或重新排列元素,並且仍然確保生成的代碼沒有錯誤。
已經有很多好的答案,但magic_enum值得一看。
它把自己描述為——
現代 C++ 的枚舉(到字符串、從字符串、迭代)的靜態反射,適用於任何枚舉類型,無需任何宏或樣板代碼。
僅標頭的 C++17 庫為枚舉提供靜態反射,無需任何宏或樣板代碼即可處理任何枚舉類型。
示例用法
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
}
這是僅使用 C 預處理器的 Old Skool 方法(曾在 gcc 中廣泛使用)。 如果您正在生成離散數據結構但需要保持它們之間的順序一致,則很有用。 mylist.tbl 中的條目當然可以擴展到更復雜的內容。
測試.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"
};
然后是 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 )
就個人而言,我會做一些簡單的事情並使用運算符來做到這一點。
考慮以下枚舉:
enum WeekDay { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };
我們可以創建一個運算符來將結果輸出到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;
}
與此線程中提供的其他一些方法相比,樣板代碼確實相當大。 盡管如此,它的優點是非常簡單且易於使用。
std::cout << "First day of the week is " << WeekDay::Monday << std::endl;
#include <EnumString.h>
來自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,
};
插入
Begin_Enum_String( FORM )
{
Enum_String( F_NONE );
Enum_String( F_BOX );
Enum_String( F_CUBE );
Enum_String( F_SPHERE );
}
End_Enum_String;
如果枚舉中的值不重復,則工作正常。
將枚舉值轉換為字符串的示例代碼:
enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );
正好相反的示例代碼:
assert( EnumString< FORM >::To( f, str ) );
謝謝詹姆斯的建議。 它非常有用,所以我以另一種方式實現以某種方式做出貢獻。
#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;
}
為了擴展 James 的答案,有人想要一些示例代碼來支持帶有 int 值的枚舉定義,我也有這個要求,所以這是我的方法:
第一個是內部使用宏,由 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) ),
而且,這里是定義宏:
#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \
enum name { \
BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \
0, enumerators) };
所以在使用的時候,你可能喜歡這樣寫:
DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum,
((FIRST, 1))
((SECOND))
((MAX, SECOND)) )
這將擴展到:
enum MyEnum
{
FIRST = 1,
SECOND,
MAX = SECOND,
};
基本思想是定義一個 SEQ,其中每個元素都是一個 TUPLE,這樣我們就可以為 enum 成員添加值。 在 FOR_EACH 循環中,檢查項 TUPLE 大小,如果大小為 2,則將代碼展開為 KEY = VALUE,否則保留 TUPLE 的第一個元素。
因為輸入的 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))
宏DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ
只會保留每個 TUPLE 中的第一個元素,稍后轉換為 SEQ,現在修改 James 的代碼,您將擁有全部權力。
我的實現可能不是最簡單的,所以如果你沒有找到任何干凈的代碼,我的供你參考。
純標准 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;
}
輸出
0 hello
1 world
1 world
基本原理
在解決核心問題“有對應字符串的枚舉常量”時,明智的程序員會提出以下要求:
第一個要求,也許還有第二個要求,可以通過各種凌亂的宏解決方案來滿足,例如臭名昭著的“x 宏”技巧或其他形式的宏魔法。 此類解決方案的問題在於,它們會給您留下一堆完全無法閱讀的神秘宏——它們不滿足上述第三個要求。
這里唯一需要的是實際上有一個字符串查找表,我們可以通過使用枚舉變量作為索引來訪問它。 這樣的表自然必須直接對應於枚舉,反之亦然。 當其中一個更新時,另一個也必須更新,否則將無法工作。
代碼說明
假設我們有一個像
typedef enum
{
hello,
world
} test_t;
這可以更改為
#define TEST_0 hello
#define TEST_1 world
typedef enum
{
TEST_0,
TEST_1,
} test_t;
這些宏常量的優點是現在可以在其他地方使用,例如生成字符串查找表。 可以使用“stringify”宏將預處理器常量轉換為字符串:
#define STRF(x) #x
#define STRINGIFY(x) STRF(x)
const char* test_str[]=
{
STRINGIFY(TEST_0),
STRINGIFY(TEST_1),
};
就是這樣。 通過使用hello
,我們得到值為 0 的枚舉常量。通過使用test_str[hello]
我們得到字符串“hello”。
為了使枚舉和查找表直接對應,我們必須確保它們包含相同數量的項目。 如果有人維護代碼並且只更改枚舉,而不更改查找表,反之亦然,則此方法將不起作用。
解決方案是讓枚舉告訴您它包含多少項。 對此有一個常用的C技巧,只需在最后添加一個項目,這只是為了告訴枚舉有多少項目:
typedef enum
{
TEST_0,
TEST_1,
TEST_N // will have value 2, there are 2 enum constants in this enum
} test_t;
現在我們可以在編譯時檢查枚舉中的項數是否與查找表中的項數一樣多,最好使用 C11 靜態斷言:
_Static_assert(sizeof test_str / sizeof *test_str == TEST_N,
"Incorrect number of items in enum or look-up table");
(如果有人堅持使用恐龍編譯器,在舊版本的 C 標准中也有丑陋但功能齊全的方法來創建靜態斷言。至於 C++,它也支持靜態斷言。)
作為旁注,在 C11 中,我們還可以通過更改 stringify 宏來實現更高的類型安全性:
#define STRINGIFY(x) _Generic((x), int : STRF(x))
( int
因為枚舉常量實際上是int
類型,而不是test_t
)
這將阻止編譯STRINGIFY(random_stuff)
類的代碼。
我所做的是結合我在這里看到的以及在這個網站上的類似問題。 我做的是 Visual Studio 2013。我沒有用其他編譯器測試過它。
首先,我定義了一組可以實現這些技巧的宏。
// 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
接下來定義一個宏來創建枚舉類和獲取字符串的函數。
#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; \
}
現在定義一個枚舉類型並為其添加字符串變得非常容易。 您需要做的就是:
ENUM(MyEnumType, A, B, C);
以下幾行可用於測試它。
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;
}
這將輸出:
3
A
B
C
A
B
C
A
B
C
我相信它非常干凈且易於使用。 有一些限制:
如果你能解決這個問題。 我認為,尤其是如何使用它,這很好,很精簡。 好處:
這個問題的一個干凈的解決方案是:
#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>";
}
這個解決方案的好處是它很簡單,並且可以通過復制和替換輕松構建函數。 請注意,如果您要進行大量轉換並且您的枚舉有太多可能的值,則此解決方案可能會占用大量 CPU。
我有點晚了,但這是我使用 g++ 和僅標准庫的解決方案。 我試圖盡量減少命名空間污染並消除重新鍵入枚舉名稱的任何需要。
頭文件“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]]); \
} \
}
使用示例:
#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));
}
這將輸出:
MERCURY
EARTH
你只需要定義一次,你的命名空間不應該被污染,所有的計算只完成一次(其余的只是查找)。 但是,您沒有獲得枚舉類的類型安全性(它們仍然只是短整數),您無法為枚舉分配值,您必須在可以定義名稱空間的地方(例如全局)定義枚舉。
我不確定這方面的表現有多好,或者這是否是一個好主意(我在 C++ 之前學習了 C,所以我的大腦仍然以這種方式工作)。 如果有人知道為什么這是一個壞主意,請隨時指出。
現在是 2017 年,但問題仍然存在
還有一種方式:
#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;
}
輸出:
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"); \
}
這是一個精心設計的類擴展枚舉版本……除了提供的值之外,它不添加任何其他枚舉值。
用法:DECLARE_ENUM_SEQ(CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)
在 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";
}
我需要它在兩個方向上工作,並且我經常將我的枚舉嵌入到一個包含類中,所以我從 James McNellis 的解決方案開始,在這些答案的頂部,但我做了這個解決方案。 另請注意,我更喜歡 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)
要在類中使用它,您可以執行以下操作:
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)
}
我寫了一個 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);
}
您必須選擇要使用的宏,DEFINE_ENUMERATION 或 DEFINE_ENUMERATION_INSIDE_CLASS。 你會看到我在定義 ComponentStatus::Status 時使用了后者,但我在定義 Status 時使用了前者。 區別很簡單。 在類中,我將 to/from 方法作為“靜態”前綴,如果不在類中,則使用“內聯”。 微不足道的差異,但必要的。
不幸的是,我認為沒有一種干凈的方法可以避免這樣做:
const char * valueStr = ComponentStatus::ToString(value);
盡管您可以在類定義之后手動創建一個簡單地鏈接到類方法的內聯方法,例如:
inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }
如何使printf顯示枚舉類型的變量的值? 例如:
typedef enum {Linux, Apple, Windows} OS_type;
OS_type myOS = Linux;
我需要的是
printenum(OS_type, "My OS is %s", myOS);
其中必須顯示字符串“ Linux”,而不是整數。
我想,首先,我必須創建一個值索引的字符串數組。 但是我不知道這是否是最美麗的方式。 有可能嗎?
如何使printf顯示枚舉類型的變量的值? 例如:
typedef enum {Linux, Apple, Windows} OS_type;
OS_type myOS = Linux;
我需要的是
printenum(OS_type, "My OS is %s", myOS);
其中必須顯示字符串“ Linux”,而不是整數。
我想,首先,我必須創建一個值索引的字符串數組。 但是我不知道這是否是最美麗的方式。 有可能嗎?
另一個基於無依賴標准編譯預處理器的解決方案,它將為給定的枚舉生成 constexpr to_string
函數(並且不需要對現有枚舉進行任何代碼更改) 。
用法:
#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);
}
摘自要復制和粘貼的完整代碼:
// 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>"; \
}
我們希望統治它們的 C++ 內置反射解決方案最終將進入 C++26(現在我們知道它不會像 C++23 那樣發生)。
擴展@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;
}
編譯上面的例子
g++ <savedfilename>.cpp
./a.out
定義 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
未定義 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
如果定義了 DEBUG 宏,它將創建一個數組,如果未定義其原始枚舉將在預處理后創建。
我添加了一個不支持枚舉值的答案,現在添加了也支持枚舉值分配的支持。 像以前的解決方案一樣,此方法使用了最小定義魔術。
這是頭文件:
#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;
}
這是示例測試應用程序:
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);
相同頭文件的更新版本將保留在這里:
https://github.com/tapika/cppreflect/blob/master/cppreflect/enumreflect.h
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.