현대 C++11 / C++14 / C++17 및 미래 C++20의 문자열에 열거
다른 모든 유사한 질문과 달리 이 질문은 새로운 C++ 기능을 사용하는 것에 관한 것입니다.
- 2008 c C++ 열거를 문자열로 변환하는 간단한 방법이 있습니까?
- 2008 c C에서 열거형의 변수를 문자열로 사용하는 쉬운 방법은?
- 2008 c++ c++ 열거형을 문자열에 쉽게 매핑하는 방법
- 2008 c++ 무언가를 C 식별자와 문자열 둘 다 만드는 것?
- 2008 c++ C++ enum을 문자열로 변환하는 간단한 스크립트가 있습니까?
- 2009 c++ C++에서 열거형을 플래그로 사용하는 방법은 무엇입니까?
- 2011 c++ 열거형 변수를 문자열로 변환하는 방법은 무엇입니까?
- 2011 C++ 문자열 C++에 열거
- 2011 c++ 열거형 변수를 문자열로 변환하는 방법은 무엇입니까?
- 2012 c in c에서 열거형 이름을 문자열로 변환하는 방법
- 2013 c 조건부 컴파일된 열거형을 C에서 문자열화하기
많은 답변을 읽은 후에, 저는 아직 다음과 같은 답변을 찾지 못했습니다.
예
예는 종종 긴 설명보다 낫습니다.
콜리루에서 이 스니펫을 컴파일하고 실행할 수 있습니다.
(이전의 다른 예도 사용할 수 있습니다.)
#include <map>
#include <iostream>
struct MyClass
{
enum class MyEnum : char {
AAA = -8,
BBB = '8',
CCC = AAA + BBB
};
};
// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
{ MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
{ MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
{ MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
};
auto it = MyEnumStrings.find(e);
return it == MyEnumStrings.end() ? "Out of range" : it->second;
}
int main()
{
std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}
제약
- 다른 답변이나 기본 링크를 무의미하게 복제하지 마십시오.
- 확대 매크로 기반 답변을 피하거나 축소하십시오.
#define오버헤드를 최소화해야 합니다. - 설명서는 사용하지 마십시오.
enum->string지도 제작
있어서 좋습니다
- 지다
enum와) 다른 숫자로 이 0과(와) 다를 수 있습니다. - 서포트 네거티브
enum - 된 원편화를
enum - 지다
class enum(C++11) - 지다
class enum : <type>되는 것을 것<type>(C++11) - 아님) 로 변환합니다
또최한런빠예실른행에임타소는예실(행:▁at▁or른e빠▁execution▁at-에▁least▁fast)std::map좋은 생각이 아닙니다...) constexpr(C++11, 이후 C++14/17/20에서 완화)noexcept(C++11)- C++17/C++20 친근한 스니펫
는 C++ 시에 일 수 . C++ 컴파일러는 C++에 을 둔 메타 을 사용합니다.variadic template class그리고.constexpr함수...
Magic Enum 헤더 전용 라이브러리는 C++17에 대한 열거형(문자열에서 문자열로, 반복에서)에 대한 정적 반사를 제공합니다.
#include <magic_enum.hpp>
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
};
자세한 예는 홈 저장소(https://github.com/Neargye/magic_enum )를 확인하십시오.
단점은 어디에 있습니까?
합니다.__PRETTY_FUNCTION__/__FUNCSIG__), 는 >, > = 3 및 > = 9.), 이 Clang > = 5, MSVC > = 15.3 및 GCC > = 9에서
은 열값은범내있합어니다야에위거합니다▁be 범위에 있어야 합니다.[MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].
으로 기적으로
MAGIC_ENUM_RANGE_MIN = -128,MAGIC_ENUM_RANGE_MAX = 128.를 합니다.
MAGIC_ENUM_RANGE_MIN그리고.MAGIC_ENUM_RANGE_MAX.MAGIC_ENUM_RANGE_MIN거나 합야니다같아다보다 .0음보다 합니다보다 .INT16_MIN.MAGIC_ENUM_RANGE_MAX다 커합니보다 .0이값다작합 니다야아보보다 작아야 .INT16_MAX.특정 열거형에 대해 다른 범위가 필요한 경우 필요한 열거형에 대해 특수화 enum_range를 추가합니다.
#include <magic_enum.hpp> enum number { one = 100, two = 200, three = 300 }; namespace magic_enum { template <> struct enum_range<number> { static constexpr int min = 100; static constexpr int max = 300; }; }
(better_enums 라이브러리의 접근 방식)
현재 C++에서 다음과 같은 enum to string을 수행하는 방법이 있습니다.
ENUM(Channel, char, Red = 1, Green, Blue)
// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };
용도:
Channel c = Channel::_from_string("Green"); // Channel::Green (2)
c._to_string(); // string "Green"
for (Channel c : Channel::_values())
std::cout << c << std::endl;
// And so on...
을 수행할 수 .constexpr@ecatmur의 답변에 언급된 C++17 반영 제안을 구현할 수도 있습니다.
- 매크로가 하나만 있습니다. 스트링화preprocessor )가 가능한 합니다.
#는 현재 입니다. C++의 경우는 다음과 같습니다. - 매크로는 매우 방해가 되지 않습니다. 이니셜라이저를 포함한 지속적인 선언은 기본 제공 열거형 선언에 붙여넣습니다.즉, 기본 제공 열거형에서와 동일한 구문 및 의미를 가집니다.
- 반복이 제거됩니다.
- 은 구은적어 C++11 가현자유 에 적어도 C합니다.
constexpr또한 C++98+와 함께 작동할 수 있습니다.__VA_ARGS__확실히 현대적인 C++입니다.
매크로의 정의가 어느 정도 개입되어 있기 때문에 여러 가지 방법으로 답변을 드립니다.
- 이 답변의 대부분은 StackOverflow의 공간 제약 조건에 적합하다고 생각하는 구현입니다.
- 긴 형식의 자습서에서 구현의 기본 사항을 설명하는 CodeProject 기사도 있습니다.[여기로 옮길까요? SO 답변을 하기에는 너무 많은 것 같습니다.
- 단일 헤더 파일에 매크로를 구현하는 전체 기능 라이브러리 "Better Enums"가 있습니다.또한 C++17 반영 제안서 N4113의 현재 개정판인 N4428 유형 속성 쿼리를 구현합니다.따라서 적어도 이 매크로를 통해 선언된 열거형의 경우 C++11/C++14에서 제안된 C++17 열거형 반사를 가질 수 있습니다.
이 답변을 라이브러리의 기능으로 확장하는 것은 간단합니다. 여기에는 "중요한" 항목이 포함되어 있지 않습니다.그러나 이는 상당히 지루하며 컴파일러 이동성에 대한 우려가 있습니다.
고지 사항:저는 CodeProject 기사와 도서관의 저자입니다.
이 답변의 코드, 라이브러리 및 Wandbox에서 N4428 라이브 온라인 구현을 시도할 수 있습니다.라이브러리 설명서에는 N4428로 사용하는 방법에 대한 개요도 포함되어 있으며, 제안서의 열거 부분을 설명합니다.
설명.
아래 코드는 열거형과 문자열 간의 변환을 구현합니다.그러나 반복과 같은 다른 작업도 수행하도록 확장할 수 있습니다.은 이응은다열감쌉다니거을형으로 .struct을 생성할 수 있습니다.struct대신 열거형 옆에
전략은 다음과 같은 것을 생성하는 것입니다.
struct Channel {
enum _enum : char { __VA_ARGS__ };
constexpr static const Channel _values[] = { __VA_ARGS__ };
constexpr static const char * const _names[] = { #__VA_ARGS__ };
static const char* _to_string(Channel v) { /* easy */ }
constexpr static Channel _from_string(const char *s) { /* easy */ }
};
문제는 다음과 같습니다.
- 우리는 다음과 같은 것으로 끝날 것입니다.
{Red = 1, Green, Blue}값 배열의 이니셜라이저로 사용합니다. C입니다. "C++", "C++"이기 때문입니다.Red할당할 수 있는 식이 아닙니다.이는 각 상수를 유형에 캐스팅하면 해결됩니다.T연산자가 합니다.{(T)Red = 1, (T)Green, (T)Blue}. - 는 마가지로, 우는결국으로 입니다.
{"Red = 1", "Green", "Blue"}이름 배열의 이니셜라이저로 사용합니다.우리는 그것을 잘라내야 할 것입니다." = 1"컴파일 시간에 이 작업을 수행할 수 있는 좋은 방법을 모르기 때문에 실행 시간으로 연기하겠습니다. 적으로결과로._to_string그렇지 않을 것입니다constexpr,그렇지만_from_string여전히 그럴 수 있습니다constexpr왜냐하면 우리는 절단되지 않은 문자열과 비교할 때 공백과 동등한 부호를 종결자로 취급할 수 있기 때문입니다. - 의 두 가지 ▁in▁that▁▁to▁both▁macro▁macro매▁▁canpping"▁need▁the니▁"▁each▁above▁apply합필요의위매크로가"두가지의 각 요소에 다른 매크로를 적용할 수 있는 "매핑" 매크로가 필요합니다.
__VA_ARGS__이것은 꽤 표준적입니다.이 답변에는 최대 8개의 요소를 처리할 수 있는 간단한 버전이 포함되어 있습니다. - 매크로가 진정으로 자체 포함되도록 하려면 별도의 정의가 필요한 정적 데이터가 없음을 선언해야 합니다.실제로는 어레이에 특별한 처리가 필요하다는 것을 의미합니다.해결책이 : 두가지해방있습다니법이결:다▁there있.
constexprjust (그냥)const범위의 non-non-non-non-non-non-non-non-normal 배열의 정규constexpr이 는 C 코드이며 접근법을 .이 답변의 코드는 C++11을 위한 것이며 이전 접근 방식을 취합니다.CodeProject 기사는 C++98을 위한 것이며 후자를 사용합니다.
코드
#include <cstddef> // For size_t.
#include <cstring> // For strcspn, strncpy.
#include <stdexcept> // For runtime_error.
// A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
// macro(a) macro(b) macro(c) ...
// The helper macro COUNT(a, b, c, ...) expands to the number of
// arguments, and IDENTITY(x) is needed to control the order of
// expansion of __VA_ARGS__ on Visual C++ compilers.
#define MAP(macro, ...) \
IDENTITY( \
APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
(macro, __VA_ARGS__))
#define CHOOSE_MAP_START(count) MAP ## count
#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))
#define IDENTITY(x) x
#define MAP1(m, x) m(x)
#define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
#define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
#define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
#define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
#define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
#define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
#define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))
#define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
count
#define COUNT(...) \
IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))
// The type "T" mentioned above that drops assignment operations.
template <typename U>
struct ignore_assign {
constexpr explicit ignore_assign(U value) : _value(value) { }
constexpr operator U() const { return _value; }
constexpr const ignore_assign& operator =(int dummy) const
{ return *this; }
U _value;
};
// Prepends "(ignore_assign<_underlying>)" to each argument.
#define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
#define IGNORE_ASSIGN(...) \
IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))
// Stringizes each argument.
#define STRINGIZE_SINGLE(e) #e,
#define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))
// Some helpers needed for _from_string.
constexpr const char terminators[] = " =\t\r\n";
// The size of terminators includes the implicit '\0'.
constexpr bool is_terminator(char c, size_t index = 0)
{
return
index >= sizeof(terminators) ? false :
c == terminators[index] ? true :
is_terminator(c, index + 1);
}
constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
size_t index = 0)
{
return
is_terminator(untrimmed[index]) ? s[index] == '\0' :
s[index] != untrimmed[index] ? false :
matches_untrimmed(untrimmed, s, index + 1);
}
// The macro proper.
//
// There are several "simplifications" in this implementation, for the
// sake of brevity. First, we have only one viable option for declaring
// constexpr arrays: at namespace scope. This probably should be done
// two namespaces deep: one namespace that is likely to be unique for
// our little enum "library", then inside it a namespace whose name is
// based on the name of the enum to avoid collisions with other enums.
// I am using only one level of nesting.
//
// Declaring constexpr arrays inside the struct is not viable because
// they will need out-of-line definitions, which will result in
// duplicate symbols when linking. This can be solved with weak
// symbols, but that is compiler- and system-specific. It is not
// possible to declare constexpr arrays as static variables in
// constexpr functions due to the restrictions on such functions.
//
// Note that this prevents the use of this macro anywhere except at
// namespace scope. Ironically, the C++98 version of this, which can
// declare static arrays inside static member functions, is actually
// more flexible in this regard. It is shown in the CodeProject
// article.
//
// Second, for compilation performance reasons, it is best to separate
// the macro into a "parametric" portion, and the portion that depends
// on knowing __VA_ARGS__, and factor the former out into a template.
//
// Third, this code uses a default parameter in _from_string that may
// be better not exposed in the public interface.
#define ENUM(EnumName, Underlying, ...) \
namespace data_ ## EnumName { \
using _underlying = Underlying; \
enum { __VA_ARGS__ }; \
\
constexpr const size_t _size = \
IDENTITY(COUNT(__VA_ARGS__)); \
\
constexpr const _underlying _values[] = \
{ IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) }; \
\
constexpr const char * const _raw_names[] = \
{ IDENTITY(STRINGIZE(__VA_ARGS__)) }; \
} \
\
struct EnumName { \
using _underlying = Underlying; \
enum _enum : _underlying { __VA_ARGS__ }; \
\
const char * _to_string() const \
{ \
for (size_t index = 0; index < data_ ## EnumName::_size; \
++index) { \
\
if (data_ ## EnumName::_values[index] == _value) \
return _trimmed_names()[index]; \
} \
\
throw std::runtime_error("invalid value"); \
} \
\
constexpr static EnumName _from_string(const char *s, \
size_t index = 0) \
{ \
return \
index >= data_ ## EnumName::_size ? \
throw std::runtime_error("invalid identifier") : \
matches_untrimmed( \
data_ ## EnumName::_raw_names[index], s) ? \
(EnumName)(_enum)data_ ## EnumName::_values[ \
index] : \
_from_string(s, index + 1); \
} \
\
EnumName() = delete; \
constexpr EnumName(_enum value) : _value(value) { } \
constexpr operator _enum() const { return (_enum)_value; } \
\
private: \
_underlying _value; \
\
static const char * const * _trimmed_names() \
{ \
static char *the_names[data_ ## EnumName::_size]; \
static bool initialized = false; \
\
if (!initialized) { \
for (size_t index = 0; index < data_ ## EnumName::_size; \
++index) { \
\
size_t length = \
std::strcspn(data_ ## EnumName::_raw_names[index],\
terminators); \
\
the_names[index] = new char[length + 1]; \
\
std::strncpy(the_names[index], \
data_ ## EnumName::_raw_names[index], \
length); \
the_names[index][length] = '\0'; \
} \
\
initialized = true; \
} \
\
return the_names; \
} \
};
그리고.
// The code above was a "header file". This is a program that uses it.
#include <iostream>
#include "the_file_above.h"
ENUM(Channel, char, Red = 1, Green, Blue)
constexpr Channel channel = Channel::_from_string("Red");
int main()
{
std::cout << channel._to_string() << std::endl;
switch (channel) {
case Channel::Red: return 0;
case Channel::Green: return 1;
case Channel::Blue: return 2;
}
}
static_assert(sizeof(Channel) == sizeof(char), "");
위의 프로그램이 인쇄됩니다.Red자네가 예상대로초기화하지 않고는 열거형을 만들 수 없기 때문에 유형 안전의 정도가 있습니다.switch컴파일러 및 플래그에 따라 컴파일러에서 경고가 발생합니다. 또한, 로참고에 하십시오."Red"컴파일 중에 열거형으로 변환되었습니다.
위해서
C++17
C++20, Reflection Study Group(SG7)의 작업에 관심이 있을 것입니다.문구(P0194)와 이론적 근거, 설계 및 진화(P0385)를 다루는 일련의 논문이 있습니다. (각 시리즈의 최신 논문에 대한 링크가 결정됩니다.)
된 P0194r2(2016-10-15)의 구문을 합니다.reflexpr키워드:
meta::get_base_name_v<
meta::get_element_m<
meta::get_enumerators_m<reflexpr(MyEnum)>,
0>
>
예를 들어, (마투스 초클릭의 clang의 reflexpr 분기에서 채택됨):
#include <reflexpr>
#include <iostream>
enum MyEnum { AAA = 1, BBB, CCC = 99 };
int main()
{
auto name_of_MyEnum_0 =
std::meta::get_base_name_v<
std::meta::get_element_m<
std::meta::get_enumerators_m<reflexpr(MyEnum)>,
0>
>;
// prints "AAA"
std::cout << name_of_MyEnum_0 << std::endl;
}
정적 반영은 C++17(오히려 2016년 11월 이사콰에서 열린 표준 회의에서 제시된 최종 초안으로)에 도달하는 데 실패했지만 허브 서터의 여행 보고서에서 C++20에 도달할 것이라는 확신이 있습니다.
특히 Reflection 연구 그룹은 최근에 병합된 정적 반사 제안을 검토한 후 TS 또는 다음 표준에 대한 통합 정적 반사 제안을 고려하기 위해 다음 회의에서 주요 Evolution 그룹에 들어갈 준비가 되어 있음을 발견했습니다.
2011년에는 매크로 기반 솔루션을 미세 조정하는 데 주말을 보냈지만 사용하지 않게 되었습니다.
현재 절차는 Vim을 시작하고, 빈 스위치 본문에 열거자를 복사하고, 새 매크로를 시작하고, 첫 번째 열거자를 대/소문자 문장으로 변환하고, 커서를 다음 줄의 시작 부분으로 이동하고, 매크로를 중지하고, 다른 열거자에서 매크로를 실행하여 나머지 대/소문자 문장을 생성하는 것입니다.
Vim 매크로는 C++ 매크로보다 더 재미있습니다.
실제 사례:
enum class EtherType : uint16_t
{
ARP = 0x0806,
IPv4 = 0x0800,
VLAN = 0x8100,
IPv6 = 0x86DD
};
다음을 작성합니다.
std::ostream& operator<< (std::ostream& os, EtherType ethertype)
{
switch (ethertype)
{
case EtherType::ARP : return os << "ARP" ;
case EtherType::IPv4: return os << "IPv4";
case EtherType::VLAN: return os << "VLAN";
case EtherType::IPv6: return os << "IPv6";
// omit default case to trigger compiler warning for missing cases
};
return os << static_cast<std::uint16_t>(ethertype);
}
그게 제가 살아가는 방법입니다.
그러나 열거형 문자열화에 대한 네이티브 지원이 훨씬 더 나을 것입니다.저는 C++17에서 반영 작업 그룹의 결과를 보는 것이 매우 흥미롭습니다.
@sehe가 댓글에 올린 다른 방법입니다.
이것은 Yuri Finkelstein과 비슷하지만 힘이 필요하지 않습니다.저는 지도를 사용하고 있습니다. 그래서 당신은 열거나 주문에 어떤 값이든 할당할 수 있습니다.
열거형 클래스 선언:
DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
다음 코드는 자동으로 열거형 클래스와 오버로드를 만듭니다.
- std::string의 '+='
- 스트림의 경우 '<'
- 문자열로 변환하기 위해(단일 연산자는 어느 것이든 상관없지만, 저는 개인적으로 명확성을 위해 그것을 좋아하지 않습니다.)
- 열거의 수를 보다
부스트가 필요하지 않으며 필요한 모든 기능이 제공됩니다.
코드:
#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>
#define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())
std::vector<std::string> splitString(std::string str, char sep = ',') {
std::vector<std::string> vecString;
std::string item;
std::stringstream stringStream(str);
while (std::getline(stringStream, item, sep))
{
vecString.push_back(item);
}
return vecString;
}
#define DECLARE_ENUM_WITH_TYPE(E, T, ...) \
enum class E : T \
{ \
__VA_ARGS__ \
}; \
std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__)); \
std::ostream &operator<<(std::ostream &os, E enumTmp) \
{ \
os << E##MapName[static_cast<T>(enumTmp)]; \
return os; \
} \
size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); } \
std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; } \
std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } \
std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } \
std::string &operator+=(std::string &str, E enumTmp) \
{ \
str += E##MapName[static_cast<T>(enumTmp)]; \
return str; \
} \
E operator++(E &enumTmp) \
{ \
auto iter = E##MapName.find(static_cast<T>(enumTmp)); \
if (iter == E##MapName.end() || std::next(iter) == E##MapName.end()) \
iter = E##MapName.begin(); \
else \
{ \
++iter; \
} \
enumTmp = static_cast<E>(iter->first); \
return enumTmp; \
} \
bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }
#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
template <typename T>
std::map<T, std::string> generateEnumMap(std::string strMap)
{
STRING_REMOVE_CHAR(strMap, ' ');
STRING_REMOVE_CHAR(strMap, '(');
std::vector<std::string> enumTokens(splitString(strMap));
std::map<T, std::string> retMap;
T inxMap;
inxMap = 0;
for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
{
// Token: [EnumName | EnumName=EnumValue]
std::string enumName;
T enumValue;
if (iter->find('=') == std::string::npos)
{
enumName = *iter;
}
else
{
std::vector<std::string> enumNameValue(splitString(*iter, '='));
enumName = enumNameValue[0];
//inxMap = static_cast<T>(enumNameValue[1]);
if (std::is_unsigned<T>::value)
{
inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
}
else
{
inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
}
}
retMap[inxMap++] = enumName;
}
return retMap;
}
예:
DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
int main(void) {
TestEnumClass first, second;
first = TestEnumClass::FOUR;
second = TestEnumClass::TWO;
std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)
std::string strOne;
strOne = ~first;
std::cout << strOne << std::endl; // FOUR
std::string strTwo;
strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
std::cout << strTwo << std::endl; // Enum-TWOTHREE-test
std::string strThree("TestEnumClass: ");
strThree += second;
std::cout << strThree << std::endl; // TestEnumClass: TWO
std::cout << "Enum count=" << *first << std::endl;
}
You can run the code here
이 솔루션이 마음에 들지 않을지는 모르겠지만 템플릿 변수를 사용하고 템플릿 전문화를 남용하고 있기 때문에 C++14 방식입니다.
enum class MyEnum : std::uint_fast8_t {
AAA,
BBB,
CCC,
};
template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";
int main()
{
// Prints "AAA"
std::cout << MyEnumName<MyEnum::AAA> << '\n';
// Prints "Invalid MyEnum value"
std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
// Well... in fact it prints "Invalid MyEnum value" for any value
// different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.
return 0;
}
이 접근법의 가장 나쁜 점은 유지해야 하는 고통이지만 다른 유사한 접근법을 유지해야 하는 것도 고통입니다. 그렇지 않나요?
이 접근 방식의 좋은 점:
- 가변 온도 사용(C++14 기능)
- 템플릿 전문화를 통해 잘못된 값이 사용될 때 "감지"할 수 있습니다(그러나 이것이 유용할 수 있을지는 모르겠습니다).
- 깔끔해 보여요.
- 이름 조회는 컴파일 시간에 수행됩니다.
편집
신비한 사용자 673679 당신이 맞습니다; C++14 변수 템플릿 접근법은 런타임 케이스를 처리하지 않습니다, 그것을 잊어버린 것은 제 잘못입니다. :(
그러나 우리는 여전히 일부 현대적인 C++ 기능과 변수 템플릿 및 변수 템플릿 속임수를 사용하여 열거값에서 문자열로 런타임 변환을 수행할 수 있습니다.그것은 다른 것들처럼 귀찮지만 여전히 언급할 가치가 있습니다.
템플릿 별칭을 사용하여 열거형-문자열 맵에 대한 액세스를 단축합니다.
// enum_map contains pairs of enum value and value string for each enum
// this shortcut allows us to use enum_map<whatever>.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;
// This variable template will create a map for each enum type which is
// instantiated with.
template <typename ENUM>
enum_map<ENUM> enum_values{};
그런 다음, 변수 템플릿 속임수:
template <typename ENUM>
void initialize() {}
template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
enum_values<ENUM>.emplace(value, name);
initialize<ENUM>(tail ...);
}
여기서 "최고의 트릭"은 각 열거 항목의 값과 이름을 포함하는 맵에 대한 변수 템플릿을 사용하는 것입니다. 이 맵은 각 번역 단위에서 동일하고 모든 곳에서 동일한 이름을 가질 것이기 때문에 우리가 다음을 부른다면 꽤 간단하고 깔끔합니다.initialize다음과 같은 기능:
initialize
(
MyEnum::AAA, "AAA",
MyEnum::BBB, "BBB",
MyEnum::CCC, "CCC"
);
우리는 서로에게 서명하는 이름입니다.MyEnum엔트리이며 런타임에 사용할 수 있습니다.
std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';
될 수 .<<연산자:
template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
std::ostream &operator <<(std::ostream &o, const ENUM value)
{
static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
auto found = enum_values<ENUM>.find(value);
return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
}
올른경으로 합니다.operator <<이제 다음과 같은 방식으로 열거형을 사용할 수 있습니다.
std::cout << MyEnum::AAA << '\n';
이 또한 유지 관리하기가 번거롭고 개선될 수 있지만, 아이디어를 얻기를 바랍니다.
만약 당신이enum처럼 .
enum MyEnum
{
AAA = -8,
BBB = '8',
CCC = AAA + BBB
};
의 할 수 .enum파일로: 파일 이름:
AAA = -8,
BBB = '8',
CCC = AAA + BBB
그런 다음 값을 매크로로 둘러쌀 수 있습니다.
// default definition
#ifned ITEM(X,Y)
#define ITEM(X,Y)
#endif
// Items list
ITEM(AAA,-8)
ITEM(BBB,'8')
ITEM(CCC,AAA+BBB)
// clean up
#undef ITEM
는 다단는항포수있다에 있는 항목들을 할 수 .enum실행:
enum MyEnum
{
#define ITEM(X,Y) X=Y,
#include "enum_definition_file"
};
으로 이 그고마으이것대에유기생수있다습니성할능을틸에 대한 유틸리티 함수를 할 수 .enum:
std::string ToString(MyEnum value)
{
switch( value )
{
#define ITEM(X,Y) case X: return #X;
#include "enum_definition_file"
}
return "";
}
MyEnum FromString(std::string const& value)
{
static std::map<std::string,MyEnum> converter
{
#define ITEM(X,Y) { #X, X },
#include "enum_definition_file"
};
auto it = converter.find(value);
if( it != converter.end() )
return it->second;
else
throw std::runtime_error("Value is missing");
}
이 솔루션은 오래된 C++ 표준에 적용할 수 있으며 최신 C++ 요소를 사용하지 않지만 많은 노력과 유지보수 없이 많은 코드를 생성하는 데 사용할 수 있습니다.
며칠 전에도 같은 문제가 있었습니다.이상한 매크로 매직이 없으면 C++ 솔루션을 찾을 수가 없어서 간단한 스위치 케이스 문을 생성하기 위해 CMake 코드 생성기를 작성하기로 했습니다.
용도:
enum2str_generate(
PATH <path to place the files in>
CLASS_NAME <name of the class (also prefix for the files)>
FUNC_NAME <name of the (static) member function>
NAMESPACE <the class will be inside this namespace>
INCLUDES <LIST of files where the enums are defined>
ENUMS <LIST of enums to process>
BLACKLIST <LIST of constants to ignore>
USE_CONSTEXPR <whether to use constexpr or not (default: off)>
USE_C_STRINGS <whether to use c strings instead of std::string or not (default: off)>
)
이 함수는 파일 시스템에서 포함 파일을 검색하고(include_directories 명령으로 제공된 포함 디렉터리를 사용) 읽고 클래스와 함수를 생성하기 위해 정규식을 수행합니다.
참고: constexpr은 C++에서 인라인을 의미하므로 USE_CONSTEXPR 옵션을 사용하면 헤더 전용 클래스가 생성됩니다!
예:
./sk/a.h:
enum AAA : char { A1, A2 };
typedef enum {
VAL1 = 0,
VAL2 = 1,
VAL3 = 2,
VAL_FIRST = VAL1, // Ignored
VAL_LAST = VAL3, // Ignored
VAL_DUPLICATE = 1, // Ignored
VAL_STRANGE = VAL2 + 1 // Must be blacklisted
} BBB;
./CMakeLists.txt:
include_directories( ${PROJECT_SOURCE_DIR}/includes ...)
enum2str_generate(
PATH "${PROJECT_SOURCE_DIR}"
CLASS_NAME "enum2Str"
NAMESPACE "abc"
FUNC_NAME "toStr"
INCLUDES "a.h" # WITHOUT directory
ENUMS "AAA" "BBB"
BLACKLIST "VAL_STRANGE")
생성:
./enum2Str.hpp:
/*!
* \file enum2Str.hpp
* \warning This is an automatically generated file!
*/
#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP
#include <string>
#include <a.h>
namespace abc {
class enum2Str {
public:
static std::string toStr( AAA _var ) noexcept;
static std::string toStr( BBB _var ) noexcept;
};
}
#endif // ENUM2STR_HPP
./enum2Str.cpp:
/*!
* \file enum2Str.cpp
* \warning This is an automatically generated file!
*/
#include "enum2Str.hpp"
namespace abc {
/*!
* \brief Converts the enum AAA to a std::string
* \param _var The enum value to convert
* \returns _var converted to a std::string
*/
std::string enum2Str::toStr( AAA _var ) noexcept {
switch ( _var ) {
case A1: return "A1";
case A2: return "A2";
default: return "<UNKNOWN>";
}
}
/*!
* \brief Converts the enum BBB to a std::string
* \param _var The enum value to convert
* \returns _var converted to a std::string
*/
std::string enum2Str::toStr( BBB _var ) noexcept {
switch ( _var ) {
case VAL1: return "VAL1";
case VAL2: return "VAL2";
case VAL3: return "VAL3";
default: return "<UNKNOWN>";
}
}
}
업데이트:
이 스크립트는 이제 범위가 지정된 열거형(확장 클래스|module)도 지원하므로 자주 사용하는 다른 스크립트와 함께 별도의 repo로 이동했습니다. https://github.com/mensinda/cmakeBuildTools
OP의 요청에 따라, 여기에 Boost Preprocessor 및 Variadic Macro를 기반으로 한 추악한 매크로 솔루션의 분해 버전이 있습니다.
열거자 요소의 구문과 같은 간단한 목록과 특정 요소에 대한 값을 설정할 수 있습니다.
XXX_ENUM(foo,(a,b,(c,42)));
로 확장됨.
enum foo {
a,
b,
c=42
};
출력 및 변환을 수행하는 데 필요한 기능과 함께 제공됩니다.이 매크로는 오랫동안 이 근처에 있었고, 저는 이것이 가장 효율적인 방법인지 또는 그것이 적합한 방법인지 완전히 확신할 수 없지만, 그 이후로 계속 작동하고 있습니다.
완전한 코드는 Ideone과 Coliru 모두에서 작동하는 것을 볼 수 있습니다.
그것의 거대한 추함은 위에 있습니다; 만약 내가 방법을 알았다면, 당신의 눈을 보호하기 위해 그것을 스포일러 뒤에 두었을 것이지만, 마크다운은 나를 좋아하지 않습니다.
라이브러리(하나의 헤더 파일 내에서 병합됨)
#include <boost/preprocessor.hpp>
#include <string>
#include <unordered_map>
namespace xxx
{
template<class T>
struct enum_cast_adl_helper { };
template<class E>
E enum_cast( const std::string& s )
{
return do_enum_cast(s,enum_cast_adl_helper<E>());
}
template<class E>
E enum_cast( const char* cs )
{
std::string s(cs);
return enum_cast<E>(s);
}
} // namespace xxx
#define XXX_PP_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,...) N
#define XXX_PP_RSEQ_N() \
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,0
#define XXX_PP_NARG_(...) XXX_PP_ARG_N(__VA_ARGS__)
#define XXX_PP_NARG(...) XXX_PP_NARG_(__VA_ARGS__,XXX_PP_RSEQ_N())
#define XXX_TUPLE_SIZE_INTERNAL(TUPLE) XXX_PP_NARG TUPLE
#define XXX_TUPLE_CHOICE(i) \
BOOST_PP_APPLY( \
BOOST_PP_TUPLE_ELEM( \
25, i, ( \
(0), (1), (2), (3), (4), (5), (6), (7), (8), \
(9), (10), (11), (12), (13), (14), (15), (16), \
(17), (18), (19), (20), (21), (22), (23), (24) \
) ) )
#define BOOST_PP_BOOL_00 BOOST_PP_BOOL_0
#define BOOST_PP_BOOL_01 BOOST_PP_BOOL_1
#define BOOST_PP_BOOL_02 BOOST_PP_BOOL_2
#define BOOST_PP_BOOL_03 BOOST_PP_BOOL_3
#define BOOST_PP_BOOL_04 BOOST_PP_BOOL_4
#define BOOST_PP_BOOL_05 BOOST_PP_BOOL_5
#define BOOST_PP_BOOL_06 BOOST_PP_BOOL_6
#define BOOST_PP_BOOL_07 BOOST_PP_BOOL_7
#define BOOST_PP_BOOL_08 BOOST_PP_BOOL_8
#define BOOST_PP_BOOL_09 BOOST_PP_BOOL_9
#define BOOST_PP_BOOL_010 BOOST_PP_BOOL_10
#define BOOST_PP_BOOL_011 BOOST_PP_BOOL_11
#define BOOST_PP_BOOL_012 BOOST_PP_BOOL_12
#define BOOST_PP_BOOL_013 BOOST_PP_BOOL_13
#define BOOST_PP_BOOL_014 BOOST_PP_BOOL_14
#define BOOST_PP_BOOL_015 BOOST_PP_BOOL_15
#define BOOST_PP_BOOL_016 BOOST_PP_BOOL_16
#define BOOST_PP_BOOL_017 BOOST_PP_BOOL_17
#define BOOST_PP_BOOL_018 BOOST_PP_BOOL_18
#define BOOST_PP_BOOL_019 BOOST_PP_BOOL_19
#define BOOST_PP_BOOL_020 BOOST_PP_BOOL_20
#define BOOST_PP_BOOL_021 BOOST_PP_BOOL_21
#define BOOST_PP_BOOL_022 BOOST_PP_BOOL_22
#define BOOST_PP_BOOL_023 BOOST_PP_BOOL_23
#define BOOST_PP_BOOL_024 BOOST_PP_BOOL_24
#define BOOST_PP_BOOL_025 BOOST_PP_BOOL_25
#define BOOST_PP_BOOL_026 BOOST_PP_BOOL_26
#define BOOST_PP_BOOL_027 BOOST_PP_BOOL_27
#define BOOST_PP_BOOL_028 BOOST_PP_BOOL_28
#define BOOST_PP_BOOL_029 BOOST_PP_BOOL_29
#define BOOST_PP_BOOL_030 BOOST_PP_BOOL_30
#define BOOST_PP_BOOL_031 BOOST_PP_BOOL_31
#define BOOST_PP_BOOL_032 BOOST_PP_BOOL_32
#define BOOST_PP_BOOL_033 BOOST_PP_BOOL_33
#define BOOST_PP_BOOL_034 BOOST_PP_BOOL_34
#define BOOST_PP_BOOL_035 BOOST_PP_BOOL_35
#define BOOST_PP_BOOL_036 BOOST_PP_BOOL_36
#define BOOST_PP_BOOL_037 BOOST_PP_BOOL_37
#define BOOST_PP_BOOL_038 BOOST_PP_BOOL_38
#define BOOST_PP_BOOL_039 BOOST_PP_BOOL_39
#define BOOST_PP_BOOL_040 BOOST_PP_BOOL_40
#define BOOST_PP_BOOL_041 BOOST_PP_BOOL_41
#define BOOST_PP_BOOL_042 BOOST_PP_BOOL_42
#define BOOST_PP_BOOL_043 BOOST_PP_BOOL_43
#define BOOST_PP_BOOL_044 BOOST_PP_BOOL_44
#define BOOST_PP_BOOL_045 BOOST_PP_BOOL_45
#define BOOST_PP_BOOL_046 BOOST_PP_BOOL_46
#define BOOST_PP_BOOL_047 BOOST_PP_BOOL_47
#define BOOST_PP_BOOL_048 BOOST_PP_BOOL_48
#define BOOST_PP_BOOL_049 BOOST_PP_BOOL_49
#define BOOST_PP_BOOL_050 BOOST_PP_BOOL_50
#define BOOST_PP_BOOL_051 BOOST_PP_BOOL_51
#define BOOST_PP_BOOL_052 BOOST_PP_BOOL_52
#define BOOST_PP_BOOL_053 BOOST_PP_BOOL_53
#define BOOST_PP_BOOL_054 BOOST_PP_BOOL_54
#define BOOST_PP_BOOL_055 BOOST_PP_BOOL_55
#define BOOST_PP_BOOL_056 BOOST_PP_BOOL_56
#define BOOST_PP_BOOL_057 BOOST_PP_BOOL_57
#define BOOST_PP_BOOL_058 BOOST_PP_BOOL_58
#define BOOST_PP_BOOL_059 BOOST_PP_BOOL_59
#define BOOST_PP_BOOL_060 BOOST_PP_BOOL_60
#define BOOST_PP_BOOL_061 BOOST_PP_BOOL_61
#define BOOST_PP_BOOL_062 BOOST_PP_BOOL_62
#define BOOST_PP_BOOL_063 BOOST_PP_BOOL_63
#define BOOST_PP_DEC_00 BOOST_PP_DEC_0
#define BOOST_PP_DEC_01 BOOST_PP_DEC_1
#define BOOST_PP_DEC_02 BOOST_PP_DEC_2
#define BOOST_PP_DEC_03 BOOST_PP_DEC_3
#define BOOST_PP_DEC_04 BOOST_PP_DEC_4
#define BOOST_PP_DEC_05 BOOST_PP_DEC_5
#define BOOST_PP_DEC_06 BOOST_PP_DEC_6
#define BOOST_PP_DEC_07 BOOST_PP_DEC_7
#define BOOST_PP_DEC_08 BOOST_PP_DEC_8
#define BOOST_PP_DEC_09 BOOST_PP_DEC_9
#define BOOST_PP_DEC_010 BOOST_PP_DEC_10
#define BOOST_PP_DEC_011 BOOST_PP_DEC_11
#define BOOST_PP_DEC_012 BOOST_PP_DEC_12
#define BOOST_PP_DEC_013 BOOST_PP_DEC_13
#define BOOST_PP_DEC_014 BOOST_PP_DEC_14
#define BOOST_PP_DEC_015 BOOST_PP_DEC_15
#define BOOST_PP_DEC_016 BOOST_PP_DEC_16
#define BOOST_PP_DEC_017 BOOST_PP_DEC_17
#define BOOST_PP_DEC_018 BOOST_PP_DEC_18
#define BOOST_PP_DEC_019 BOOST_PP_DEC_19
#define BOOST_PP_DEC_020 BOOST_PP_DEC_20
#define BOOST_PP_DEC_021 BOOST_PP_DEC_21
#define BOOST_PP_DEC_022 BOOST_PP_DEC_22
#define BOOST_PP_DEC_023 BOOST_PP_DEC_23
#define BOOST_PP_DEC_024 BOOST_PP_DEC_24
#define BOOST_PP_DEC_025 BOOST_PP_DEC_25
#define BOOST_PP_DEC_026 BOOST_PP_DEC_26
#define BOOST_PP_DEC_027 BOOST_PP_DEC_27
#define BOOST_PP_DEC_028 BOOST_PP_DEC_28
#define BOOST_PP_DEC_029 BOOST_PP_DEC_29
#define BOOST_PP_DEC_030 BOOST_PP_DEC_30
#define BOOST_PP_DEC_031 BOOST_PP_DEC_31
#define BOOST_PP_DEC_032 BOOST_PP_DEC_32
#define BOOST_PP_DEC_033 BOOST_PP_DEC_33
#define BOOST_PP_DEC_034 BOOST_PP_DEC_34
#define BOOST_PP_DEC_035 BOOST_PP_DEC_35
#define BOOST_PP_DEC_036 BOOST_PP_DEC_36
#define BOOST_PP_DEC_037 BOOST_PP_DEC_37
#define BOOST_PP_DEC_038 BOOST_PP_DEC_38
#define BOOST_PP_DEC_039 BOOST_PP_DEC_39
#define BOOST_PP_DEC_040 BOOST_PP_DEC_40
#define BOOST_PP_DEC_041 BOOST_PP_DEC_41
#define BOOST_PP_DEC_042 BOOST_PP_DEC_42
#define BOOST_PP_DEC_043 BOOST_PP_DEC_43
#define BOOST_PP_DEC_044 BOOST_PP_DEC_44
#define BOOST_PP_DEC_045 BOOST_PP_DEC_45
#define BOOST_PP_DEC_046 BOOST_PP_DEC_46
#define BOOST_PP_DEC_047 BOOST_PP_DEC_47
#define BOOST_PP_DEC_048 BOOST_PP_DEC_48
#define BOOST_PP_DEC_049 BOOST_PP_DEC_49
#define BOOST_PP_DEC_050 BOOST_PP_DEC_50
#define BOOST_PP_DEC_051 BOOST_PP_DEC_51
#define BOOST_PP_DEC_052 BOOST_PP_DEC_52
#define BOOST_PP_DEC_053 BOOST_PP_DEC_53
#define BOOST_PP_DEC_054 BOOST_PP_DEC_54
#define BOOST_PP_DEC_055 BOOST_PP_DEC_55
#define BOOST_PP_DEC_056 BOOST_PP_DEC_56
#define BOOST_PP_DEC_057 BOOST_PP_DEC_57
#define BOOST_PP_DEC_058 BOOST_PP_DEC_58
#define BOOST_PP_DEC_059 BOOST_PP_DEC_59
#define BOOST_PP_DEC_060 BOOST_PP_DEC_60
#define BOOST_PP_DEC_061 BOOST_PP_DEC_61
#define BOOST_PP_DEC_062 BOOST_PP_DEC_62
#define BOOST_PP_DEC_063 BOOST_PP_DEC_63
#define XXX_TO_NUMx(x) 0 ## x
#define XXX_TO_NUM(x) BOOST_PP_ADD(0,XXX_TO_NUMx(x))
#define XXX_STRINGIZEX(x) # x
#define XXX_VSTRINGIZE_SINGLE(a,b,x) XXX_STRINGIZE(x)
#define XXX_VSTRINGIZE_TUPLE(tpl) XXX_TUPLE_FOR_EACH(XXX_VSTRINGIZE_SINGLE,,tpl)
#define XXX_TUPLE_SIZE(TUPLE) XXX_TO_NUM(XXX_TUPLE_CHOICE(XXX_TUPLE_SIZE_INTERNAL(TUPLE)))
#define XXX_TUPLE_FOR_EACH(MACRO,DATA,TUPLE) BOOST_PP_LIST_FOR_EACH(MACRO,DATA,BOOST_PP_TUPLE_TO_LIST(XXX_TUPLE_SIZE(TUPLE),TUPLE))
#define XXX_STRINGIZE(x) XXX_STRINGIZEX(x)
#define XXX_VSTRINGIZE(...) XXX_VSTRINGIZE_TUPLE((__VA_ARGS__))
#define XXX_CAST_TO_VOID_ELEMENT(r,data,elem) (void)(elem);
#define XXX_CAST_TO_VOID_INTERNAL(TUPLE) XXX_TUPLE_FOR_EACH(XXX_CAST_TO_VOID_ELEMENT,,TUPLE)
#define XXX_CAST_TO_VOID(...) XXX_CAST_TO_VOID_INTERNAL((__VA_ARGS__))
#define XXX_ENUM_EXTRACT_SP(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en) = BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),1,en)
#define XXX_ENUM_ELEMENT(r,data,elem) BOOST_PP_IF( XXX_TUPLE_SIZE(elem), XXX_ENUM_EXTRACT_SP(elem), elem) ,
#define XXX_ENUM_EXTRACT_ELEMENT(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en)
#define XXX_ENUM_CASE_ELEMENT(en) BOOST_PP_IF( XXX_TUPLE_SIZE(en), XXX_ENUM_EXTRACT_ELEMENT(en), en )
#define XXX_ENUM_CASE(r,data,elem) case data :: XXX_ENUM_CASE_ELEMENT(elem) : return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem));
#define XXX_ENUM_IFELSE(r,data,elem) else if( en == data :: XXX_ENUM_CASE_ELEMENT(elem)) { return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); }
#define XXX_ENUM_CASTLIST(r,data,elem) { XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_QUALIFIED_CASTLIST(r,data,elem) { #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_INTERNAL(TYPE,NAME,TUPLE) \
enum TYPE \
{ \
XXX_TUPLE_FOR_EACH(XXX_ENUM_ELEMENT,,TUPLE) \
BOOST_PP_CAT(last_enum_,NAME) \
}; \
\
inline \
const char* to_string( NAME en ) \
{ \
if(false) \
{ \
} \
XXX_TUPLE_FOR_EACH(XXX_ENUM_IFELSE,NAME,TUPLE) \
else if( en == NAME :: BOOST_PP_CAT(last_enum_,NAME) ) \
{ \
return XXX_VSTRINGIZE(NAME,::,BOOST_PP_CAT(last_enum_,NAME)); \
} \
else \
{ \
return "Invalid enum value specified for " # NAME; \
} \
} \
\
inline \
std::ostream& operator<<( std::ostream& os, const NAME& en ) \
{ \
os << to_string(en); \
return os; \
} \
\
inline \
NAME do_enum_cast( const std::string& s, const ::xxx::enum_cast_adl_helper<NAME>& ) \
{ \
static const std::unordered_map<std::string,NAME> map = \
{ \
XXX_TUPLE_FOR_EACH(XXX_ENUM_CASTLIST,NAME,TUPLE) \
XXX_TUPLE_FOR_EACH(XXX_ENUM_QUALIFIED_CASTLIST,NAME,TUPLE) \
}; \
\
auto cit = map.find(s); \
if( cit == map.end() ) \
{ \
throw std::runtime_error("Invalid value to cast to enum"); \
} \
return cit->second; \
}
#define XXX_ENUM(NAME,TUPLE) XXX_ENUM_INTERNAL(NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS(NAME,TUPLE) XXX_ENUM_INTERNAL(class NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(class NAME : TYPE,NAME,TUPLE)
#define XXX_ENUM_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(NAME : TYPE,NAME,TUPLE)
사용.
#include "xxx_enum.h" // the above lib
#include <iostream>
XXX_ENUM(foo,(a,b,(c,42)));
int main()
{
std::cout << "foo::a = " << foo::a <<'\n';
std::cout << "(int)foo::c = " << (int)foo::c <<'\n';
std::cout << "to_string(foo::b) = " << to_string(foo::b) <<'\n';
std::cout << "xxx::enum_cast<foo>(\"b\") = " << xxx::enum_cast<foo>("b") <<'\n';
}
헤기컴일파(▁within헤) 내의 헤더 )main.cpp)
> g++ --version | sed 1q
g++ (GCC) 4.9.2
> g++ -std=c++14 -pedantic -Wall -Wextra main.cpp
main.cpp:268:31: warning: extra ';' [-Wpedantic]
XXX_ENUM(foo,(a,b,(c,42)));
^
산출량
foo::a = foo::a
(int)foo::c = 42
to_string(foo::b) = foo::b
xxx::enum_cast<foo>("b") = foo::b
열거형을 생성합니다.그 목적을 위해 발전기를 쓰는 것은 약 5분의 일입니다.
자바와 파이썬의 제너레이터 코드는 C++를 포함하여 원하는 모든 언어로 이식하기 매우 쉽습니다.
또한 원하는 기능에 따라 매우 쉽게 확장할 수 있습니다.
입력 예:
First = 5
Second
Third = 7
Fourth
Fifth=11
생성된 헤더:
#include <iosfwd>
enum class Hallo
{
First = 5,
Second = 6,
Third = 7,
Fourth = 8,
Fifth = 11
};
std::ostream & operator << (std::ostream &, const Hallo&);
생성된 cpp 파일
#include <ostream>
#include "Hallo.h"
std::ostream & operator << (std::ostream &out, const Hallo&value)
{
switch(value)
{
case Hallo::First:
out << "First";
break;
case Hallo::Second:
out << "Second";
break;
case Hallo::Third:
out << "Third";
break;
case Hallo::Fourth:
out << "Fourth";
break;
case Hallo::Fifth:
out << "Fifth";
break;
default:
out << "<unknown>";
}
return out;
}
그리고 발전기는 이식과 확장을 위한 템플릿으로 매우 간결한 형태로 만들어졌습니다.이 예제 코드는 파일을 덮어쓰는 것을 피하려고 하지만 여전히 사용자의 위험을 감수하고 사용합니다.
package cppgen;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EnumGenerator
{
static void fail(String message)
{
System.err.println(message);
System.exit(1);
}
static void run(String[] args)
throws Exception
{
Pattern pattern = Pattern.compile("\\s*(\\w+)\\s*(?:=\\s*(\\d+))?\\s*", Pattern.UNICODE_CHARACTER_CLASS);
Charset charset = Charset.forName("UTF8");
String tab = " ";
if (args.length != 3)
{
fail("Required arguments: <enum name> <input file> <output dir>");
}
String enumName = args[0];
File inputFile = new File(args[1]);
if (inputFile.isFile() == false)
{
fail("Not a file: [" + inputFile.getCanonicalPath() + "]");
}
File outputDir = new File(args[2]);
if (outputDir.isDirectory() == false)
{
fail("Not a directory: [" + outputDir.getCanonicalPath() + "]");
}
File headerFile = new File(outputDir, enumName + ".h");
File codeFile = new File(outputDir, enumName + ".cpp");
for (File file : new File[] { headerFile, codeFile })
{
if (file.exists())
{
fail("Will not overwrite file [" + file.getCanonicalPath() + "]");
}
}
int nextValue = 0;
Map<String, Integer> fields = new LinkedHashMap<>();
try
(
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), charset));
)
{
while (true)
{
String line = reader.readLine();
if (line == null)
{
break;
}
if (line.trim().length() == 0)
{
continue;
}
Matcher matcher = pattern.matcher(line);
if (matcher.matches() == false)
{
fail("Syntax error: [" + line + "]");
}
String fieldName = matcher.group(1);
if (fields.containsKey(fieldName))
{
fail("Double fiend name: " + fieldName);
}
String valueString = matcher.group(2);
if (valueString != null)
{
int value = Integer.parseInt(valueString);
if (value < nextValue)
{
fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName);
}
nextValue = value;
}
fields.put(fieldName, nextValue);
++nextValue;
}
}
try
(
PrintWriter headerWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(headerFile), charset));
PrintWriter codeWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(codeFile), charset));
)
{
headerWriter.println();
headerWriter.println("#include <iosfwd>");
headerWriter.println();
headerWriter.println("enum class " + enumName);
headerWriter.println('{');
boolean first = true;
for (Entry<String, Integer> entry : fields.entrySet())
{
if (first == false)
{
headerWriter.println(",");
}
headerWriter.print(tab + entry.getKey() + " = " + entry.getValue());
first = false;
}
if (first == false)
{
headerWriter.println();
}
headerWriter.println("};");
headerWriter.println();
headerWriter.println("std::ostream & operator << (std::ostream &, const " + enumName + "&);");
headerWriter.println();
codeWriter.println();
codeWriter.println("#include <ostream>");
codeWriter.println();
codeWriter.println("#include \"" + enumName + ".h\"");
codeWriter.println();
codeWriter.println("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)");
codeWriter.println('{');
codeWriter.println(tab + "switch(value)");
codeWriter.println(tab + '{');
first = true;
for (Entry<String, Integer> entry : fields.entrySet())
{
codeWriter.println(tab + "case " + enumName + "::" + entry.getKey() + ':');
codeWriter.println(tab + tab + "out << \"" + entry.getKey() + "\";");
codeWriter.println(tab + tab + "break;");
first = false;
}
codeWriter.println(tab + "default:");
codeWriter.println(tab + tab + "out << \"<unknown>\";");
codeWriter.println(tab + '}');
codeWriter.println();
codeWriter.println(tab + "return out;");
codeWriter.println('}');
codeWriter.println();
}
}
public static void main(String[] args)
{
try
{
run(args);
}
catch(Exception exc)
{
exc.printStackTrace();
System.exit(1);
}
}
}
Python 3.5에 대한 포트는 잠재적으로 도움이 될 만큼 충분히 다르기 때문입니다.
import re
import collections
import sys
import io
import os
def fail(*args):
print(*args)
exit(1)
pattern = re.compile(r'\s*(\w+)\s*(?:=\s*(\d+))?\s*')
tab = " "
if len(sys.argv) != 4:
n=0
for arg in sys.argv:
print("arg", n, ":", arg, " / ", sys.argv[n])
n += 1
fail("Required arguments: <enum name> <input file> <output dir>")
enumName = sys.argv[1]
inputFile = sys.argv[2]
if not os.path.isfile(inputFile):
fail("Not a file: [" + os.path.abspath(inputFile) + "]")
outputDir = sys.argv[3]
if not os.path.isdir(outputDir):
fail("Not a directory: [" + os.path.abspath(outputDir) + "]")
headerFile = os.path.join(outputDir, enumName + ".h")
codeFile = os.path.join(outputDir, enumName + ".cpp")
for file in [ headerFile, codeFile ]:
if os.path.exists(file):
fail("Will not overwrite file [" + os.path.abspath(file) + "]")
nextValue = 0
fields = collections.OrderedDict()
for line in open(inputFile, 'r'):
line = line.strip()
if len(line) == 0:
continue
match = pattern.match(line)
if match == None:
fail("Syntax error: [" + line + "]")
fieldName = match.group(1)
if fieldName in fields:
fail("Double field name: " + fieldName)
valueString = match.group(2)
if valueString != None:
value = int(valueString)
if value < nextValue:
fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName)
nextValue = value
fields[fieldName] = nextValue
nextValue += 1
headerWriter = open(headerFile, 'w')
codeWriter = open(codeFile, 'w')
try:
headerWriter.write("\n")
headerWriter.write("#include <iosfwd>\n")
headerWriter.write("\n")
headerWriter.write("enum class " + enumName + "\n")
headerWriter.write("{\n")
first = True
for fieldName, fieldValue in fields.items():
if not first:
headerWriter.write(",\n")
headerWriter.write(tab + fieldName + " = " + str(fieldValue))
first = False
if not first:
headerWriter.write("\n")
headerWriter.write("};\n")
headerWriter.write("\n")
headerWriter.write("std::ostream & operator << (std::ostream &, const " + enumName + "&);\n")
headerWriter.write("\n")
codeWriter.write("\n")
codeWriter.write("#include <ostream>\n")
codeWriter.write("\n")
codeWriter.write("#include \"" + enumName + ".h\"\n")
codeWriter.write("\n")
codeWriter.write("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)\n")
codeWriter.write("{\n")
codeWriter.write(tab + "switch(value)\n")
codeWriter.write(tab + "{\n")
for fieldName in fields.keys():
codeWriter.write(tab + "case " + enumName + "::" + fieldName + ":\n")
codeWriter.write(tab + tab + "out << \"" + fieldName + "\";\n")
codeWriter.write(tab + tab + "break;\n")
codeWriter.write(tab + "default:\n")
codeWriter.write(tab + tab + "out << \"<unknown>\";\n")
codeWriter.write(tab + "}\n")
codeWriter.write("\n")
codeWriter.write(tab + "return out;\n")
codeWriter.write("}\n")
codeWriter.write("\n")
finally:
headerWriter.close()
codeWriter.close()
사용자 정의 리터럴을 사용하여 원하는 결과를 얻을 수 있습니다.
enum
{
AAA = "AAA"_h8,
BB = "BB"_h8,
};
std::cout << h8::to_string(AAA) << std::endl;
std::cout << h8::to_string(BB) << std::endl;
이렇게 하면 문자열이 정수로 채워지며, 이는 가역적입니다.여기에서 예제를 확인하십시오.
저도 이 문제로 오랫동안 좌절해왔고, 활자를 제대로 문자열로 변환하는 문제도 있었습니다.그러나 마지막 문제에 대해서는 "Can I get C++ type names in constexpref?"의 아이디어를 사용하여 변수의 유형을 표준 C++로 인쇄하는 것이 가능합니까?에서 설명한 솔루션에 놀랐습니다.이 기법을 사용하여 열거값을 문자열로 가져오는 유사한 함수를 구성할 수 있습니다.
#include <iostream>
using namespace std;
class static_string
{
const char* const p_;
const std::size_t sz_;
public:
typedef const char* const_iterator;
template <std::size_t N>
constexpr static_string(const char(&a)[N]) noexcept
: p_(a)
, sz_(N - 1)
{}
constexpr static_string(const char* p, std::size_t N) noexcept
: p_(p)
, sz_(N)
{}
constexpr const char* data() const noexcept { return p_; }
constexpr std::size_t size() const noexcept { return sz_; }
constexpr const_iterator begin() const noexcept { return p_; }
constexpr const_iterator end() const noexcept { return p_ + sz_; }
constexpr char operator[](std::size_t n) const
{
return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
}
};
inline std::ostream& operator<<(std::ostream& os, static_string const& s)
{
return os.write(s.data(), s.size());
}
/// \brief Get the name of a type
template <class T>
static_string typeName()
{
#ifdef __clang__
static_string p = __PRETTY_FUNCTION__;
return static_string(p.data() + 30, p.size() - 30 - 1);
#elif defined(_MSC_VER)
static_string p = __FUNCSIG__;
return static_string(p.data() + 37, p.size() - 37 - 7);
#endif
}
namespace details
{
template <class Enum>
struct EnumWrapper
{
template < Enum enu >
static static_string name()
{
#ifdef __clang__
static_string p = __PRETTY_FUNCTION__;
static_string enumType = typeName<Enum>();
return static_string(p.data() + 73 + enumType.size(), p.size() - 73 - enumType.size() - 1);
#elif defined(_MSC_VER)
static_string p = __FUNCSIG__;
static_string enumType = typeName<Enum>();
return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7);
#endif
}
};
}
/// \brief Get the name of an enum value
template <typename Enum, Enum enu>
static_string enumName()
{
return details::EnumWrapper<Enum>::template name<enu>();
}
enum class Color
{
Blue = 0,
Yellow = 1
};
int main()
{
std::cout << "_" << typeName<Color>() << "_" << std::endl;
std::cout << "_" << enumName<Color, Color::Blue>() << "_" << std::endl;
return 0;
}
위의 코드는 Clang에서만 테스트되었습니다(https://ideone.com/je5Quv) 및 VS2015 참조). 정수 상수를 사용하여 다른 컴파일러에 적용할 수 있어야 합니다.물론 여전히 후드 아래 매크로를 사용하지만 적어도 하나는 열거형 구현에 대한 액세스가 필요하지 않습니다.
저는 @antron에서 아이디어를 얻어 실제 열거형 클래스를 생성하는 것과 다르게 구현했습니다.
이 구현은 원래 질문에 나열된 모든 요구 사항을 충족하지만 현재 단 하나의 실제 제한 사항이 있습니다. 열거값이 제공되지 않거나 제공된 경우 0으로 시작하고 공백 없이 순차적으로 상승해야 한다는 것입니다.
이것은 본질적인 제한이 아닙니다. 단지 애드혹 열거값을 사용하지 않습니다.필요한 경우 벡터 조회를 기존 스위치/케이스 구현으로 대체할 수 있습니다.
솔루션은 인라인 변수에 일부 c++17을 사용하지만 필요한 경우 쉽게 피할 수 있습니다.또한 단순성 때문에 boost:trim을 사용합니다.
가장 중요한 것은 코드 30줄만 있으면 되고 블랙 매직 매크로는 없다는 것입니다.코드는 아래와 같습니다.헤더에 넣어 여러 컴파일 모듈에 포함되도록 되어 있습니다.
이 스레드에서 앞서 제안한 것과 동일한 방법으로 사용할 수 있습니다.
ENUM(Channel, int, Red, Green = 1, Blue)
std::out << "My name is " << Channel::Green;
//prints My name is Green
이것이 유용한지, 어떻게 더 개선할 수 있는지 알려주시기 바랍니다.
#include <boost/algorithm/string.hpp>
struct EnumSupportBase {
static std::vector<std::string> split(const std::string s, char delim) {
std::stringstream ss(s);
std::string item;
std::vector<std::string> tokens;
while (std::getline(ss, item, delim)) {
auto pos = item.find_first_of ('=');
if (pos != std::string::npos)
item.erase (pos);
boost::trim (item);
tokens.push_back(item);
}
return tokens;
}
};
#define ENUM(EnumName, Underlying, ...) \
enum class EnumName : Underlying { __VA_ARGS__, _count }; \
struct EnumName ## Support : EnumSupportBase { \
static inline std::vector<std::string> _token_names = split(#__VA_ARGS__, ','); \
static constexpr const char* get_name(EnumName enum_value) { \
int index = (int)enum_value; \
if (index >= (int)EnumName::_count || index < 0) \
return "???"; \
else \
return _token_names[index].c_str(); \
} \
}; \
inline std::ostream& operator<<(std::ostream& os, const EnumName & es) { \
return os << EnumName##Support::get_name(es); \
}
당신이 별도로 작성하는 것만 괜찮다면요..h/.cpp쿼리 가능한 각 열거형에 대해 쌍을 이루어 이 솔루션은 일반 c++ 열거형과 거의 동일한 구문 및 기능으로 작동합니다.
// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if
enum MyEnum : int ETRAITS
{
EDECL(AAA) = -8,
EDECL(BBB) = '8',
EDECL(CCC) = AAA + BBB
};
그.cpp파일은 3줄의 보일러 플레이트입니다.
// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>
사용 예:
for (MyEnum value : EnumTraits<MyEnum>::GetValues())
std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;
코드
이 솔루션에는 두 개의 소스 파일이 필요합니다.
// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
#define ETRAITS
#define EDECL(x) x
template <class ENUM>
class EnumTraits
{
public:
static const std::vector<ENUM>& GetValues()
{
return values;
}
static ENUM GetValue(const char* name)
{
auto match = valueMap.find(name);
return (match == valueMap.end() ? ENUM() : match->second);
}
static const char* GetName(ENUM value)
{
auto match = nameMap.find(value);
return (match == nameMap.end() ? nullptr : match->second);
}
public:
EnumTraits() = delete;
using vector_type = std::vector<ENUM>;
using name_map_type = std::unordered_map<ENUM, const char*>;
using value_map_type = std::unordered_map<std::string, ENUM>;
private:
static const vector_type values;
static const name_map_type nameMap;
static const value_map_type valueMap;
};
struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }
...그리고.
// EnumTraits.inl
#define ENUM_INCLUDE_MULTI
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;
#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL
설명.
이 구현은 열거형 정의 요소의 브레이스된 목록을 클래스 멤버 초기화를 위한 브레이스된 초기화자 목록으로도 사용할 수 있다는 사실을 이용합니다.
때ETRAITS는 의맥서평다니의 됩니다.EnumTraits.inl 정멤정확다니장됩의로에 대한 정적 됩니다.EnumTraits<>학급.
그EDECL매크로는 각 열거형 구성원을 초기화자 목록 값으로 변환하고, 이후 구성원 생성자에게 전달하여 열거형 정보를 채웁니다.
그EnumInitGuard클래스는 열거형 이니셜라이저 값을 사용한 다음 접도록 설계되어 순수 열거형 데이터 목록을 남깁니다.
혜택들
c++구문 -- 둘 다 동일하게 작동합니다.
enum그리고.enum class)(*표시) - 작업대상에서 합니다.
enum밑에 이 있는 은 다음과 같습니다. - 작업대상에서 합니다.
enum및된 이니셜라이저 은 다음과 같습니다. - 대량 이름 변경 작업(인텔리전스 링크 보존)
- 사전 처리기 기호 5개만(글로벌 3개)
* 와대적로으와 으로.enums는 의니셜이저로 표시됩니다.enum class은 완전 .
불이익
- 의 별필요가 합니다.
.h/.cpp가능한 각 쌍에 을 지정합니다.enum - 된 복성에따다에 따라 .
macro그리고.include - 사소한 구문 오류가 훨씬 더 큰 오류로 폭발합니다.
- 의
class또는namespace것이 아닙니다. - 컴파일 시간 초기화 없음
평.
할 때 개인 의 불만을 제기할 입니다.EnumTraits.inl그러나 확장된 매크로는 실제로 클래스 멤버를 정의하기 때문에 실제로는 문제가 되지 않습니다.
그#ifndef ENUM_INCLUDE_MULTI헤더 파일의 맨 위에 있는 블록은 매크로나 다른 것으로 축소될 수 있는 작은 성가신 것이지만, 현재 크기로 사용할 수 있을 수 있습니다.
네임스페이스 범위 열거를 선언하려면 먼저 이름 공간 범위 내에서 열거를 포워드 선언한 다음 글로벌 네임스페이스에서 정의해야 합니다.또한 동일한 열거형의 값을 사용하는 열거형 이니셜라이저는 해당 값을 완전히 검증해야 합니다.
namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
EDECL(AAA) = -8,
EDECL(BBB) = '8',
EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}
제약이 : 사 제 지 값 할 을 수 없 할 당 습 정 용 솔 루 약 있 한 이 단 간 는 매 우 ▁values ▁very ▁to ▁with 다 니 : raintenum값이지만 올바른 정규식을 사용하면 할 수 있습니다.당신은 또한 그것들을 다시 번역할 지도를 추가할 수 있습니다.enum 없이 를 평가합니다.
#include <vector>
#include <string>
#include <regex>
#include <iterator>
std::vector<std::string> split(const std::string& s,
const std::regex& delim = std::regex(",\\s*"))
{
using namespace std;
vector<string> cont;
copy(regex_token_iterator<string::const_iterator>(s.begin(), s.end(), delim, -1),
regex_token_iterator<string::const_iterator>(),
back_inserter(cont));
return cont;
}
#define EnumType(Type, ...) enum class Type { __VA_ARGS__ }
#define EnumStrings(Type, ...) static const std::vector<std::string> \
Type##Strings = split(#__VA_ARGS__);
#define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \
EnumStrings(Type, __VA_ARGS__)
사용 예:
EnumToString(MyEnum, Red, Green, Blue);
이 접근 방식이 다른 답변 중 하나에서 이미 다루어지고 있는지 확신할 수 없습니다(실제로는 그렇습니다, 아래 참조).여러 번 문제가 발생했지만 난독화된 매크로나 타사 라이브러리를 사용하지 않는 솔루션을 찾지 못했습니다.그래서 나는 나만의 난독화된 매크로 버전을 쓰기로 결정했습니다.
내가 활성화하고 싶은 것은 다음과 같은 것입니다.
enum class test1 { ONE, TWO = 13, SIX };
std::string toString(const test1& e) { ... }
int main() {
test1 x;
std::cout << toString(x) << "\n";
std::cout << toString(test1::TWO) << "\n";
std::cout << static_cast<std::underlying_type<test1>::type>(test1::TWO) << "\n";
//std::cout << toString(123);// invalid
}
인쇄될 것입니다.
ONE
TWO
13
저는 매크로를 좋아하지 않습니다.그러나 c++가 기본적으로 열거형을 문자열로 변환하는 것을 지원하지 않는 한, 일종의 코드 생성 및/또는 매크로를 사용해야 합니다(그리고 이것이 너무 빨리 발생할지 의심됩니다).X-매크로를 사용하고 있습니다.
// x_enum.h
#include <string>
#include <map>
#include <type_traits>
#define x_begin enum class x_name {
#define x_val(X) X
#define x_value(X,Y) X = Y
#define x_end };
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#define x_begin inline std::string toString(const x_name& e) { \
static std::map<x_name,std::string> names = {
#define x_val(X) { x_name::X , #X }
#define x_value(X,Y) { x_name::X , #X }
#define x_end }; return names[e]; }
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#undef x_name
#undef x_enum_def
대부분은 사용자가 포함을 통해 X-marko에 매개 변수로 전달할 기호를 정의하고 정의하지 않는 것입니다.사용법은 다음과 같습니다.
#define x_name test1
#define x_enum_def x_begin x_val(ONE) , \
x_value(TWO,13) , \
x_val(SIX) \
x_end
#include "x_enum.h"
참고로 저는 아직 기본 유형을 선택하지 않았습니다.저는 지금까지 그것이 필요하지 않았지만, 그것을 가능하게 하기 위해 코드를 수정하는 것이 간단할 것입니다.
이 글을 쓴 후에야 나는 그것이 오히려 에페리온들의 대답과 비슷하다는 것을 깨달았습니다.아마 전에 읽었을지도 모르고 영감의 주요 원천이었을지도 모릅니다.저는 제 자신의 글을 쓰기 전까지 X-macros를 이해하는 데 항상 실패했습니다 ;).
전처리기 정의를 사용하는 내 솔루션.
이 코드는 https://repl.it/ @JomaCorpFX/name of #main.cpp에서 확인할 수 있습니다.
#include <iostream>
#include <stdexcept>
#include <regex>
typedef std::string String;
using namespace std::literals::string_literals;
class Strings
{
public:
static String TrimStart(const std::string& data)
{
String s = data;
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
return s;
}
static String TrimEnd(const std::string& data)
{
String s = data;
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(),
s.end());
return s;
}
static String Trim(const std::string& data)
{
return TrimEnd(TrimStart(data));
}
static String Replace(const String& data, const String& toFind, const String& toReplace)
{
String result = data;
size_t pos = 0;
while ((pos = result.find(toFind, pos)) != String::npos)
{
result.replace(pos, toFind.length(), toReplace);
pos += toReplace.length();
pos = result.find(toFind, pos);
}
return result;
}
};
static String Nameof(const String& name)
{
std::smatch groups;
String str = Strings::Trim(name);
if (std::regex_match(str, groups, std::regex(u8R"(^&?([_a-zA-Z]\w*(->|\.|::))*([_a-zA-Z]\w*)$)")))
{
if (groups.size() == 4)
{
return groups[3];
}
}
throw std::invalid_argument(Strings::Replace(u8R"(nameof(#). Invalid identifier "#".)", u8"#", name));
}
#define nameof(name) Nameof(u8## #name ## s)
#define cnameof(name) Nameof(u8## #name ## s).c_str()
enum TokenType {
COMMA,
PERIOD,
Q_MARK
};
struct MyClass
{
enum class MyEnum : char {
AAA = -8,
BBB = '8',
CCC = AAA + BBB
};
};
int main() {
String greetings = u8"Hello"s;
std::cout << nameof(COMMA) << std::endl;
std::cout << nameof(TokenType::PERIOD) << std::endl;
std::cout << nameof(TokenType::Q_MARK) << std::endl;
std::cout << nameof(int) << std::endl;
std::cout << nameof(std::string) << std::endl;
std::cout << nameof(Strings) << std::endl;
std::cout << nameof(String) << std::endl;
std::cout << nameof(greetings) << std::endl;
std::cout << nameof(&greetings) << std::endl;
std::cout << nameof(greetings.c_str) << std::endl;
std::cout << nameof(std::string::npos) << std::endl;
std::cout << nameof(MyClass::MyEnum::AAA) << std::endl;
std::cout << nameof(MyClass::MyEnum::BBB) << std::endl;
std::cout << nameof(MyClass::MyEnum::CCC) << std::endl;
std::cin.get();
return 0;
}
산출량
COMMA
PERIOD
Q_MARK
int
string
Strings
String
greetings
greetings
c_str
npos
AAA
BBB
CCC
클랑
비주얼 C++
다음 솔루션은 다음을 기반으로 합니다.std::array<std::string,N>소정의 열거에 대하여
위해서enumstd::string 리가그열냥던수질있변환는거를우▁to변▁enum환▁the▁cast있▁convers▁just에 열거형을 캐스팅할 수 있습니다.size_t배열에서 문자열을 찾습니다.작업은 O(1)이며 힙 할당이 필요하지 않습니다.
#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <string>
#include <array>
#include <iostream>
#define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem)
// ENUM
// ============================================================================
#define ENUM(X, SEQ) \
struct X { \
enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \
static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \
return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \
} \
static std::string to_string(Enum e) { \
auto a = array_of_strings(); \
return a[static_cast<size_t>(e)]; \
} \
}
위해서std::stringenum를 환에서어대에선해검형수색어행고인합다니캐스야해트를덱스변레이하을레로 캐스팅해야 합니다.enum.
사용 예를 들어 http://coliru.stacked-crooked.com/a/e4212f93bee65076 을 사용해 보십시오.
편집: 사용자 정의 Enum을 클래스 내에서 사용할 수 있도록 내 솔루션을 재작업했습니다.
클래스/구조체 내에서 열거형(공개 구성원이 있는 구조체 기본값)과 오버로드된 연산자를 사용하는 솔루션:
struct Color
{
enum Enum { RED, GREEN, BLUE };
Enum e;
Color() {}
Color(Enum e) : e(e) {}
Color operator=(Enum o) { e = o; return *this; }
Color operator=(Color o) { e = o.e; return *this; }
bool operator==(Enum o) { return e == o; }
bool operator==(Color o) { return e == o.e; }
operator Enum() const { return e; }
std::string toString() const
{
switch (e)
{
case Color::RED:
return "red";
case Color::GREEN:
return "green";
case Color::BLUE:
return "blue";
default:
return "unknown";
}
}
};
외부에서 보면 클래스 열거형과 거의 동일하게 보입니다.
Color red;
red = Color::RED;
Color blue = Color::BLUE;
cout << red.toString() << " " << Color::GREEN << " " << blue << endl;
그러면 "빨간색 12"가 출력됩니다.파란색 출력을 문자열로 만들기 위해 <<을(를) 오버로드할 수 있지만 색상::에는 작동하지 않습니다.색상으로 자동 변환되지 않으므로 녹색입니다.
Enum으로 암시적 변환(지정된 int 또는 type으로 암시적 변환)하는 목적은 다음과 같습니다.
Color color;
switch (color) ...
이것은 효과가 있지만, 이 또한 효과가 있다는 것을 의미합니다.
int i = color;
열거형 클래스를 사용하면 컴파일할 수 없습니다.열거형과 정수를 사용하는 두 함수를 오버로드하거나 암시적 변환을 제거하는 경우 주의해야 합니다.
또 다른 솔루션은 실제 열거 클래스와 정적 멤버를 사용하는 것입니다.
struct Color
{
enum class Enum { RED, GREEN, BLUE };
static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;
//same as previous...
};
더 많은 공간이 필요하고 만드는 데 시간이 더 오래 걸리지만 암묵적인 int 변환에 대한 컴파일 오류가 발생합니다.그거 때문에 이거 쓸 거예요.
물론 오버헤드가 있지만, 제가 본 다른 코드보다 단순하고 더 좋아 보입니다.클래스 내에서 모두 범위를 지정할 수 있는 기능을 추가할 수도 있습니다.
편집: 이 기능은 작동하며 대부분 실행 전에 컴파일할 수 있습니다.
class Color
{
public:
enum class Enum { RED, GREEN, BLUE };
static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;
constexpr Color() : e(Enum::RED) {}
constexpr Color(Enum e) : e(e) {}
constexpr bool operator==(Enum o) const { return e == o; }
constexpr bool operator==(Color o) const { return e == o.e; }
constexpr operator Enum() const { return e; }
Color& operator=(Enum o) { const_cast<Enum>(this->e) = o; return *this; }
Color& operator=(Color o) { const_cast<Enum>(this->e) = o.e; return *this; }
std::string toString() const
{
switch (e)
{
case Enum::RED:
return "red";
case Enum::GREEN:
return "green";
case Enum::BLUE:
return "blue";
default:
return "unknown";
}
}
private:
const Enum e;
};
이 요지는 C++ 변수 템플릿을 기반으로 한 간단한 매핑을 제공합니다.
다음은 C++17 형식 기반 맵의 단순화된 버전입니다.
#include <cstring> // http://stackoverflow.com/q/24520781
template<typename KeyValue, typename ... RestOfKeyValues>
struct map {
static constexpr typename KeyValue::key_t get(const char* val) noexcept {
if constexpr (sizeof...(RestOfKeyValues)==0) // C++17 if constexpr
return KeyValue::key; // Returns last element
else {
static_assert(KeyValue::val != nullptr,
"Only last element may have null name");
return strcmp(val, KeyValue::val())
? map<RestOfKeyValues...>::get(val) : KeyValue::key;
}
}
static constexpr const char* get(typename KeyValue::key_t key) noexcept {
if constexpr (sizeof...(RestOfKeyValues)==0)
return (KeyValue::val != nullptr) && (key == KeyValue::key)
? KeyValue::val() : "";
else
return (key == KeyValue::key)
? KeyValue::val() : map<RestOfKeyValues...>::get(key);
}
};
template<typename Enum, typename ... KeyValues>
class names {
typedef map<KeyValues...> Map;
public:
static constexpr Enum get(const char* nam) noexcept {
return Map::get(nam);
}
static constexpr const char* get(Enum key) noexcept {
return Map::get(key);
}
};
사용 예:
enum class fasion {
fancy,
classic,
sporty,
emo,
__last__ = emo,
__unknown__ = -1
};
#define NAME(s) static inline constexpr const char* s() noexcept {return #s;}
namespace name {
NAME(fancy)
NAME(classic)
NAME(sporty)
NAME(emo)
}
template<auto K, const char* (*V)()> // C++17 template<auto>
struct _ {
typedef decltype(K) key_t;
typedef decltype(V) name_t;
static constexpr key_t key = K; // enum id value
static constexpr name_t val = V; // enum id name
};
typedef names<fasion,
_<fasion::fancy, name::fancy>,
_<fasion::classic, name::classic>,
_<fasion::sporty, name::sporty>,
_<fasion::emo, name::emo>,
_<fasion::__unknown__, nullptr>
> fasion_names;
그map<KeyValues...>양방향으로 사용할 수 있습니다.
fasion_names::get(fasion::emo)fasion_names::get("emo")
이 예는 godbolt.org 에서 확인할 수 있습니다.
int main ()
{
constexpr auto str = fasion_names::get(fasion::emo);
constexpr auto fsn = fasion_names::get(str);
return (int) fsn;
}
으로부터 결과가 나온gcc-7 -std=c++1z -Ofast -S
main:
mov eax, 3
ret
편집: 아래에서 최신 버전을 확인하십시오.
위에서 언급한 바와 같이 N4113이 이 문제의 최종 해결책이지만, 그것이 나오려면 1년 이상 기다려야 할 것 같습니다.
한편, 이러한 기능을 원한다면 "단순한" 템플릿과 몇 가지 전처리 마법에 의존해야 합니다.
열거자
template<typename T>
class Enum final
{
const char* m_name;
const T m_value;
static T m_counter;
public:
Enum(const char* str, T init = m_counter) : m_name(str), m_value(init) {m_counter = (init + 1);}
const T value() const {return m_value;}
const char* name() const {return m_name;}
};
template<typename T>
T Enum<T>::m_counter = 0;
#define ENUM_TYPE(x) using Enum = Enum<x>;
#define ENUM_DECL(x,...) x(#x,##__VA_ARGS__)
#define ENUM(...) const Enum ENUM_DECL(__VA_ARGS__);
사용.
#include <iostream>
//the initialization order should be correct in all scenarios
namespace Level
{
ENUM_TYPE(std::uint8)
ENUM(OFF)
ENUM(SEVERE)
ENUM(WARNING)
ENUM(INFO, 10)
ENUM(DEBUG)
ENUM(ALL)
}
namespace Example
{
ENUM_TYPE(long)
ENUM(A)
ENUM(B)
ENUM(C, 20)
ENUM(D)
ENUM(E)
ENUM(F)
}
int main(int argc, char** argv)
{
Level::Enum lvl = Level::WARNING;
Example::Enum ex = Example::C;
std::cout << lvl.value() << std::endl; //2
std::cout << ex.value() << std::endl; //20
}
간단한 설명
Enum<T>::m_counter각 네임스페이스 선언 내에서 0으로 설정됩니다.
(기준에서 ^^이 동작^^이 언급된 위치를 누가 알려주시겠습니까?)
전처리기 마법은 열거자 선언을 자동화합니다.
단점들
- 진짜가 아닙니다
enumtype, int , 따서 int 승할수없유로 승격할 수 . - 스위치 케이스에 사용할 수 없습니다.
대체 해결책
이것은 라인 번호 지정을 희생하지만(실제는 그렇지 않습니다) 스위치 케이스에 사용할 수 있습니다.
#define ENUM_TYPE(x) using type = Enum<x>
#define ENUM(x) constexpr type x{__LINE__,#x}
template<typename T>
struct Enum final
{
const T value;
const char* name;
constexpr operator const T() const noexcept {return value;}
constexpr const char* operator&() const noexcept {return name;}
};
에라타
#line 0과 합니다.-pedanticGCC와 쨍그랑.
해결 방법
에서 #line 1 1을 빼세요.__LINE__.
또사용안함을 -pedantic.
그리고 우리가 그것을 하는 동안, 무슨 수를 써서라도 VC++을 피하라, 그것은 항상 컴파일러의 농담이었습니다.
사용.
#include <iostream>
namespace Level
{
ENUM_TYPE(short);
#line 0
ENUM(OFF);
ENUM(SEVERE);
ENUM(WARNING);
#line 10
ENUM(INFO);
ENUM(DEBUG);
ENUM(ALL);
#line <next line number> //restore the line numbering
};
int main(int argc, char** argv)
{
std::cout << Level::OFF << std::endl; // 0
std::cout << &Level::OFF << std::endl; // OFF
std::cout << Level::INFO << std::endl; // 10
std::cout << &Level::INFO << std::endl; // INFO
switch(/* any integer or integer-convertible type */)
{
case Level::OFF:
//...
break;
case Level::SEVERE:
//...
break;
//...
}
return 0;
}
실제 구현 및 사용
r3dVoxel - 열거형
r3dVoxel - 기록 수준
빠른 참조
#line lineno -- cppreference.com
Ponder와 같은 반사 라이브러리를 사용할 수 있습니다.
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"
(https://stackoverflow.com/a/54967187/2338477, 의 아날로그가 약간 수정되었습니다.)
최소 정의 마법과 개별 열거형 할당을 지원하는 나만의 솔루션이 있습니다.
다음은 머리글 파일입니다.
#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
저는 이 문제를 해결하기 위해 도서관을 썼습니다. 메시지를 받는 것을 제외하고는 모든 것이 컴파일 시간에 발생합니다.
용도:
사용하기DEF_MSG매크로 및 메시지 쌍을 정의하려면:
DEF_MSG(CODE_OK, "OK!")
DEF_MSG(CODE_FAIL, "Fail!")
CODE_OK이며, 는 사용할 매크로입니다."OK!"는 해당 메시지입니다.
사용하다get_message()아니면 그냥gm()메시지를 받는 방법
get_message(CODE_FAIL); // will return "Fail!"
gm(CODE_FAIL); // works exactly the same as above
사용하다MSG_NUM정의된 매크로 수를 확인합니다.자동으로 증가하므로 아무것도 할 필요가 없습니다.
미리 정의된 메시지:
MSG_OK: OK
MSG_BOTTOM: Message bottom
프로젝트: libcodemsg
라이브러리에서 추가 데이터를 생성하지 않습니다.모든 것은 컴파일 시간에 발생합니다.message_def.h은 생니다합성은그것을 합니다.enum라고 하는MSG_CODEmessage_def.c합니다. " 수를생다니"에 있는 합니다. 모든 문자열을 저장합니다.static const char* _g_messages[].
의 이경라러는하생제한다니됩록도성하나를리이브우▁one▁is▁create▁library▁to▁limited를 만드는 것으로 제한됩니다.enum값은 값에 를 들어 다음과 같습니다. 예:
MSG_CODE foo(void) {
return MSG_OK; // or something else
}
MSG_CODE ret = foo();
if (MSG_OK != ret) {
printf("%s\n", gm(ret););
}
내가 이 디자인을 좋아하는 또 다른 점은 다른 파일의 메시지 정의를 관리할 수 있다는 것입니다.
저는 이 질문에 대한 해결책이 훨씬 더 좋아 보인다는 것을 알았습니다.
#define ENUM_MAKE(TYPE, ...) \
enum class TYPE {__VA_ARGS__};\
struct Helper_ ## TYPE { \
static const String& toName(TYPE type) {\
int index = static_cast<int>(type);\
return splitStringVec()[index];}\
static const TYPE toType(const String& name){\
static std::unordered_map<String,TYPE> typeNameMap;\
if( typeNameMap.empty() )\
{\
const StringVector& ssVec = splitStringVec();\
for (size_t i = 0; i < ssVec.size(); ++i)\
typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\
}\
return typeNameMap[name];}\
static const StringVector& splitStringVec() {\
static StringVector typeNameVector;\
if(typeNameVector.empty()) \
{\
typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\
for (auto& name : typeNameVector)\
{\
name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \
name = String(#TYPE) + "::" + name;\
}\
}\
return typeNameVector;\
}\
};
using String = std::string;
using StringVector = std::vector<String>;
StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
{
StringVector ret;
// Pre-allocate some space for performance
ret.reserve(maxSplits ? maxSplits+1 : 10); // 10 is guessed capacity for most case
unsigned int numSplits = 0;
// Use STL methods
size_t start, pos;
start = 0;
do
{
pos = str.find_first_of(delims, start);
if (pos == start)
{
// Do nothing
start = pos + 1;
}
else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
{
// Copy the rest of the string
ret.push_back( str.substr(start) );
break;
}
else
{
// Copy up to delimiter
ret.push_back( str.substr(start, pos - start) );
if(preserveDelims)
{
// Sometimes there could be more than one delimiter in a row.
// Loop until we don't find any more delims
size_t delimStart = pos, delimPos;
delimPos = str.find_first_not_of(delims, delimStart);
if (delimPos == String::npos)
{
// Copy the rest of the string
ret.push_back( str.substr(delimStart) );
}
else
{
ret.push_back( str.substr(delimStart, delimPos - delimStart) );
}
}
start = pos + 1;
}
// parse up to next real data
start = str.find_first_not_of(delims, start);
++numSplits;
} while (pos != String::npos);
return ret;
}
예
ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)
MY_TEST s1 = MY_TEST::MY_1;
MY_TEST s2 = MY_TEST::MY_2;
MY_TEST s3 = MY_TEST::MY_3;
String z1 = Helper_MY_TEST::toName(s1);
String z2 = Helper_MY_TEST::toName(s2);
String z3 = Helper_MY_TEST::toName(s3);
MY_TEST q1 = Helper_MY_TEST::toType(z1);
MY_TEST q2 = Helper_MY_TEST::toType(z2);
MY_TEST q3 = Helper_MY_TEST::toType(z3);
ENUM_MAKE 매크로는 자동으로 'enum 클래스'와 'enum reflection function'을 가진 도우미 클래스를 생성합니다.
실수를 줄이기 위해 한 번에 ENUM_MAKE 하나로 모든 것을 정의합니다.
이 코드의 장점은 매크로 코드를 자세히 살펴보고 반영하기 위해 자동으로 생성되며, 이해하기 쉬운 코드입니다.'enum to string', 'string to enum' 성능 모두 알고리즘 O(1)입니다.
단점은 처음 사용할 때 열거형의 문자열 벡터 및 맵에 대한 도우미 클래스가 초기화된다는 것입니다. 그러나 원하는 경우 미리 초기화됩니다.–
제 솔루션은 매크로를 사용하지 않는 것입니다.
장점:
- 당신은 당신이 무엇을 하는지 정확히 봅니다.
- 해시 맵을 사용하여 액세스할 수 있으므로 많은 중요한 열거형에 적합합니다.
- 순서 또는 비연속 값을 고려할 필요 없음
- enum to string과 string to enum translation 모두에서 추가된 enum 값은 하나의 추가 위치에만 추가되어야 합니다.
단점:
- 모든 열거값을 텍스트로 복제해야 합니다.
- 해시 맵의 액세스는 문자열 대소문자를 고려해야 합니다.
- 값을 추가하는 것이 어려운 경우 유지 관리 - 열거형 및 직접 변환 맵 모두에 추가해야 합니다.
그래서... C++가 C# Enum을 구현하는 날까지.구문 분석 기능을 사용하면 다음과 같은 작업이 수행됩니다.
#include <unordered_map>
enum class Language
{ unknown,
Chinese,
English,
French,
German
// etc etc
};
class Enumerations
{
public:
static void fnInit(void);
static std::unordered_map <std::wstring, Language> m_Language;
static std::unordered_map <Language, std::wstring> m_invLanguage;
private:
static void fnClear();
static void fnSetValues(void);
static void fnInvertValues(void);
static bool m_init_done;
};
std::unordered_map <std::wstring, Language> Enumerations::m_Language = std::unordered_map <std::wstring, Language>();
std::unordered_map <Language, std::wstring> Enumerations::m_invLanguage = std::unordered_map <Language, std::wstring>();
void Enumerations::fnInit()
{
fnClear();
fnSetValues();
fnInvertValues();
}
void Enumerations::fnClear()
{
m_Language.clear();
m_invLanguage.clear();
}
void Enumerations::fnSetValues(void)
{
m_Language[L"unknown"] = Language::unknown;
m_Language[L"Chinese"] = Language::Chinese;
m_Language[L"English"] = Language::English;
m_Language[L"French"] = Language::French;
m_Language[L"German"] = Language::German;
// and more etc etc
}
void Enumerations::fnInvertValues(void)
{
for (auto it = m_Language.begin(); it != m_Language.end(); it++)
{
m_invLanguage[it->second] = it->first;
}
}
// usage -
//Language aLanguage = Language::English;
//wstring sLanguage = Enumerations::m_invLanguage[aLanguage];
//wstring sLanguage = L"French" ;
//Language aLanguage = Enumerations::m_Language[sLanguage];
음, 또 다른 선택지.일반적인 사용 사례는 HTTP 동사와 해당 문자열 버전 값을 사용하기 위해 상수가 필요한 경우입니다.
예:
int main () {
VERB a = VERB::GET;
VERB b = VERB::GET;
VERB c = VERB::POST;
VERB d = VERB::PUT;
VERB e = VERB::DELETE;
std::cout << a.toString() << std::endl;
std::cout << a << std::endl;
if ( a == VERB::GET ) {
std::cout << "yes" << std::endl;
}
if ( a == b ) {
std::cout << "yes" << std::endl;
}
if ( a != c ) {
std::cout << "no" << std::endl;
}
}
VERB 클래스:
// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {
private:
// private constants
enum Verb {GET_=0, POST_, PUT_, DELETE_};
// private string values
static const std::string theStrings[];
// private value
const Verb value;
const std::string text;
// private constructor
VERB (Verb v) :
value(v), text (theStrings[v])
{
// std::cout << " constructor \n";
}
public:
operator const char * () const { return text.c_str(); }
operator const std::string () const { return text; }
const std::string toString () const { return text; }
bool operator == (const VERB & other) const { return (*this).value == other.value; }
bool operator != (const VERB & other) const { return ! ( (*this) == other); }
// ---
static const VERB GET;
static const VERB POST;
static const VERB PUT;
static const VERB DELETE;
};
const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};
const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file
제 대답은 여기 있습니다.
열거형 값 이름과 이러한 인덱스를 문자열의 데큐로 동시에 가져올 수 있습니다.
이 방법은 복사하여 붙여넣기 및 편집만 하면 됩니다.
얻은 결과는 enum 클래스 유형 값이 필요할 때 size_t에서 enum 클래스 유형으로 type-casting이 필요하지만 enum 클래스를 처리하는 매우 휴대성이 높고 강력한 방법이라고 생각합니다.
enum class myenum
{
one = 0,
two,
three,
};
deque<string> ssplit(const string &_src, boost::regex &_re)
{
boost::sregex_token_iterator it(_src.begin(), _src.end(), _re, -1);
boost::sregex_token_iterator e;
deque<string> tokens;
while (it != e)
tokens.push_back(*it++);
return std::move(tokens);
}
int main()
{
regex re(",");
deque<string> tokens = ssplit("one,two,three", re);
for (auto &t : tokens) cout << t << endl;
getchar();
return 0;
}
내 3센트야, 이건 작전이 원하는 것과 완벽하게 일치하지는 않지만.여기 관련 자료가 있습니다.
namespace enums
{
template <typename T, T I, char ...Chars>
struct enums : std::integral_constant<T, I>
{
static constexpr char const chars[sizeof...(Chars)]{Chars...};
};
template <typename T, T X, typename S, std::size_t ...I>
constexpr auto make(std::index_sequence<I...>) noexcept
{
return enums<T, X, S().chars[I]...>();
}
#define ENUM(s, n) []() noexcept{\
struct S { char const (&chars)[sizeof(s)]{s}; };\
return enums::make<decltype(n), n, S>(\
std::make_index_sequence<sizeof(s)>());}()
#define ENUM_T(s, n)\
static constexpr auto s ## _tmp{ENUM(#s, n)};\
using s ## _enum_t = decltype(s ## _tmp)
template <typename T, typename ...A, std::size_t N>
inline auto map(char const (&s)[N]) noexcept
{
constexpr auto invalid(~T{});
auto r{invalid};
return
(
(
invalid == r ?
r = std::strncmp(A::chars, s, N) ? invalid : A{} :
r
),
...
);
}
}
int main()
{
ENUM_T(echo, 0);
ENUM_T(cat, 1);
ENUM_T(ls, 2);
std::cout << echo_enum_t{} << " " << echo_enum_t::chars << std::endl;
std::cout << enums::map<int, echo_enum_t, cat_enum_t, ls_enum_t>("ls")) << std::endl;
return 0;
}
따라서 정수 및/또는 문자열로 변환할 수 있는 유형을 생성합니다.
저는 이것과 함께 제안되고 있는 모든 화려한 프레임워크(매크로, 템플릿, 클래스)가 마음에 들지 않습니다. 왜냐하면 그것들을 사용하면 코드를 이해하기가 훨씬 어렵고 컴파일 시간이 증가하고 버그를 숨길 수 있기 때문입니다.일반적으로 저는 이 문제에 대한 간단한 해결책을 원합니다.100줄의 코드를 추가하는 것은 쉽지 않습니다.
원래 질문에 제시된 예제는 실제 프로덕션에서 사용하는 코드에 매우 가까웠습니다.대신, 원래 예제 검색 기능에 대한 몇 가지 작은 개선 사항을 제안하고 싶습니다.
const std::string& magic(MyClass::MyEnum e)
{
static const std::string OUT_OF_RANGE = "Out of range";
#define ENTRY(v) { MyClass::MyEnum::v, "MyClass::MyEnum::" #v }
static const std::unordered_map<MyClass::MyEnum, std::string> LOOKUP {
ENTRY(AAA),
ENTRY(BBB),
ENTRY(CCC),
};
#undef ENTRY
auto it = LOOKUP.find(e);
return ((it != LOOKUP.end()) ? it->second : OUT_OF_RANGE);
}
구체적으로:
- 내부 데이터 구조는 이제 '정적' 및 'const'입니다.이들은 변경되지 않으므로 함수에 대한 모든 호출에 대해 이들을 구성할 필요가 없으며, 그렇게 하는 것은 매우 비효율적입니다.대신 함수에 대한 첫 번째 호출에서만 생성됩니다.
- 반환 값은 이제 'conststd::string&'입니다.이 함수는 'static' 수명을 가진 이미 할당된 std::string 개체에 대한 참조만 반환하므로 반환 시 해당 개체를 복사할 필요가 없습니다.
- 이제 O(1) 액세스의 경우 std::map의 O(log(N)) 액세스 대신 맵 유형이 'std::unordered_map'입니다.
- 엔트리 매크로를 사용하면 좀 더 간결한 코드를 사용할 수 있으며 문자열 리터럴에 이름을 입력하는 동안 오타가 발생할 수 있는 잠재적인 문제를 피할 수 있습니다. (프로그래머가 잘못된 이름을 입력하면 컴파일러 오류가 발생합니다.)
언급URL : https://stackoverflow.com/questions/28828957/enum-to-string-in-modern-c11-c14-c17-and-future-c20
'programing' 카테고리의 다른 글
| Git: 두 분기의 가장 최근 공통 조상 찾기 (0) | 2023.05.09 |
|---|---|
| 이유: 적합한 이미지를 찾을 수 없습니다. (0) | 2023.05.09 |
| 새 문서를 삽입할 수 있는 경우 업데이트하는 방법은 무엇입니까? (0) | 2023.05.09 |
| "git pull"을 강제로 로컬 파일을 덮어쓰려면 어떻게 해야 합니까? (0) | 2023.05.09 |
| 열거 결합 (0) | 2023.05.09 |

