C ++ 열거 형을 문자열로 변환하는 간단한 방법이 있습니까?
명명 된 열거 형이 있다고 가정합니다.
enum MyEnum {
FOO,
BAR = 0x50
};
내가 Google에서 찾은 것은 프로젝트의 모든 헤더를 스캔하고 열거 형당 하나의 함수로 헤더를 생성하는 스크립트 (모든 언어)입니다.
char* enum_to_string(MyEnum t);
그리고 이와 같은 구현 :
char* enum_to_string(MyEnum t){
switch(t){
case FOO:
return "FOO";
case BAR:
return "BAR";
default:
return "INVALID ENUM";
}
}
gotcha에는 실제로 typedefed 열거 형과 명명되지 않은 C 스타일 열거 형이 있습니다. 아무도 이것에 대해 알고 있습니까?
편집 : 생성 된 함수를 제외하고 솔루션에서 소스를 수정해서는 안됩니다. 열거 형은 API에 있으므로 지금까지 제안 된 솔루션을 사용하는 것은 옵션이 아닙니다.
GCCXML 을 확인하고 싶을 수도있다 .
샘플 코드에서 GCCXML을 실행하면 다음이 생성됩니다.
<GCC_XML>
<Namespace id="_1" name="::" members="_3 " mangled="_Z2::"/>
<Namespace id="_2" name="std" context="_1" members="" mangled="_Z3std"/>
<Enumeration id="_3" name="MyEnum" context="_1" location="f0:1" file="f0" line="1">
<EnumValue name="FOO" init="0"/>
<EnumValue name="BAR" init="80"/>
</Enumeration>
<File id="f0" name="my_enum.h"/>
</GCC_XML>
Enumeration 및 EnumValue 태그를 가져오고 원하는 코드를 생성하기 위해 원하는 언어를 사용할 수 있습니다.
X- 매크로가 최고의 솔루션입니다. 예:
#include <iostream>
enum Colours {
# define X(a) a,
# include "colours.def"
# undef X
ColoursCount
};
char const* const colours_str[] = {
# define X(a) #a,
# include "colours.def"
# undef X
0
};
std::ostream& operator<<(std::ostream& os, enum Colours c)
{
if (c >= ColoursCount || c < 0) return os << "???";
return os << colours_str[c];
}
int main()
{
std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}
colours.def :
X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)
그러나 일반적으로 다음 방법을 선호하므로 문자열을 약간 조정할 수 있습니다.
#define X(a, b) a,
#define X(a, b) b,
X(Red, "red")
X(Green, "green")
// etc.
@hydroo : 추가 파일없이 :
#define SOME_ENUM(DO) \
DO(Foo) \
DO(Bar) \
DO(Baz)
#define MAKE_ENUM(VAR) VAR,
enum MetaSyntacticVariable{
SOME_ENUM(MAKE_ENUM)
};
#define MAKE_STRINGS(VAR) #VAR,
const char* const MetaSyntacticVariableNames[] = {
SOME_ENUM(MAKE_STRINGS)
};
내가하는 경향은 열거 형 값과 동일한 순서와 위치로 이름을 가진 C 배열을 만드는 것입니다.
예.
enum colours { red, green, blue };
const char *colour_names[] = { "red", "green", "blue" };
그런 다음 사람이 읽을 수있는 값을 원하는 곳에서 배열을 사용할 수 있습니다. 예 :
colours mycolour = red;
cout << "the colour is" << colour_names[mycolour];
다음과 같은 상황에서 원하는 것을 수행하는 스트링 화 연산자 (전 처리기 참조의 # 참조)를 약간 실험 해 볼 수 있습니다.
#define printword(XX) cout << #XX;
printword(red);
stdout에 "빨간색"을 인쇄합니다. 불행히도 변수에 대해서는 작동하지 않습니다 (변수 이름이 인쇄되므로)
완전히 건조한 방식 으로이 작업을 수행하는 매크로를 사용하는 것이 매우 간단합니다. 그것은 variadic 매크로와 간단한 파싱 마법을 포함합니다. 간다 :
#define AWESOME_MAKE_ENUM(name, ...) enum class name { __VA_ARGS__, __COUNT}; \
inline std::ostream& operator<<(std::ostream& os, name value) { \
std::string enumName = #name; \
std::string str = #__VA_ARGS__; \
int len = str.length(); \
std::vector<std::string> strings; \
std::ostringstream temp; \
for(int i = 0; i < len; i ++) { \
if(isspace(str[i])) continue; \
else if(str[i] == ',') { \
strings.push_back(temp.str()); \
temp.str(std::string());\
} \
else temp<< str[i]; \
} \
strings.push_back(temp.str()); \
os << enumName << "::" << strings[static_cast<int>(value)]; \
return os;}
코드에서 이것을 사용하려면 다음을 수행하십시오.
AWESOME_MAKE_ENUM(Animal,
DOG,
CAT,
HORSE
);
QT는 (메타 오브젝트 컴파일러 덕분에)의를 당길 수있다 : 링크
나는 오늘이 바퀴를 다시 발명했고 그것을 공유 할 것이라고 생각했다.
이 구현에는 상수를 정의하는 코드를 변경할 필요 가 없습니다 . 상수 또는 열거 형 또는 #define
정수로 변환되는 다른 것이 될 수 있습니다 . 필자의 경우 다른 기호로 정의 된 기호가 있습니다. 희소 값에도 잘 작동합니다. 동일한 값에 여러 이름을 허용하여 항상 첫 번째 이름을 반환합니다. 유일한 단점은 상수 테이블을 만들어야한다는 것입니다. 예를 들어 새로운 상수가 추가되면 구식이 될 수 있습니다.
struct IdAndName
{
int id;
const char * name;
bool operator<(const IdAndName &rhs) const { return id < rhs.id; }
};
#define ID_AND_NAME(x) { x, #x }
const char * IdToName(int id, IdAndName *table_begin, IdAndName *table_end)
{
if ((table_end - table_begin) > 1 && table_begin[0].id > table_begin[1].id)
std::stable_sort(table_begin, table_end);
IdAndName searchee = { id, NULL };
IdAndName *p = std::lower_bound(table_begin, table_end, searchee);
return (p == table_end || p->id != id) ? NULL : p->name;
}
template<int N>
const char * IdToName(int id, IdAndName (&table)[N])
{
return IdToName(id, &table[0], &table[N]);
}
사용 방법의 예 :
static IdAndName WindowsErrorTable[] =
{
ID_AND_NAME(INT_MAX), // flag value to indicate unsorted table
ID_AND_NAME(NO_ERROR),
ID_AND_NAME(ERROR_INVALID_FUNCTION),
ID_AND_NAME(ERROR_FILE_NOT_FOUND),
ID_AND_NAME(ERROR_PATH_NOT_FOUND),
ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES),
ID_AND_NAME(ERROR_ACCESS_DENIED),
ID_AND_NAME(ERROR_INVALID_HANDLE),
ID_AND_NAME(ERROR_ARENA_TRASHED),
ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY),
ID_AND_NAME(ERROR_INVALID_BLOCK),
ID_AND_NAME(ERROR_BAD_ENVIRONMENT),
ID_AND_NAME(ERROR_BAD_FORMAT),
ID_AND_NAME(ERROR_INVALID_ACCESS),
ID_AND_NAME(ERROR_INVALID_DATA),
ID_AND_NAME(ERROR_INVALID_DRIVE),
ID_AND_NAME(ERROR_CURRENT_DIRECTORY),
ID_AND_NAME(ERROR_NOT_SAME_DEVICE),
ID_AND_NAME(ERROR_NO_MORE_FILES)
};
const char * error_name = IdToName(GetLastError(), WindowsErrorTable);
이 IdToName
함수 std::lower_bound
는 빠른 조회를 수행하기 때문에 테이블을 정렬해야합니다. 테이블의 처음 두 항목이 순서가 맞지 않으면 함수가 자동으로 정렬합니다.
편집 : 한 의견은 나에게 동일한 원칙을 사용하는 다른 방법을 생각하게했습니다. 매크로는 큰 switch
문장 의 생성을 단순화합니다 .
#define ID_AND_NAME(x) case x: return #x
const char * WindowsErrorToName(int id)
{
switch(id)
{
ID_AND_NAME(ERROR_INVALID_FUNCTION);
ID_AND_NAME(ERROR_FILE_NOT_FOUND);
ID_AND_NAME(ERROR_PATH_NOT_FOUND);
ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES);
ID_AND_NAME(ERROR_ACCESS_DENIED);
ID_AND_NAME(ERROR_INVALID_HANDLE);
ID_AND_NAME(ERROR_ARENA_TRASHED);
ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY);
ID_AND_NAME(ERROR_INVALID_BLOCK);
ID_AND_NAME(ERROR_BAD_ENVIRONMENT);
ID_AND_NAME(ERROR_BAD_FORMAT);
ID_AND_NAME(ERROR_INVALID_ACCESS);
ID_AND_NAME(ERROR_INVALID_DATA);
ID_AND_NAME(ERROR_INVALID_DRIVE);
ID_AND_NAME(ERROR_CURRENT_DIRECTORY);
ID_AND_NAME(ERROR_NOT_SAME_DEVICE);
ID_AND_NAME(ERROR_NO_MORE_FILES);
default: return NULL;
}
}
#define stringify( name ) # name
enum MyEnum {
ENUMVAL1
};
...stuff...
stringify(EnumName::ENUMVAL1); // Returns MyEnum::ENUMVAL1
이것은 C ++ 11에서 수행 할 수 있습니다
#include <map>
enum MyEnum { AA, BB, CC, DD };
static std::map< MyEnum, const char * > info = {
{AA, "This is an apple"},
{BB, "This is a book"},
{CC, "This is a coffee"},
{DD, "This is a door"}
};
void main()
{
std::cout << info[AA] << endl
<< info[BB] << endl
<< info[CC] << endl
<< info[DD] << endl;
}
방법의 수를 보는 것이 흥미 롭습니다. 여기 오래 전에 사용한 것이 있습니다.
myenummap.h 파일에서 :
#include <map>
#include <string>
enum test{ one, two, three, five=5, six, seven };
struct mymap : std::map<unsigned int, std::string>
{
mymap()
{
this->operator[]( one ) = "ONE";
this->operator[]( two ) = "TWO";
this->operator[]( three ) = "THREE";
this->operator[]( five ) = "FIVE";
this->operator[]( six ) = "SIX";
this->operator[]( seven ) = "SEVEN";
};
~mymap(){};
};
main.cpp에서
#include "myenummap.h"
...
mymap nummap;
std::cout<< nummap[ one ] << std::endl;
const가 아니라 편리합니다.
C ++ 11 기능을 사용하는 또 다른 방법이 있습니다. 이것은 const이며 STL 컨테이너를 상속하지 않으며 약간 더 깔끔합니다.
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
//These stay together and must be modified together
enum test{ one, two, three, five=5, six, seven };
std::string enum_to_str(test const& e)
{
typedef std::pair<int,std::string> mapping;
auto m = [](test const& e,std::string const& s){return mapping(static_cast<int>(e),s);};
std::vector<mapping> const nummap =
{
m(one,"one"),
m(two,"two"),
m(three,"three"),
m(five,"five"),
m(six,"six"),
m(seven,"seven"),
};
for(auto i : nummap)
{
if(i.first==static_cast<int>(e))
{
return i.second;
}
}
return "";
}
int main()
{
// std::cout<< enum_to_str( 46 ) << std::endl; //compilation will fail
std::cout<< "Invalid enum to string : [" << enum_to_str( test(46) ) << "]"<<std::endl; //returns an empty string
std::cout<< "Enumval five to string : ["<< enum_to_str( five ) << "] "<< std::endl; //works
return 0;
}
#include <stdarg.h>
#include <algorithm>
#include <string>
#include <vector>
#include <sstream>
#include <map>
#define SMART_ENUM(EnumName, ...) \
class EnumName \
{ \
private: \
static std::map<int, std::string> nameMap; \
public: \
enum {__VA_ARGS__}; \
private: \
static std::map<int, std::string> initMap() \
{ \
using namespace std; \
\
int val = 0; \
string buf_1, buf_2, str = #__VA_ARGS__; \
replace(str.begin(), str.end(), '=', ' '); \
stringstream stream(str); \
vector<string> strings; \
while (getline(stream, buf_1, ',')) \
strings.push_back(buf_1); \
map<int, string> tmp; \
for(vector<string>::iterator it = strings.begin(); \
it != strings.end(); \
++it) \
{ \
buf_1.clear(); buf_2.clear(); \
stringstream localStream(*it); \
localStream>> buf_1 >> buf_2; \
if(buf_2.size() > 0) \
val = atoi(buf_2.c_str()); \
tmp[val++] = buf_1; \
} \
return tmp; \
} \
public: \
static std::string toString(int aInt) \
{ \
return nameMap[aInt]; \
} \
}; \
std::map<int, std::string> \
EnumName::nameMap = EnumName::initMap();
용법:
SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
cout<<MyEnum::toString(MyEnum::TWO);
cout<<MyEnum::toString(10);
수마의 매크로 솔루션 이 좋습니다. 그러나 두 개의 다른 매크로를 가질 필요는 없습니다. C ++은 행복하게 헤더를 두 번 포함시킵니다. 포함 가드를 제거하십시오.
그래서 당신은 foobar.h를 가질 것입니다.
ENUM(Foo, 1)
ENUM(Bar, 2)
그리고 당신은 이것을 다음과 같이 포함시킬 것입니다 :
#define ENUMFACTORY_ARGUMENT "foobar.h"
#include "enumfactory.h"
enumfactory.h는 2 #include ENUMFACTORY_ARGUMENT
초를 수행합니다. 첫 번째 라운드에서는 Suma처럼 ENUM을 확장합니다 DECLARE_ENUM
. 두 번째 라운드에서 ENUM은 다음과 같이 작동합니다 DEFINE_ENUM
.
ENUMFACTORY_ARGUMENT에 대해 다른 #define을 전달하는 한 enumfactory.h도 여러 번 포함 할 수 있습니다.
또 다른 대답 : 일부 상황에서는 열거를 CSV, YAML 또는 XML 파일과 같은 비 코드 형식으로 정의한 다음 정의에서 C ++ 열거 코드와 문자열 화 코드를 모두 생성하는 것이 좋습니다. 이 접근 방식은 응용 프로그램에서 실용적이거나 실용적이지 않을 수 있지만 명심해야합니다.
이것은 @ user3360260 답변의 수정입니다. 다음과 같은 새로운 기능이 있습니다
MyEnum fromString(const string&)
지원하다- VisualStudio 2012로 컴파일
- 열거 형은 const 선언뿐만 아니라 실제 POD 유형이므로 변수에 할당 할 수 있습니다.
- 열거 형에 대해 "foreach"반복을 허용하는 C ++ "range"기능 (벡터 형식)을 추가했습니다.
용법:
SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
MyEnum foo = MyEnum::TWO;
cout << MyEnum::toString(foo); // static method
cout << foo.toString(); // member method
cout << MyEnum::toString(MyEnum::TWO);
cout << MyEnum::toString(10);
MyEnum foo = myEnum::fromString("TWO");
// C++11 iteration over all values
for( auto x : MyEnum::allValues() )
{
cout << x.toString() << endl;
}
코드는 다음과 같습니다
#define SMART_ENUM(EnumName, ...) \
class EnumName \
{ \
public: \
EnumName() : value(0) {} \
EnumName(int x) : value(x) {} \
public: \
enum {__VA_ARGS__}; \
private: \
static void initMap(std::map<int, std::string>& tmp) \
{ \
using namespace std; \
\
int val = 0; \
string buf_1, buf_2, str = #__VA_ARGS__; \
replace(str.begin(), str.end(), '=', ' '); \
stringstream stream(str); \
vector<string> strings; \
while (getline(stream, buf_1, ',')) \
strings.push_back(buf_1); \
for(vector<string>::iterator it = strings.begin(); \
it != strings.end(); \
++it) \
{ \
buf_1.clear(); buf_2.clear(); \
stringstream localStream(*it); \
localStream>> buf_1 >> buf_2; \
if(buf_2.size() > 0) \
val = atoi(buf_2.c_str()); \
tmp[val++] = buf_1; \
} \
} \
int value; \
public: \
operator int () const { return value; } \
std::string toString(void) const { \
return toString(value); \
} \
static std::string toString(int aInt) \
{ \
return nameMap()[aInt]; \
} \
static EnumName fromString(const std::string& s) \
{ \
auto it = find_if(nameMap().begin(), nameMap().end(), [s](const std::pair<int,std::string>& p) { \
return p.second == s; \
}); \
if (it == nameMap().end()) { \
/*value not found*/ \
throw EnumName::Exception(); \
} else { \
return EnumName(it->first); \
} \
} \
class Exception : public std::exception {}; \
static std::map<int,std::string>& nameMap() { \
static std::map<int,std::string> nameMap0; \
if (nameMap0.size() ==0) initMap(nameMap0); \
return nameMap0; \
} \
static std::vector<EnumName> allValues() { \
std::vector<EnumName> x{ __VA_ARGS__ }; \
return x; \
} \
bool operator<(const EnumName a) const { return (int)*this < (int)a; } \
};
String으로의 변환은 빠른 검색을하는 반면 String에서의 변환은 느린 선형 검색입니다. 그러나 문자열은 어쨌든 너무 비싸고 (및 관련 파일 IO), 나는 bimap을 최적화하거나 사용해야 할 필요성을 느끼지 못했습니다.
변환 함수는 이상적으로 const char *를 반환해야합니다 .
열거 형을 별도의 헤더 파일에 넣을 여유가 있다면 매크로를 사용하여 이와 같은 작업을 수행 할 수 있습니다 (오, 이것이 추한 것입니다).
#include "enum_def.h"
#include "colour.h"
#include "enum_conv.h"
#include "colour.h"
enum_def.h의 위치 :
#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) enum NAME {
#define ENUM_ADD(NAME, VALUE) NAME = VALUE,
#define ENUM_END };
그리고 enum_conv.h는 다음과 같습니다.
#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) const char *##NAME##_to_string(NAME val) { switch (val) {
#define ENUM_ADD(NAME, VALUE) case NAME: return #NAME;
#define ENUM_END default: return "Invalid value"; } }
그리고 마지막으로 colour.h에는 다음이 있습니다.
ENUM_START(colour)
ENUM_ADD(red, 0xff0000)
ENUM_ADD(green, 0x00ff00)
ENUM_ADD(blue, 0x0000ff)
ENUM_END
변환 기능을 다음과 같이 사용할 수 있습니다.
printf("%s", colour_to_string(colour::red));
이것은 추악하지만 코드의 한 곳에서 열거 형을 정의 할 수있는 유일한 방법입니다 (전 처리기 수준에서). 따라서 코드가 열거 형을 수정하여 오류가 발생하지 않습니다. 열거 형 정의와 변환 기능은 항상 동기화됩니다. 그러나 반복합니다. 이것은 추악합니다 :)
매크로로 생성되는 별도의 병렬 열거 형 래퍼 클래스 로이 작업을 수행합니다. 몇 가지 장점이 있습니다.
- 내가 정의하지 않은 열거 형에 대해 생성 할 수 있습니다 (예 : OS 플랫폼 헤더 열거 형)
- 래퍼 클래스에 범위 검사를 통합 할 수 있습니다
- 비트 필드 열거 형을 사용하여 "더 똑똑한"형식화 가능
물론 단점은 포맷터 클래스에서 열거 형 값을 복제해야하며 생성 할 스크립트가 없다는 것입니다. 그 외에는 꽤 잘 작동하는 것 같습니다.
다음은 내 코드베이스의 열거 형 예제입니다. 매크로 및 템플릿을 구현하는 모든 프레임 워크 코드는 있지만 아이디어를 얻을 수 있습니다.
enum EHelpLocation
{
HELP_LOCATION_UNKNOWN = 0,
HELP_LOCAL_FILE = 1,
HELP_HTML_ONLINE = 2,
};
class CEnumFormatter_EHelpLocation : public CEnumDefaultFormatter< EHelpLocation >
{
public:
static inline CString FormatEnum( EHelpLocation eValue )
{
switch ( eValue )
{
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCATION_UNKNOWN );
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCAL_FILE );
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_HTML_ONLINE );
default:
return FormatAsNumber( eValue );
}
}
};
DECLARE_RANGE_CHECK_CLASS( EHelpLocation, CRangeInfoSequential< HELP_HTML_ONLINE > );
typedef ESmartEnum< EHelpLocation, HELP_LOCATION_UNKNOWN, CEnumFormatter_EHelpLocation, CRangeInfo_EHelpLocation > SEHelpLocation;
아이디어는 EHelpLocation을 사용하는 대신 SEHelpLocation을 사용합니다. 모든 것이 동일하게 작동하지만 열거 형 변수 자체에서 범위 검사 및 'Format ()'메서드를 얻습니다. 독립형 값을 형식화해야하는 경우 CEnumFormatter_EHelpLocation :: FormatEnum (...)을 사용할 수 있습니다.
이것이 도움이 되길 바랍니다. 나는 이것이 실제로 다른 클래스를 생성하는 스크립트에 관한 원래의 질문을 다루지 않는다는 것을 알고 있지만, 구조가 누군가가 같은 문제를 해결하거나 그러한 스크립트를 작성하는 데 도움이되기를 바랍니다.
다음은 단일 파일 솔루션입니다 (@Marcin의 우아한 답변을 기반으로 함).
#include <iostream>
#define ENUM_TXT \
X(Red) \
X(Green) \
X(Blue) \
X(Cyan) \
X(Yellow) \
X(Magenta) \
enum Colours {
# define X(a) a,
ENUM_TXT
# undef X
ColoursCount
};
char const* const colours_str[] = {
# define X(a) #a,
ENUM_TXT
# undef X
0
};
std::ostream& operator<<(std::ostream& os, enum Colours c)
{
if (c >= ColoursCount || c < 0) return os << "???";
return os << colours_str[c] << std::endl;
}
int main()
{
std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}
이것은 BOOST와 함께 내 솔루션이었습니다.
#include <boost/preprocessor.hpp>
#define X_STR_ENUM_TOSTRING_CASE(r, data, elem) \
case elem : return BOOST_PP_STRINGIZE(elem);
#define X_ENUM_STR_TOENUM_IF(r, data, elem) \
else if(data == BOOST_PP_STRINGIZE(elem)) return elem;
#define STR_ENUM(name, enumerators) \
enum name { \
BOOST_PP_SEQ_ENUM(enumerators) \
}; \
\
inline const QString enumToStr(name v) \
{ \
switch (v) \
{ \
BOOST_PP_SEQ_FOR_EACH( \
X_STR_ENUM_TOSTRING_CASE, \
name, \
enumerators \
) \
\
default: \
return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \
} \
} \
\
template <typename T> \
inline const T strToEnum(QString v); \
\
template <> \
inline const name strToEnum(QString v) \
{ \
if(v=="") \
throw std::runtime_error("Empty enum value"); \
\
BOOST_PP_SEQ_FOR_EACH( \
X_ENUM_STR_TOENUM_IF, \
v, \
enumerators \
) \
\
else \
throw std::runtime_error( \
QString("[Unknown value %1 for enum %2]") \
.arg(v) \
.arg(BOOST_PP_STRINGIZE(name)) \
.toStdString().c_str()); \
}
열거 형을 만들려면 다음을 선언하십시오.
STR_ENUM
(
SERVICE_RELOAD,
(reload_log)
(reload_settings)
(reload_qxml_server)
)
전환의 경우 :
SERVICE_RELOAD serviceReloadEnum = strToEnum<SERVICE_RELOAD>("reload_log");
QString serviceReloadStr = enumToStr(reload_log);
Jasper Bekkers의 환상적인 답변 에 사용의 단순성을 추가하십시오 .
한 번 설정 :
#define MAKE_ENUM(VAR) VAR,
#define MAKE_STRINGS(VAR) #VAR,
#define MAKE_ENUM_AND_STRINGS(source, enumName, enumStringName) \
enum enumName { \
source(MAKE_ENUM) \
};\
const char* const enumStringName[] = { \
source(MAKE_STRINGS) \
};
그런 다음 사용법 :
#define SOME_ENUM(DO) \
DO(Foo) \
DO(Bar) \
DO(Baz)
...
MAKE_ENUM_AND_STRINGS(SOME_ENUM, someEnum, someEnumNames)
답변 0의 문제점은 열거 이진 값이 반드시 0에서 시작하지 않아도되고 반드시 연속적 일 필요는 없다는 것입니다.
이것이 필요할 때 나는 보통 :
- 열거 형 정의를 내 소스로 가져옵니다.
- 이름 만 얻으려면 편집
- 매크로를 사용하여 질문에서 case 절로 이름을 변경하십시오 (일반적으로 한 줄에 있음). case foo : return "foo";
- 스위치, 기본 및 기타 구문을 추가하여 합법적
다음 루비 스크립트는 헤더를 구문 분석하고 원본 헤더와 함께 필요한 소스를 빌드합니다.
#! /usr/bin/env ruby
# Let's "parse" the headers
# Note that using a regular expression is rather fragile
# and may break on some inputs
GLOBS = [
"toto/*.h",
"tutu/*.h",
"tutu/*.hxx"
]
enums = {}
GLOBS.each { |glob|
Dir[glob].each { |header|
enums[header] = File.open(header, 'rb') { |f|
f.read
}.scan(/enum\s+(\w+)\s+\{\s*([^}]+?)\s*\}/m).collect { |enum_name, enum_key_and_values|
[
enum_name, enum_key_and_values.split(/\s*,\s*/).collect { |enum_key_and_value|
enum_key_and_value.split(/\s*=\s*/).first
}
]
}
}
}
# Now we build a .h and .cpp alongside the parsed headers
# using the template engine provided with ruby
require 'erb'
template_h = ERB.new <<-EOS
#ifndef <%= enum_name %>_to_string_h_
#define <%= enum_name %>_to_string_h_ 1
#include "<%= header %>"
char* enum_to_string(<%= enum_name %> e);
#endif
EOS
template_cpp = ERB.new <<-EOS
#include "<%= enum_name %>_to_string.h"
char* enum_to_string(<%= enum_name %> e)
{
switch (e)
{<% enum_keys.each do |enum_key| %>
case <%= enum_key %>: return "<%= enum_key %>";<% end %>
default: return "INVALID <%= enum_name %> VALUE";
}
}
EOS
enums.each { |header, enum_name_and_keys|
enum_name_and_keys.each { |enum_name, enum_keys|
File.open("#{File.dirname(header)}/#{enum_name}_to_string.h", 'wb') { |built_h|
built_h.write(template_h.result(binding))
}
File.open("#{File.dirname(header)}/#{enum_name}_to_string.cpp", 'wb') { |built_cpp|
built_cpp.write(template_cpp.result(binding))
}
}
}
정규 표현식을 사용하면이 "파서"가 매우 약해져 특정 헤더를 정상적으로 처리하지 못할 수 있습니다.
열거 형 MyEnum 및 MyEnum2에 대한 정의가 포함 된 헤더 toto / ah가 있다고 가정합니다. 스크립트는 다음을 빌드합니다.
toto/MyEnum_to_string.h
toto/MyEnum_to_string.cpp
toto/MyEnum2_to_string.h
toto/MyEnum2_to_string.cpp
보다 강력한 솔루션은 다음과 같습니다.
- 열거 형과 그 운영을 정의하는 모든 소스를 다른 소스에서 빌드하십시오. 즉, C / C ++보다 구문 분석이 훨씬 쉬운 XML / YML / 무엇이든 파일에서 열거 형을 정의합니다.
- Avdi가 제안한 실제 컴파일러를 사용하십시오.
- 템플릿이 있거나없는 전 처리기 매크로를 사용하십시오.
출시되지 않은 소프트웨어이지만 Frank Laub의 BOOST_ENUM이 비용에 적합 할 것으로 보입니다. 내가 좋아하는 부분은 대부분의 매크로 기반 열거 형이 일반적으로 허용하지 않는 클래스 범위 내에서 열거 형을 정의 할 수 있다는 것입니다. Boost Vault의 다음 위치에 있습니다. http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=& 2006 년 이후 개발이 이루어지지 않아서 새로운 Boost 릴리스로 얼마나 잘 컴파일되는지 알 수 있습니다. 사용법 예제는 libs / test를보십시오.
누군가가 유용하다고 생각하는 경우에 이것을 게시하고 싶습니다.
필자의 경우 단일 파일 에서 단일 C ++ 11 열거 형 을 생성 ToString()
하고 FromString()
함수를 작성하면 .hpp
됩니다.
열거 형 항목이 포함 된 헤더 파일을 구문 분석하고 함수를 새 .cpp
파일로 생성하는 Python 스크립트를 작성했습니다 .
이 스크립트를 execute_process 와 함께 CMakeLists.txt에 추가 하거나 Visual Studio에서 사전 빌드 이벤트로 추가 할 수 있습니다 . .cpp
파일이 자동으로 수동으로 새로운 열거 항목이 추가 될 때마다 업데이트 할 필요없이 생성됩니다.
generate_enum_strings.py
# This script is used to generate strings from C++ enums
import re
import sys
import os
fileName = sys.argv[1]
enumName = os.path.basename(os.path.splitext(fileName)[0])
with open(fileName, 'r') as f:
content = f.read().replace('\n', '')
searchResult = re.search('enum(.*)\{(.*?)\};', content)
tokens = searchResult.group(2)
tokens = tokens.split(',')
tokens = map(str.strip, tokens)
tokens = map(lambda token: re.search('([a-zA-Z0-9_]*)', token).group(1), tokens)
textOut = ''
textOut += '\n#include "' + enumName + '.hpp"\n\n'
textOut += 'namespace myns\n'
textOut += '{\n'
textOut += ' std::string ToString(ErrorCode errorCode)\n'
textOut += ' {\n'
textOut += ' switch (errorCode)\n'
textOut += ' {\n'
for token in tokens:
textOut += ' case ' + enumName + '::' + token + ':\n'
textOut += ' return "' + token + '";\n'
textOut += ' default:\n'
textOut += ' return "Last";\n'
textOut += ' }\n'
textOut += ' }\n'
textOut += '\n'
textOut += ' ' + enumName + ' FromString(const std::string &errorCode)\n'
textOut += ' {\n'
textOut += ' if ("' + tokens[0] + '" == errorCode)\n'
textOut += ' {\n'
textOut += ' return ' + enumName + '::' + tokens[0] + ';\n'
textOut += ' }\n'
for token in tokens[1:]:
textOut += ' else if("' + token + '" == errorCode)\n'
textOut += ' {\n'
textOut += ' return ' + enumName + '::' + token + ';\n'
textOut += ' }\n'
textOut += '\n'
textOut += ' return ' + enumName + '::Last;\n'
textOut += ' }\n'
textOut += '}\n'
fileOut = open(enumName + '.cpp', 'w')
fileOut.write(textOut)
예:
ErrorCode.hpp
#pragma once
#include <string>
#include <cstdint>
namespace myns
{
enum class ErrorCode : uint32_t
{
OK = 0,
OutOfSpace,
ConnectionFailure,
InvalidJson,
DatabaseFailure,
HttpError,
FileSystemError,
FailedToEncrypt,
FailedToDecrypt,
EndOfFile,
FailedToOpenFileForRead,
FailedToOpenFileForWrite,
FailedToLaunchProcess,
Last
};
std::string ToString(ErrorCode errorCode);
ErrorCode FromString(const std::string &errorCode);
}
운영 python generate_enum_strings.py ErrorCode.hpp
결과:
ErrorCode.cpp
#include "ErrorCode.hpp"
namespace myns
{
std::string ToString(ErrorCode errorCode)
{
switch (errorCode)
{
case ErrorCode::OK:
return "OK";
case ErrorCode::OutOfSpace:
return "OutOfSpace";
case ErrorCode::ConnectionFailure:
return "ConnectionFailure";
case ErrorCode::InvalidJson:
return "InvalidJson";
case ErrorCode::DatabaseFailure:
return "DatabaseFailure";
case ErrorCode::HttpError:
return "HttpError";
case ErrorCode::FileSystemError:
return "FileSystemError";
case ErrorCode::FailedToEncrypt:
return "FailedToEncrypt";
case ErrorCode::FailedToDecrypt:
return "FailedToDecrypt";
case ErrorCode::EndOfFile:
return "EndOfFile";
case ErrorCode::FailedToOpenFileForRead:
return "FailedToOpenFileForRead";
case ErrorCode::FailedToOpenFileForWrite:
return "FailedToOpenFileForWrite";
case ErrorCode::FailedToLaunchProcess:
return "FailedToLaunchProcess";
case ErrorCode::Last:
return "Last";
default:
return "Last";
}
}
ErrorCode FromString(const std::string &errorCode)
{
if ("OK" == errorCode)
{
return ErrorCode::OK;
}
else if("OutOfSpace" == errorCode)
{
return ErrorCode::OutOfSpace;
}
else if("ConnectionFailure" == errorCode)
{
return ErrorCode::ConnectionFailure;
}
else if("InvalidJson" == errorCode)
{
return ErrorCode::InvalidJson;
}
else if("DatabaseFailure" == errorCode)
{
return ErrorCode::DatabaseFailure;
}
else if("HttpError" == errorCode)
{
return ErrorCode::HttpError;
}
else if("FileSystemError" == errorCode)
{
return ErrorCode::FileSystemError;
}
else if("FailedToEncrypt" == errorCode)
{
return ErrorCode::FailedToEncrypt;
}
else if("FailedToDecrypt" == errorCode)
{
return ErrorCode::FailedToDecrypt;
}
else if("EndOfFile" == errorCode)
{
return ErrorCode::EndOfFile;
}
else if("FailedToOpenFileForRead" == errorCode)
{
return ErrorCode::FailedToOpenFileForRead;
}
else if("FailedToOpenFileForWrite" == errorCode)
{
return ErrorCode::FailedToOpenFileForWrite;
}
else if("FailedToLaunchProcess" == errorCode)
{
return ErrorCode::FailedToLaunchProcess;
}
else if("Last" == errorCode)
{
return ErrorCode::Last;
}
return ErrorCode::Last;
}
}
Ponder 와 같은 리플렉션 라이브러리를 사용할 수 있습니다 . 열거 형을 등록 한 다음 API를 사용하여 앞뒤로 변환 할 수 있습니다.
enum class MyEnum
{
Zero = 0,
One = 1,
Two = 2
};
ponder::Enum::declare<MyEnum>()
.value("Zero", MyEnum::Zero)
.value("One", MyEnum::One)
.value("Two", MyEnum::Two);
ponder::EnumObject zero(MyEnum::Zero);
zero.name(); // -> "Zero"
그것은 할 수있는 유일한 방법입니다 (문자열 배열도 작동 할 수 있음).
문제는 일단 C 프로그램이 컴파일되면 열거 형의 이진 값만 사용되며 이름이 사라진다는 것입니다.
다음은 열거 형을 문자열로 쉽게 변환하기 위해 작성한 CLI 프로그램입니다. 사용하기 쉽고 5 초 정도 걸립니다 (프로그램이 포함 된 디렉토리로 cd 한 다음 실행하여 열거 형이 포함 된 파일로 전달하는 시간 포함).
여기에서 다운로드하십시오 : http://www.mediafire.com/?nttignoozzz
여기에 대한 토론 주제 : http://cboard.cprogramming.com/projects-job-recruitment/127488-free-program-im-sharing-convertenumtostrings.html
"--help"인수로 프로그램을 실행하여 사용법을 설명하십시오.
얼마 전 QComboBox에 열거 형을 올바르게 표시하고 열거 형과 문자열 표현을 하나의 문장으로 정의하는 트릭을 만들었습니다.
#pragma once
#include <boost/unordered_map.hpp>
namespace enumeration
{
struct enumerator_base : boost::noncopyable
{
typedef
boost::unordered_map<int, std::wstring>
kv_storage_t;
typedef
kv_storage_t::value_type
kv_type;
kv_storage_t const & kv() const
{
return storage_;
}
LPCWSTR name(int i) const
{
kv_storage_t::const_iterator it = storage_.find(i);
if(it != storage_.end())
return it->second.c_str();
return L"empty";
}
protected:
kv_storage_t storage_;
};
template<class T>
struct enumerator;
template<class D>
struct enum_singleton : enumerator_base
{
static enumerator_base const & instance()
{
static D inst;
return inst;
}
};
}
#define QENUM_ENTRY(K, V, N) K, N storage_.insert(std::make_pair((int)K, V));
#define QBEGIN_ENUM(NAME, C) \
enum NAME \
{ \
C \
} \
}; \
} \
#define QEND_ENUM(NAME) \
}; \
namespace enumeration \
{ \
template<> \
struct enumerator<NAME>\
: enum_singleton< enumerator<NAME> >\
{ \
enumerator() \
{
//usage
/*
QBEGIN_ENUM(test_t,
QENUM_ENTRY(test_entry_1, L"number uno",
QENUM_ENTRY(test_entry_2, L"number dos",
QENUM_ENTRY(test_entry_3, L"number tres",
QEND_ENUM(test_t)))))
*/
이제 enumeration::enum_singleton<your_enum>::instance()
열거 형을 문자열로 변환 할 수 있습니다. 당신이 교체하는 경우 kv_storage_t
에 boost::bimap
, 당신은 또한 이전 버전과의 변환을 할 수있을 것입니다. Qt 객체는 템플릿이 될 수 없기 때문에 Qt 객체에 저장하기 위해 변환기의 공통 기본 클래스가 도입되었습니다.
변형으로 간단한 lib> http://codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C를 사용 하십시오.
코드에서
#include <EnumString.h>
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 <string>
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>
#define MAKE_STRING(str, ...) #str, MAKE_STRING1_(__VA_ARGS__)
#define MAKE_STRING1_(str, ...) #str, MAKE_STRING2_(__VA_ARGS__)
#define MAKE_STRING2_(str, ...) #str, MAKE_STRING3_(__VA_ARGS__)
#define MAKE_STRING3_(str, ...) #str, MAKE_STRING4_(__VA_ARGS__)
#define MAKE_STRING4_(str, ...) #str, MAKE_STRING5_(__VA_ARGS__)
#define MAKE_STRING5_(str, ...) #str, MAKE_STRING6_(__VA_ARGS__)
#define MAKE_STRING6_(str, ...) #str, MAKE_STRING7_(__VA_ARGS__)
#define MAKE_STRING7_(str, ...) #str, MAKE_STRING8_(__VA_ARGS__)
#define MAKE_STRING8_(str, ...) #str, MAKE_STRING9_(__VA_ARGS__)
#define MAKE_STRING9_(str, ...) #str, MAKE_STRING10_(__VA_ARGS__)
#define MAKE_STRING10_(str) #str
#define MAKE_ENUM(name, ...) MAKE_ENUM_(, name, __VA_ARGS__)
#define MAKE_CLASS_ENUM(name, ...) MAKE_ENUM_(friend, name, __VA_ARGS__)
#define MAKE_ENUM_(attribute, name, ...) name { __VA_ARGS__ }; \
attribute std::istream& operator>>(std::istream& is, name& e) { \
const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
std::string str; \
std::istream& r = is >> str; \
const size_t len = sizeof(name##Str)/sizeof(name##Str[0]); \
const std::vector<std::string> enumStr(name##Str, name##Str + len); \
const std::vector<std::string>::const_iterator it = std::find(enumStr.begin(), enumStr.end(), str); \
if (it != enumStr.end())\
e = name(it - enumStr.begin()); \
else \
throw std::runtime_error("Value \"" + str + "\" is not part of enum "#name); \
return r; \
}; \
attribute std::ostream& operator<<(std::ostream& os, const name& e) { \
const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
return (os << name##Str[e]); \
}
용법:
// Declare global enum
enum MAKE_ENUM(Test3, Item13, Item23, Item33, Itdsdgem43);
class Essai {
public:
// Declare enum inside class
enum MAKE_CLASS_ENUM(Test, Item1, Item2, Item3, Itdsdgem4);
};
int main() {
std::cout << Essai::Item1 << std::endl;
Essai::Test ddd = Essai::Item1;
std::cout << ddd << std::endl;
std::istringstream strm("Item2");
strm >> ddd;
std::cout << (int) ddd << std::endl;
std::cout << ddd << std::endl;
}
그럼에도 불구 하고이 계획의 한계에 대해 확실하지 않습니다 ... 의견은 환영합니다!
#include <iostream>
#include <map>
#define IDMAP(x) (x,#x)
std::map<int , std::string> enToStr;
class mapEnumtoString
{
public:
mapEnumtoString(){ }
mapEnumtoString& operator()(int i,std::string str)
{
enToStr[i] = str;
return *this;
}
public:
std::string operator [] (int i)
{
return enToStr[i];
}
};
mapEnumtoString k;
mapEnumtoString& init()
{
return k;
}
int main()
{
init()
IDMAP(1)
IDMAP(2)
IDMAP(3)
IDMAP(4)
IDMAP(5);
std::cout<<enToStr[1];
std::cout<<enToStr[2];
std::cout<<enToStr[3];
std::cout<<enToStr[4];
std::cout<<enToStr[5];
}
참고 URL : https://stackoverflow.com/questions/201593/is-there-a-simple-way-to-convert-c-enum-to-string
'Programming' 카테고리의 다른 글
NPM을 업데이트하는 방법 (0) | 2020.07.19 |
---|---|
Java에서 파일 크기를 얻는 방법 (0) | 2020.07.19 |
왜 파이썬에서 0, 0 == (0, 0)이 (0, False) (0) | 2020.07.19 |
jQuery에서 버튼 클릭 이벤트를 처리하는 방법은 무엇입니까? (0) | 2020.07.19 |
Android에서 JSON을 구문 분석하는 방법 (0) | 2020.07.18 |