열거 형 변수를 문자열로 변환하는 방법은 무엇입니까?
열거 형 유형의 변수 값을 표시하기 위해 printf를 만드는 방법은 무엇입니까? 예를 들어 :
typedef enum {Linux, Apple, Windows} OS_type;
OS_type myOS = Linux;
내가 필요한 것은
printenum(OS_type, "My OS is %s", myOS);
정수가 아닌 문자열 "Linux"를 표시해야합니다.
먼저 값 인덱스 문자열 배열을 만들어야한다고 가정합니다. 그러나 그것이 가장 아름다운 방법인지는 모르겠습니다. 전혀 가능합니까?
실제로이 일을하는 아름다운 방법은 없습니다. 열거 형으로 색인 된 문자열 배열을 설정하십시오.
많은 출력을 수행하는 경우 enum 매개 변수를 사용하고 조회를 수행하는 operator <<를 정의 할 수 있습니다.
물론 순진한 솔루션은 문자열로의 변환을 수행하는 각 열거에 대한 함수를 작성하는 것입니다.
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 ++ 관련 구문 (예 : no typedef enum
)과 함수 오버로드를 사용하므로 C ++ 에 적합하지만 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(Os_type)
{
DECL_ENUM_ELEMENT(winblows),
DECL_ENUM_ELEMENT(hackintosh),
}
를 사용하여 전화
GetStringOs_type(winblows);
여기 에서 찍은 . 얼마나 멋진가요? :)
C 열거 형의 문제점은 C ++에서와 같이 자체 유형이 아니라는 것입니다. C의 열거 형은 식별자를 정수 값에 매핑하는 방법입니다. 그냥 이것이 열거 형 값을 정수 값과 교환 할 수있는 이유입니다.
올바르게 추측하면 좋은 방법은 열거 형 값과 문자열 사이의 매핑을 만드는 것입니다. 예를 들면 다음과 같습니다.
char * OS_type_label[] = {
"Linux",
"Apple",
"Windows"
};
James ' , Howard 's 및 Éder 's 솔루션 을 결합하고 보다 일반적인 구현을 만들었습니다.
- 각 enum 요소에 대해 int 값 및 사용자 정의 문자열 표현을 선택적으로 정의 할 수 있습니다.
- "enum class"가 사용됩니다
전체 코드는 다음과 같이 작성됩니다 (열 정의를 위해 "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] ;
이 간단한 예는 저에게 효과적이었습니다. 도움이 되었기를 바랍니다.
#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";
}
여기에는 좋은 대답이 많이 있지만 일부 사람들은 내 도움이 될 것이라고 생각했습니다. 매크로를 정의하는 데 사용하는 인터페이스가 가능한 한 간단하기 때문에 마음에 듭니다. 추가 라이브러리를 포함 할 필요가 없기 때문에 편리합니다. 모두 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
즐겨!
열거 형이 이미 정의되어 있다고 가정하면 쌍의 배열을 만들 수 있습니다.
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 ++에서 열거 형에서 문자열을 얻는 가장 우아한 방법이라고 생각했습니다.
이것을 시도 했습니까?
#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()
매크로는 문자열로 코드의 텍스트를 설정하는 데 사용하지만 정확한 텍스트 괄호 사이에 할 수있다. 변수 역 참조 또는 매크로 대체 또는 다른 종류의 작업은 없습니다.
http://www.cplusplus.com/forum/general/2949/
C99의 경우가 P99_DECLARE_ENUM
에서 P99 단순히 선언 할 수 있습니다 enum
다음과 같이 :
P99_DECLARE_ENUM(color, red, green, blue);
color_getname(A)
색상 이름이있는 문자열을 얻는 데 사용 합니다.
내 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)
부스트를 사용하지 않는 내 솔루션 :
#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 프리 프로세서 만 사용하는 Old Skool 방법 (gcc에서 광범위하게 사용됨)은 다음과 같습니다. 개별 데이터 구조를 생성하지만 순서를 일관성있게 유지해야하는 경우에 유용합니다. 물론 mylist.tbl의 항목은 훨씬 더 복잡한 것으로 확장 될 수 있습니다.
test.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 )
파티에 조금 늦었지만 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
.................
}
저의 선호는 반복적 인 타이핑과 이해하기 어려운 매크로를 최소화하고 일반 컴파일러 공간에 매크로 정의를 도입하지 않는 것입니다.
따라서 헤더 파일에서
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에 주목하십시오.
전처리기를 사용하여 상대방에게 또 다른 늦음 :
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- 매크로라는 잘 알려진 기술을 알려줍니다.)
7 행은 8-11 행의 실제 열거 선언을 채우기 위해 내부 매크로를 정의합니다. 12 행은 내부 매크로의 정의를 해제합니다 (컴파일러 경고를 침묵시키기 위해).
14 행은 열거 형 요소 이름의 문자열 버전을 만들기 위해 내부 매크로를 정의합니다. 그런 다음 15-18 행은 열거 형 값을 해당 문자열로 변환 할 수있는 배열을 생성합니다.
21-27 행은 문자열을 열거 형 값으로 변환하거나 문자열이 일치하지 않으면 NULL을 반환하는 함수를 생성합니다.
이것은 0 번째 요소를 처리하는 방식에 약간 번거 롭습니다. 나는 실제로 과거에 그 문제를 해결했습니다.
이 기술은 프리 프로세서 자체가 코드를 작성하도록 프로그래밍 될 수 있다고 생각하지 않는 사람들을 귀찮게한다. 가독성 과 유지 관리 성의 차이를 강력하게 보여주고 있다고 생각합니다 . 코드를 읽기는 어렵지만 열거 형에 수백 개의 요소가있는 경우 요소를 추가, 제거 또는 재 배열 할 수 있으며 생성 된 코드에 오류가 없는지 확인할 수 있습니다.
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";
}
#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 값으로 enum define을 지원하는 예제 코드를 원합니다.이 요구 사항도 있으므로 다음과 같습니다.
첫 번째는 내부 사용 매크로이며 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,
};
기본 아이디어는 모든 요소가 TUPLE 인 SEQ를 정의하는 것이므로 열거 형 멤버에 더하기 값을 넣을 수 있습니다. 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로 변환하고 이제 제임스 코드를 수정하면 모든 기능을 사용할 수 있습니다.
내 구현은 가장 간단한 구현이 아닐 수 있으므로 깨끗한 코드를 찾지 못하면 참조 용으로 사용하십시오.
순수한 표준 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
이론적 해석
핵심 문제를 해결할 때 "해당 문자열로 열거 형 상수가 있어야 함"은 합리적인 프로그래머가 다음 요구 사항을 충족시킵니다.
- 코드 반복을 피하십시오 ( "DRY"원칙).
- 열거 형 내부에 항목을 추가하거나 제거하더라도 코드는 확장 가능하고 유지 관리 가능하며 안전해야합니다.
- 모든 코드는 읽기 쉽고 유지 관리하기 쉬운 고품질이어야합니다.
첫 번째 요구 사항 및 두 번째 요구 사항은 악명 높은 "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
나는 그것이 매우 깨끗하고 사용하기 쉽다고 생각합니다. 몇 가지 제한 사항이 있습니다.
- 열거 형 멤버에는 값을 할당 할 수 없습니다.
- 열거 형 멤버의 값은 인덱스로 사용되지만 모든 것이 단일 매크로에 정의되어 있기 때문에 괜찮습니다.
- 클래스 내에서 열거 형을 정의하는 데 사용할 수 없습니다.
이 문제를 해결할 수 있다면. 나는 특히 그것을 사용하는 방법, 이것은 멋지고 마른 생각합니다. 장점 :
- 사용하기 쉬운.
- 런타임시 문자열 분할이 필요하지 않습니다.
- 컴파일시 별도의 문자열을 사용할 수 있습니다.
- 읽기 쉬운. 첫 번째 매크로 집합은 1 초가 더 필요할 수 있지만 실제로 그렇게 복잡하지는 않습니다.
이 문제에 대한 확실한 해결책은 다음과 같습니다.
#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 (카메라 모드, (3), 비행, FirstPerson, PerspectiveCorrect)
나는 양방향으로 작동하기 위해 이것을 필요로했으며 종종 열거 형을 포함하는 클래스에 포함시키기 때문에 James McNellis의 솔루션으로 시작 하여이 답변의 맨 위에 있지만 솔루션을 만들었습니다. 또한 열거 형이 아닌 열거 형 클래스를 선호하므로 대답이 다소 복잡합니다.
#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); }
부스트를 사용하지 않는 내 자신의 대답-무거운 마법을 정의하지 않고 내 자신의 접근 방식을 사용하면이 솔루션은 특정 열거 형 값을 정의 할 수 없다는 한계가 있습니다.
#pragma once
#include <string>
template <class Enum>
class EnumReflect
{
public:
static const char* getEnums() { return ""; }
};
#define DECLARE_ENUM(name, ...) \
enum name { __VA_ARGS__ }; \
template <> \
class EnumReflect<##name> { \
public: \
static const char* getEnums() { return #__VA_ARGS__; } \
};
/*
Basic usage:
Declare enumeration:
DECLARE_ENUM( enumName,
enumValue1,
enumValue2,
enumValue3,
// 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...");
WARNING: At the moment assigning enum value to specific number is not supported.
*/
//
// Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
const char* enums = EnumReflect<T>::getEnums();
const char *token, *next = enums - 1;
int id = (int)t;
do
{
token = next + 1;
if (*token == ' ') token++;
next = strchr(token, ',');
if (!next) next = token + strlen(token);
if (id == 0)
return std::string(token, next);
id--;
} while (*next != 0);
return std::string();
}
//
// Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
const char* enums = EnumReflect<T>::getEnums();
const char *token, *next = enums - 1;
int id = 0;
do
{
token = next + 1;
if (*token == ' ') token++;
next = strchr(token, ',');
if (!next) next = token + strlen(token);
if (strncmp(token, enumName, next - token) == 0)
{
t = (T)id;
return true;
}
id++;
} while (*next != 0);
return false;
}
최신 버전은 github에서 찾을 수 있습니다 :
https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h
열거 형 값을 지원하지 않는 하나의 답변을 추가했으며 이제 열거 형 값 할당을 지원하는 지원을 추가했습니다. 이전 솔루션에서와 마찬가지로 최소 정의 마법을 사용합니다.
헤더 파일은 다음과 같습니다.
#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/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h
참고URL : https://stackoverflow.com/questions/5093460/how-to-convert-an-enum-type-variable-to-a-string
'IT story' 카테고리의 다른 글
PostgreSQL : 통화에 어떤 데이터 유형을 사용해야합니까? (0) | 2020.08.05 |
---|---|
이 포인터 사용을 예측할 수 없게 만드는 것은 무엇입니까? (0) | 2020.08.05 |
CMakeLists.txt에 Boost 라이브러리를 어떻게 추가합니까? (0) | 2020.08.05 |
Textview에서 문자 간격을 변경하는 방법은 무엇입니까? (0) | 2020.08.04 |
몽구스 (mongodb) 배치 삽입물? (0) | 2020.08.04 |