현대 C ++ 11 / C ++ 14 / C ++ 17 및 미래 C ++ 20에서 문자열로 열거


354

다른 모든 유사한 질문과 달리이 질문은 새로운 C ++ 기능 사용에 관한 것입니다.

많은 답변을 읽은 후에도 아직 찾지 못했습니다.

예는 종종 긴 설명보다 낫습니다. Coliru
에서이 스 니펫을 컴파일하고 실행할 수 있습니다 . ( 또 다른 예도 가능합니다)

#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매핑을 하지 마십시오 .

가져서 좋다

  • enum0과 다른 숫자로 시작하는 지원 값
  • 음수 enum값 지원
  • 조각난 enum값 지원
  • 지원 class enum(C ++ 11)
  • class enum : <type>허용되는 모든 지원 <type>(C ++ 11)
  • 컴파일 타임 (런타임이 아닌) 문자열로의 변환
    또는 런타임에서 적어도 빠른 실행 (예 : std::map좋은 생각은 아닙니다)
  • constexpr (C ++ 11, C ++ 14 / 17 / 20에서 완화됨)
  • noexcept (C ++ 11)
  • C ++ 17 / C ++ 20 친절한 스 니펫

한 가지 가능한 아이디어는 C ++ 컴파일러 기능을 사용하여 컴파일 타임에 메타 프로그래밍 트릭 variadic template classconstexpr함수를 기반으로 C ++ 코드를 생성하는 것입니다 ...


4
(주제 일 수도 있음)이 Qt 관련 블로그를보십시오. woboq.com/blog/reflection-in-cpp-and-qt-moc.html . C ++ 리플렉션 (제안 된 표준)을 사용하여 Qt의 moc (메타-객체-컴파일러)를 대체 할 수있는 가능성을 설명합니다.
ibre5041

10
N4113 :std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
ecatmur

1
나는 작은 전 처리기 유틸리티 라이브러리를 구현하여이 문제를 해결했다. 열거 형 값을 매크로 인수로 전달하고 전처리기를 통해 열거 형과 문자열 배열을 자동으로 생성합니다. Boost Preprocessor를 사용 하여이 작업을 수행 할 수도 있습니다.
Vittorio Romeo

2
C ++로 모든 것을 해결해야합니까? 두 줄의 코드만으로 문자열 표현을위한 코드를 자동으로 생성하는 것이 매우 쉽습니다.
Karoly Horvath

2
당신이 C 기다릴 기꺼이하지 않는 ++ (17)는, 거의 아무것도 사용할 수 없다, 잘 "C 매크로 기반의 답변 가능한 경우를 제공하지 마십시오"그것은없는 것을 따라 불량이 열거 형을 선언하는 DEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))당신을 maintaint해야하지 않는 한 매크로를 생성하고 언어에 이러한 사례를 넣는 imho는 더 강력한 템플릿 / 매크로 하이브리드 대신 다른 종류의 핵입니다. 매크로가 더 이상 사용되지 않는다고 말할 수 있도록 매크로에 유용한 매크로를 모두 사용해서는 안됩니다.
PlasmaHH

답변:


43

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을 확인 하십시오 .

단점은 어디에 있습니까?

이 라이브러리는 Clang> = 5, MSVC> = 15.3 및 GCC> = 9에서 작동 하는 컴파일러 관련 해킹 ( __PRETTY_FUNCTION__/ 기반 __FUNCSIG__)을 사용합니다.

열거 형 값은 범위 내에 있어야합니다 [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;
    };
    }

왜 범위가 제한됩니까? 어떤 종류의 재귀 깊이를 제한합니까, 아니면 어떤 종류의 컴파일 타임 선형 검색 때문입니까?
Emile Cormier

이것은 놀랍습니다. 감사합니다! 컴파일러가 constexpr std :: array를 한 번만 평가할 수있을 정도로 똑똑하다면 훨씬 효율적일 것입니다. 아주 아주 좋은.
iestyn

87

better_enums 라이브러리 의 접근 방식

현재 C ++에서 다음과 같은 문자열을 열거하는 방법이 있습니다.

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 리플렉션 제안을 구현할 수도 있습니다.

  • 매크로는 하나만 있습니다. 전 처리기 문자열 화 ( #)가 현재 C ++에서 토큰을 문자열로 변환하는 유일한 방법 이기 때문에 이것이 가능한 최소라고 생각합니다 .
  • 매크로는 눈에 거슬리지 않습니다. 이니셜 라이저를 포함한 상수 선언은 내장 열거 선언에 붙여 넣습니다. 이것은 그것들이 내장 열거 형에서와 같은 구문과 의미를 가지고 있음을 의미합니다.
  • 반복이 제거됩니다.
  • 구현은 C ++ 11 이상에서 가장 자연스럽고 유용합니다 constexpr. C ++ 98 +와 함께 작동하도록 만들 수도 있습니다 __VA_ARGS__. 확실히 현대적인 C ++입니다.

매크로의 정의는 다소 관련되어 있으므로 여러 가지 방법으로 대답하고 있습니다.

  • 이 답변의 대부분은 StackOverflow의 공간 제약 조건에 적합하다고 생각되는 구현입니다.
  • 긴 형식의 튜토리얼에서 구현의 기본 사항을 설명 하는 CodeProject 기사 도 있습니다. [ 여기로 옮겨야합니까? 너무 답이 너무 많다고 생각합니다 ].
  • 매크로를 단일 헤더 파일로 구현 하는 모든 기능을 갖춘 라이브러리 "Better Enums" 가 있습니다. 또한 C ++ 17 리플렉션 제안 N4113의 현재 개정판 인 N4428 유형 속성 쿼리를 구현 합니다. 따라서 적어도이 매크로를 통해 선언 된 열거 형의 경우 C ++ 11 / C ++ 14에서 제안 된 C ++ 17 열거 형 반영을 가질 수 있습니다.

이 답변을 라이브러리의 기능으로 확장하는 것은 간단합니다. "중요한"항목은 여기에 없습니다. 그러나 매우 지루하고 컴파일러 이식성 문제가 있습니다.

면책 조항 : 저는 CodeProject 기사와 도서관의 저자입니다.

당신은 시도 할 수 있습니다 이 답변에 코드를 , 라이브러리를 , 그리고 N4428의 구현을 Wandbox에서 라이브 온라인. 라이브러리 문서에는 또한 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 */ }
};

문제는 다음과 같습니다.

  1. 우리는 {Red = 1, Green, Blue}값 배열의 초기화 와 같은 것으로 끝날 것입니다 . Red지정 가능한 표현식 이 아니므로 유효한 C ++ 이 아닙니다. 이것은 각 상수를 유형으로 캐스팅하여 해결됩니다.T 할당 연산자가 되지만 할당은 삭제됩니다 {(T)Red = 1, (T)Green, (T)Blue}.
  2. 마찬가지로 {"Red = 1", "Green", "Blue"}names 배열의 초기화 자로 끝납니다 . 을 잘라 내야합니다 " = 1". 컴파일 타임 에이 작업을 수행하는 좋은 방법을 알지 못하므로 런타임에 이것을 연기합니다. 그 결과, _to_string하지 않습니다 constexpr만,_from_string 여전히constexpr 트림 문자열을 비교할 때 우리가 터미네이터로 표지판을 공백을 치료하고 동일 할 수 있기 때문에.
  3. 위의 두 요소에는 각 요소에 다른 매크로를 적용 할 수있는 "매핑"매크로가 필요합니다. __VA_ARGS__ . 이것은 꽤 표준입니다. 이 답변에는 최대 8 개의 요소를 처리 할 수있는 간단한 버전이 포함되어 있습니다.
  4. 매크로가 완전히 독립적 인 경우 별도의 정의가 필요한 정적 데이터를 선언하지 않아야합니다. 실제로 이것은 배열에 특별한 처리가 필요하다는 것을 의미합니다. : 두 가지 솔루션이 있습니다 constexpr(또는 const) 네임 스페이스 범위에서 배열, 또는 비 정기적 배열 constexpr정적 인라인 기능. 이 답변의 코드는 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"컴파일하는 동안 열거 형으로 변환되었습니다.


Heya @mrhthepie, 편집이 거부되어 죄송합니다. 방금 이메일을 보았습니다. 버그 픽스에 감사드립니다!
antron

이것은 대단하다. 열거 형 비트를 원한다면 이것이 효과가 있습니까? 내가 BitFlags의 열거 형을 원하는 것처럼 각각 하나의 1U양만큼 이동합니까?
user3240688

1
_trimmed_names()여기에 게시 한 코드 에 메모리 누수가있는 것으로 보이지만 true로 new char[length + 1]설정되지 않았습니다 initialized. 뭔가 빠졌습니까? github 코드에서 동일한 문제가 표시되지 않습니다.
user3240688

1
로 설정되어 true있지만 if브랜치 외부 (원래 @mrhthepie에서 메모리 누수). 내부로 이동해야합니다 ... 편집 중. 이 코드와 GH 코드를 자세히 살펴 주셔서 감사합니다.
antron

1
to_stringstring_viewnull 종료를 요구하지 않는 C ++ 17에서 a 를 반환하고 constexpr이 될 수 있습니다.
Yakk-Adam Nevraumont

74

를 들어 C ++ (17) C ++ (20), 당신은 반사 연구 그룹 (SG7)의 작업에 관심이있을 것입니다. 문구 ( P0194 )와 이론적 근거, 디자인 및 진화 ( P0385 )를 다루는 일련의 논문이 있습니다. 링크는 각 시리즈의 최신 논문으로 연결됩니다.

P0194r2 (2016-10-15)부터 구문은 제안 된 reflexpr키워드를 사용합니다 .

meta::get_base_name_v<
  meta::get_element_m<
    meta::get_enumerators_m<reflexpr(MyEnum)>,
    0>
  >

예를 들어 Matus Choclik의 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 (Issaquah에서 2016 년 11 월 표준 회의에서 발표 된 아마도 최종 초안)으로 만들지 못했지만 C ++ 20으로 만들 것이라는 확신이 있습니다. 에서 허브 셔터의 여행 보고서 :

특히 Reflection 연구 그룹은 최신 병합 정적 반사 제안을 검토하고 다음 회의에서 TS 또는 다음 표준에 대한 통합 정적 반사 제안을 고려하기 위해 주요 Evolution 그룹에 참여할 준비가되었음을 발견했습니다.


2
@antron 죄송합니다. 수정이 거부되었습니다. 제 시간에 보았 으면 승인했을 것입니다. 나는 N4428을 보지 못했기 때문에 머리를 내 주셔서 감사합니다.
ecatmur

3
통합 해 주셔서 감사합니다. 왜 거절 당했는지 궁금합니다. "정확하지는 않습니다"보일러 플레이트 이유를 볼 수 있지만 현재에는 더 정확합니다.
antron

1
감사합니다 :-) 수평 스크롤 막대를 피하기 위해 마지막 예제를 나눕니다. 무엇 유감 값하는 MyEnum::AAA의 두 번째 인수로 전달 될 수 없습니다 std::meta::get_enumerators_m/ - :
olibre

1
이러한 개념적으로 간단한 작업에는 3 가지 수준의 중첩 템플릿 인수가 필요하다는 사실이 매우 과장되어 있습니다. 구체적인 기술적 이유가 있다고 확신합니다. 그러나 이것이 최종 결과가 사용자에게 친숙하다는 의미는 아닙니다. 나는 C ++을 좋아하고 코드는 나에게 의미가있다. 그러나 다른 프로그래머의 90 %는 이와 같은 코드 때문에 매일 C ++을 피합니다. 더 간단한 내장 솔루션을 보지 못한 것에 실망했습니다.
void.pointer

2
다가오는 Reflection TS를 표준에 포함시키는 것에 대한 현재의 추정치는 C ++ 23 : herbsutter.com/2018/04/02/…
Tim Rae

25

이것은 Yuri Finkelstein과 유사합니다. 부스트가 필요하지 않습니다. 맵을 사용하여 열거 형, 순서에 값을 할당 할 수 있습니다.

열거 형 클래스 선언 :

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

다음 코드는 enum 클래스와 오버로드를 자동으로 만듭니다.

  • 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


1
이 매크로 정의 안에 줄 바꿈을 할 수 있습니까?
einpoklum

1
*열거 형의 수를 얻기 위해 과부하를 추가했습니다 ... 걱정하지 않기를 바랍니다 :-)
Peter VARGA

1
이 구현 std::mapstd::unordered_map(O (1) 인덱싱) 대신 (O (log (n)) 인덱싱)을 사용하는 이유가 있습니까?
River Tam

1
또한 inline링커에서 "다중 정의"오류를 발생시키지 않고 헤더 파일에서 열거 형을 일반과 같이 선언 할 수 있도록 메서드를 표시해야한다고 생각합니다 . (실제로 가장 깨끗하고 최상의 솔루션인지 확실하지 않습니다)
River Tam

1
(스팸이 유감이지만 오늘 댓글을 편집 할 수 없습니다) 헤더 파일에 다른 문제가 있습니다. 맵 ( E##MapName)을 열거 형에 액세스 할 수있는 컴파일 단위로 이동해야합니다. 솔루션을 만들었지 만 그다지 깨끗하지 않으므로 공유 권한을 얻어야합니다. 지금은 헤더 파일에서 사용을 지원하는 데 필요한 추가 기능없이 메소드를 인라인으로 표시 할 필요가 없다고 언급하고 있습니다.
River Tam

19

2011 년에 저는 주말에 매크로 기반 솔루션을 미세 조정하는 데 시간을 보냈으며 사용하지 않았습니다.

내 현재 절차는 Vim을 시작하고 빈 스위치 본문에 열거자를 복사하고 새 매크로를 시작하고 첫 번째 열거자를 case 문으로 변환 하고 커서를 다음 줄의 시작으로 이동 하고 매크로를 중지하고 나머지 사례를 생성하는 것입니다. 다른 열거 자에서 매크로를 실행하여 문.

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가 게시했습니다 .


1
나는 이것을 정확하게한다. 나는 보통 서라운드 vim을 사용하고 선택을 차단하지만
sehe

@sehe 흥미 롭습니다. 현재 많은 키 입력 방법이 필요하기 때문에 "서라운드"를 살펴 봐야합니다.
StackedCrooked

여기에는 매크로가 없으며 전체 매크로가 없습니다 ( .횟수 제외 ) : i.imgur.com/gY4ZhBE.gif
sehe

1
애니메이션 GIF는 귀엽지 만 언제 시작하고 끝나는 지, 그리고 얼마나 멀리 있는지 알기가 어렵습니다. ... 사실, 긁는 것은 귀엽지 않고 산만합니다. 죽여라
einpoklum

vim 에서이 블록 선택 접근법은 훌륭하지만 모두 단순히 왜 그런 것을 사용하지 :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/않습니까?
Ruslan

14

나는 당신이 이것을 좋아할지 아닌지 모르겠다.이 솔루션에 만족하지 않지만 템플릿 변수를 사용하고 템플릿 전문화를 남용하기 때문에 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;
}

이 접근법에 대한 최악의 상황은 유지하는 것이 고통이지만, 다른 유사한 공격을 유지하는 것도 고통스럽지 않습니까?

이 aproach에 대한 좋은 점 :

  • 변수 템플릿 사용 (C ++ 14 기능)
  • 템플릿 특수화를 사용하면 유효하지 않은 값이 사용될 때 "감지"할 수 있습니다 (그러나 이것이 유용 할 수 있는지 확실하지 않습니다).
  • 깔끔하게 보입니다.
  • 이름 조회는 컴파일 타임에 수행됩니다.

Live example

편집하다

신비한 user673679 당신이 맞아요; C ++ 14 변수 템플릿 접근 방식은 런타임 사례를 처리하지 않으므로 잊어 버렸습니다.

그러나 우리는 enum 값에서 문자열로 런타임 변환을 달성하기 위해 현대적인 C ++ 기능과 가변 템플릿 및 가변 템플릿 트릭을 여전히 사용할 수 있습니다 ... 다른 것만 큼 귀찮지 만 여전히 언급 할 가치가 있습니다.

enum-to-string 맵에 대한 액세스를 단축하기 위해 템플리트 별명을 사용하여 시작하십시오.

// 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';

그러나 SFINAE 및 과부하 <<연산자를 사용 하여 개선 할 수 있습니다 .

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';

이것은 또한 유지 관리가 번거롭고 개선 될 수 있지만 아이디어를 얻길 바랍니다.

Live example


이것은 매우 깔끔하게 보입니다 (특수 변수를 정의하지 않는 것이 가능합니까?). 어쩌면 뭔가를 잃어 버렸을 수도 있지만 런타임 케이스를 처리하는 방법을 전혀 보지 못하기 때문입니다.
user673679

@Paula_plus_plus : std::array다루기 힘든지도 대신에 사용해서는 안되나요 ? 그것은 ~에서 시작하는 열거 형에 대해서만 선호 될 것입니다 ... 2 ^ 10 값? 아마도 훨씬 더.
einpoklum

@einpoklum : 런타임에 얼마나 많은 요소가 있는지 확인할 수 있다면 놀랍습니다 enum. 불행히도, 우리는 할 수 없습니다. 그리고지도의 요점은 이름과 값을 연결하는 std::map것입니다.
PaperBirdMaster

@Paula_plus_plus : 이미 initialize()인수의 개수가 열거 형 값의 수인 함수를 호출하고 있으므로 컴파일 타임의 값 수를 알고 있습니다. 런타임 전용으로 알려진 특정 값만 인쇄하도록 요청합니다. 또한 그 숫자를 모르더라도 std :: vector는 거의 모든 실제 경우에 std :: map보다 빠릅니다.
einpoklum

정말 좋은 지적인 @einpoklum, 나는 그것에 대해 생각할 것입니다, 감사합니다! 내가 걱정하는 유일한 것은 std::array키-값 컨테이너가 아니기 때문에 find 메소드가 없다는 것입니다. 어쨌든 나는 그것에 대해 생각할 것이다.
PaperBirdMaster

7

당신의 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 ++ 요소를 사용하지 않지만 너무 많은 노력과 유지 관리없이 많은 코드를 생성하는 데 사용할 수 있습니다.


3
별도의 파일이 필요하지 않습니다. 이것은 본질적으로 x-macro 입니다.
HolyBlackCat

@HolyBlackCat 일부 파일에서 솔루션을 분할하면 다른 목적으로 열거 형 값을 재사용 할 수 있습니다
eferion

헤더의 열거 정의와 함께 단일 매크로에 값 목록을 넣으면 동일한 작업을 수행 할 수 있다고 말하려고합니다.
HolyBlackCat

@ HolyBlackCat 예 나는 당신을 이해하지만이 솔루션을 선호합니다. 다른 한편 으로이 솔루션은 clang 소스 코드에서 찾을 수 있으므로 문제를 해결하는 좋은 방법이라고 생각합니다
eferion

그럴 수 있지. 실제로 약간의 용도가있을 수 있기 때문에 이것을 다운 투표해서는 안됩니다. (더미 편집을 용서하면 시스템이 내 투표를 달리 잠급니다.)
HolyBlackCat

6

며칠 전에 같은 문제가있었습니다. 이상한 매크로 마법없이 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 파일을 검색하고 (include_directories 명령과 함께 제공된 include 디렉토리 사용) 파일을 읽고 클래스와 함수를 생성하기 위해 정규 표현식을 수행합니다.

참고 : constexpr은 C ++에서 인라인을 의미하므로 USE_CONSTEXPR 옵션을 사용하면 헤더 전용 클래스가 생성됩니다!

예:

./includes/ah :

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>";
   }
}
}

최신 정보:

이 스크립트는 이제 범위가 지정된 열거 (enum class | struct)도 지원하며 자주 사용하는 다른 스크립트를 사용하여 별도의 저장소로 옮겼습니다. https://github.com/mensinda/cmakeBuildTools


와! 매우 독창적이고 혁신적인 아이디어 :-) 나는 당신이 제공하기 위해 발전기를 업그레이드 할 수있는 용기가 희망 constexprnoexcept버전을 ;-) 나는 또한 단지 응시 한 당신의 GitHub의 프로젝트 ;-) 건배
olibre

1
생성기를 업데이트했습니다. 이제 함수는 항상 constexpr이고 enum입니다 : <type>이 지원됩니다. 별 주셔서 감사합니다 :)
Mense

링크가 끊어졌습니다 ... -.-
yeoman

링크가 수정되었습니다.
Mense

4

열거 형을 생성하십시오. 이러한 목적으로 생성기를 작성하는 것은 약 5 분의 작업입니다.

Java 및 Python의 생성기 코드로 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()

1
생성기를 두 언어로 공유해 주셔서 대단히 감사합니다 :-) 그러나 컴파일 타임에 생성하는 방법에 대한 아이디어가 있습니까? 예를 들어 입력 데이터가 변경 될 때 C ++ 생성 코드를 새로 고치기 위해 CMake 문을 사용하여 생성기를 변환한다고 상상할 수 있습니까? 내 꿈은 C ++ 컴파일러가 메타 프로그래밍 ( variadic template classconstexpr함수)을 사용하여 컴파일시 열거 형을 생성하도록하는 것 입니다.
olibre

Otoh, 사용자 정의 cmake 명령을 추가하기가 너무 번거로운 경우 IDE를 자동화하거나 gererator를 수동으로 호출하고 소스 제어에서 출력을 가질 수 있습니다. 너무 많지 않은 한 소스 제어에서 코드를 생성하는 것이 때로는 좋은 생각이며, 사람들은 수동으로 변경하지 않아야한다는 것을 이해하는 경우가 있습니다. '뭔가 이상한 디버깅 및 발전기에 대한 최근의 변화는 무엇인가 : 부러 졌을 수 있다는 의혹이 재
유사시

컴파일 타임에 물건을 생성하는 것에 관해서는, 구문이 매우 깨끗하고 쉬우므로 LISP에서 매우 쉽습니다. 그것은 동적으로 형식화되어 많은 구문없이 간결하고 읽을 수 있다는 사실에 도움이됩니다. C ++에서 LISP 매크로와 동등한 것은 생성하려는 AST를 설명하는 매우 복잡한 방법이 필요합니다. 그리고 C ++ 용 AST는 결코 예쁘지 않습니다 :(
yeoman

cmake 대신 Make에서 직접 사용하면 매우 쉽습니다. 찾기를 통해 각 .enum 파일에 대해 .h 및 .cpp 대상을 생성하고 이러한 대상이 해당 enum defs에 종속되도록하여 .enum def 파일이 변경되면 자동으로 다시 생성됩니다. cmake에서는 아마도 이런 종류의 것들에 마법이 가득하기 때문에 아마 훨씬 쉬울 것입니다. 그러나 나는 정기적으로 Make, ant 및 gradle을 사용하지만 Maven, cmake 및 grunt에 대한 지식은 제한적입니다. :)
yeoman

답변 주셔서 감사합니다 :-) 나는 발전기 직접 같은 C ++ 코드 내에서 열거를 감지 할 수 있다면 C의 대부분 ++ 개발자 주셔서 감사합니다 생각 enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};당신은 당신이를 감지하기 위해 발전기를 적용 할 수 있다고 생각하십니까 - D 또는 여러 라인 enum은 C에서 ++ 파일? 가장 좋은 방법은 태그를 감지 할 때만 코드를 생성하는 것 /*<Generate enum to string here>*/입니다. 그런 다음 생성기는 해당 C ++ 생성 코드를 그 자리에 작성합니다 (이전에 생성 된 코드 대체). ^ _ ^ 멋진 발전기가 무엇입니까? 건배 :-)
olibre

3

OP의 요청에 따라 Boost PreprosessorVariadic Macros를 기반으로 한 추악한 매크로 솔루션 버전이 제거되었습니다. .

열거 자 요소의 구문과 같은 간단한 목록과 특정 요소의 값 설정을 허용합니다.

XXX_ENUM(foo,(a,b,(c,42)));

~로 확장

enum foo {
    a,
    b,
    c=42
};

출력 및 일부 변환을 수행하는 데 필요한 기능과 함께. 이 매크로는 오랫동안 여기에 있었으며 가장 효율적인 방법인지 또는 적합한 방법인지는 확실하지 않지만 그 이후로는 계속 작동하고 있습니다.

완전한 코드는 IdeoneColiru 에서 실제로 볼 수 있습니다 .

그것의 거대한 추함은 위에 있습니다. 내가 어떻게했는지 알고 있다면 스포일러 뒤에 숨었을 텐데, 마크 다운은 나를 좋아하지 않습니다.

라이브러리 (하나의 단일 헤더 파일 내에 병합)

#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';
}

편집 (에서 붙여 넣기 헤더 복사 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
이 코드 블록은 블랙 매직 메타 프로그래밍의 놀라운 풍경을 통해 미친 여행입니다. 나는 main집 에 도착했을 때 실제로 안심했다 – 집, 단 집!
Quentin

방금 coliru에 링크를 추가하여 출력을 확인했습니다 (경고가 있으면 답변 내 링크를 클릭하십시오). 나는 또한 Lib / Usage로 나 have습니다. 물건 namespace xxx을 헤더 장소로 옮길 수 있습니까? 당신은 당신의 사용을 소개에 말할 수 boost/preprocessor.hpp따라서 답은 현대 C ++ 준수합니다 . 더 나은 품질을 위해 경고를 수정하고 소스 코드를 약간 정리하십시오.
olibre

@olibre : 라이브러리의 5 가지 헤더가 있다고 생각합니다. enum_cast는 또 다른 일반적인 부분에서 왔지만 매크로의 do_enum_cast가 무엇인지 알기 위해 추가하려고 생각했습니다. 경고는 main<tab>사용하지 않는 인수를 포함하여 vim 에서 나옵니다 . 나는이 코드가 실제로 청소 될 수 있다고 생각하지 않는다. 그것은 무엇을 할 수 있고 보여서는 안된다는 것을 보여주는 것이다;) 여기에서 변경하면 더 이상 프로덕션에서 사용하는 코드가 아닙니다 ... 그것은 깨지기 쉬운 것 중 하나입니다 일단 작동하면 아무도 예측할 수없는 방식으로 붕괴 될 수 있으므로 만지지 않는 것이 좋습니다.
PlasmaHH

좋아, 나는 이것이 개념 증명 으로 볼 수 있다고 본다 . 그러나 투표에 너무 많은 매크로 오버 헤드가 있습니다. 그럼에도 불구하고 공유해 주셔서 감사합니다. 건배
올리버

안녕 플라즈마. 컴파일 및 실행 출력으로 완료된 깊은 소스 코드 정리 +를 수행했습니다. 내 편집 내용을 확인하십시오 . 나는 이것이 당신에게 괜찮기를 바랍니다. 답이 더 가치가 있습니까? 그러나 매크로 오버 헤드는 여전히 끔찍합니다! 좋은 하루 되세요 :-) 건배
올리버

2

다음 솔루션은 std::array<std::string,N>주어진 열거 형을 기반으로합니다 .

를 들어 enumstd::string변환 우리는 단지에 열거 캐스팅 할 수 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을 클래스 내부에서 사용할 수 있도록 솔루션을 재 작업했습니다.


흥미로운 답변 감사합니다. 수업 내에서 매크로를 사용하려면 제안을 다시 작성하십시오. 참조 coliru.stacked-crooked.com/a/00d362eba836d04b 또한 사용하려고 constexpr하고 noexept가능하면 키워드를. 건배 :-)
olibre

질문은이 요구 사항을 지정하지 않았습니다.
FKaria

질문이 업데이트되었습니다 (예 참조). 다른 두 가지 요구 사항 : (1) 지원 유형 enum 및 (2) 값은 순서 0, 1, 2와 다를 수 있습니다.
olibre

수업 내에서 사용할 수 있도록 솔루션을 재 작업했습니다. 나는 0,1,2, ..와 다른 값을 만드는 방법을 알지 못했습니다.
FKaria

안녕하세요 FKaria. 재 작업 해 주셔서 감사합니다. 같은 클래스 내에서 여러 열거 형을 지원하고 enum class X : Type형식 을 지원하기 위해 일부 변경을 수행했습니다 . 내 기여를 검토하십시오 : coliru.stacked-crooked.com/a/b02db9190d3491a3 내 변경에 대해 어떻게 생각하십니까? 열거 형 내에서 설정된 값을 지원할 아이디어가 있습니까? enum E{A=3, B=6, C=A-B};건배 예
olibre

2

요지 는 C ++ variadic 템플릿을 기반으로 간단한 매핑을 제공합니다.

이것은 gist 의 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

1
매우 흥미로운 메타 프로그래밍 방식. 나는 (Gist 링크에 의존하지 않고) 자율적으로 답변을 약간 단순화하려고 노력했다. 간결하고 이해하기 쉽도록 마침내 많은 답변을 편집했습니다. 여전히 내 변경 사항에 동의합니까? 건배 ;-)
올리버

2

나는이 문제에 오랫동안 오랫동안 좌절했으며 유형을 문자열로 변환하는 문제가있었습니다. 그러나 마지막 문제 의 경우 표준 C ++에서 변수 유형을 인쇄 할 수 있습니까? 에서 설명한 솔루션에 놀랐습니다 . , constexpr 방식으로 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 에서만 테스트 되었지만 정수 상수로 비트를 조정하여 다른 컴파일러에 적용 할 수 있어야합니다. 물론 여전히 여전히 매크로를 사용하지만 적어도 하나는 열거 구현에 액세스 할 필요가 없습니다.


g ++ 6.3.0 및 C ++ 14에서는 실패합니다.
einpoklum

열거 형을 매크로로 감싸지 않고도 정상적으로 선언 할 수 있기 때문에 흥미 롭습니다. 나는 컴파일러 의존성과 마술 상수를 좋아하지 않지만.
zett42

2

@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

Pls는 이것이 유용한 지 그리고 그것이 어떻게 더 향상 될 수 있는지 알려주세요.


#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); \
    } 

2

.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;

암호

이 솔루션에는 2 개의 소스 파일이 필요합니다.

// 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++같은 구문
  • enumenum class(* 거의) 둘 다 동일하게 작동
  • Works에 대한 enum모든 숫자 기본 유형과 종류
  • 작동 enum, 자동 명시 적으로 타입과 값 초기화 분열
  • 대량 이름 변경 작업 (지능형 연결 유지)
  • 전 처리기 기호 5 개만 (전역 3 개)

* 반대로 , 동일한 열거 형의 다른 값을 참조 enums하는 enum class형식의 이니셜 라이저 는 해당 값이 정규화되어야

단점

  • .h/.cpp쿼리 가능 각각에 대해 별도의 쌍이 필요합니다enum
  • 복잡한에 따라 다름 macroinclude마법
  • 사소한 구문 오류가 훨씬 더 큰 오류로 폭발
  • 정의 classnamespace범위의 열거하는 것은 간단한 일은
  • 컴파일 시간 초기화 없음

코멘트

Intellisense는 열 때 개인 멤버 액세스에 대해 약간의 불만을 제기 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
}

2

이 접근법이 다른 답변 중 하나에 이미 포함되어 있는지 확실하지 않습니다 (실제로 아래 참조). 여러 번 문제가 발생하여 난독 화 된 매크로 나 타사 라이브러리를 사용하지 않는 솔루션을 찾지 못했습니다. 따라서 난독 화 된 매크로 버전을 작성하기로 결정했습니다.

내가 활성화하고 싶은 것은

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-marco에 매개 변수로 전달할 기호를 정의하고 정의하지 않습니다. 사용법은 이렇습니다

#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를 이해하지 못했습니다.).


1

클래스 / struct 내에서 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;

"red 1 2"가 출력됩니다. <<를 오버로드하여 파란색 출력을 문자열로 만들 수는 있지만 (모호함을 유발할 수는 없지만) 자동으로 Color로 변환되지 않으므로 Color :: GREEN에서는 작동하지 않습니다.

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;
};

이것은 매우 흥미 롭습니다 :-) 그러나 현재 버전은 수동으로 물건을 작성해야 함을 암시합니다 case Enum::RED: return "red";. 문제는 컴파일러에서 컴파일 시간에이 내용을 자동화하는 것입니다. 문제는 아이디어를 업데이트하지 않고 열거 형 값만 변경하거나 추가하는 것 toString()입니다. 당신이 보여요? 감사합니다
olibre

1

하나의 큰 제약 조건이있는 매우 간단한 솔루션 : 값에 사용자 정의 값을 할당 할 수는 없지만 enum올바른 정규 표현식을 사용하면 가능합니다. 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);

혁신적인 아이디어에 감사합니다. 가독성을 높이기 위해 귀하의 답변을 편집했습니다. 나는 당신이 나의 변화를 좋아하기를 바랍니다. 답을 계속 향상 시키십시오. (1) "사용 예" 섹션 을 다음과 같이 확장하십시오. auto name = MyEnumStrings["Red"];(2) 왜 사용 enum class합니까? -(3) 지원 enum class MyEnum : char { Red, Green, Blue };합니까? -(4) 기능 설명 split()-(5) 파라미터가 필요 const std::regex& delim합니까? -(6) MyEnumStrings컴파일 타임 에 생성 하는 것은 어떻습니까? => 사용할 수 있습니까 constexpr? ... 건배 :-)
올리버

이 방법이 정말 마음에 듭니다. 정말 짧고 이해하기 쉽습니다.
Anton Holmberg

1

편집 : 최신 버전은 아래를 확인하십시오.

위에서 언급 한 바와 같이 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으로 설정됩니다.
( 누군가 ^^이 행동 ^^이 표준에서 언급 된 곳을 알려 줄 수 있습니까? )
전 처리기 마술은 열거 자의 선언을 자동화합니다.

단점

  • 실제 enum유형이 아니므로 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 및 clang과 충돌합니다 .

해결 방법

에서 시작하고에서 #line 11을 빼십시오 __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-ELoggingLevel

빠른 참조

# 라인의 LINENO - cppreference.com


0

이 문제를 해결하기위한 라이브러리를 작성했습니다. 메시지를받는 것을 제외하고는 모든 것이 컴파일 시간에 발생합니다.

용법:

매크로 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_CODE; 에서 message_def.c모든 문자열을 보유하는 변수를 생성합니다 static const char* _g_messages[].

이 경우 라이브러리는 enum하나만 작성하도록 제한 됩니다. 다음과 같은 반환 값에 이상적입니다.

MSG_CODE foo(void) {
    return MSG_OK; // or something else
}

MSG_CODE ret = foo();

if (MSG_OK != ret) {
    printf("%s\n", gm(ret););
}

이 디자인이 마음에 드는 또 다른 것은 메시지 정의를 다른 파일로 관리 할 수 ​​있다는 것입니다.


이 질문에 대한 해결책 훨씬 좋아 보인다는 것을 알았습니다.


안녕 매드 윈. 당신의 아이디어에 감사드립니다. 그러나 어떻게 작동합니까? 오버 헤드는 무엇입니까? (오버 헤드가 없거나 추가 데이터를 생성합니까?). 당신의 제안은 괜찮아 보이지만, 불행히도, DEF_MSGenum값 에 대해 하나의 진술 을 사용 / 업데이트 / 유지해야 합니다 :-/ 그리고 이것이 이상적으로 우리가 멈추고 싶은 것은 ... Cheers
olibre

답장을 보내 주셔서 감사합니다, @olibre. 업데이트 된 답변을 확인하십시오. 문자열에 액세스하기 위해 함수 호출이 필요하다는 점을 제외하고는 여기에서 오버 헤드가 보이지 않습니다. DEF_MSG차종 enum은 몇 가지 제한 사항이 있지만, 밀접하게 메시지와 짝을.
Madwyn

답변에 첨부 된 설명을 주셔서 감사합니다 :-) lib는 훌륭하지만 여러 열거 형에 사용할 수 없습니다 :-/ enum class(C ++ 11) 의 지원은 어떻습니까? 런타임 constexpr을 제한 하는 데 사용할 수 있습니다 _g_messages. 메타 프로그래밍 (유형 전달 {enum-type, enum-value}) 또는 템플릿 변수 (C ++ 14)를 사용하여 여러 enum유형 (피하기 _g_messages)을 지원합니다 . 귀하의 라이브러리가 C ++ 11/14/17 요구 사항에 맞지 않는다고 생각합니다. 어떻게 생각해? 건배 ;-)
올리버

1
다음에 감사드립니다. 나는 오늘 새로운 것을 배웠다! 열거 형 클래스와 템플릿 변수가 좋아 보입니다. 나는 C의 맛이 좋았 기 때문에 내 대답은 약간 "주제에서 벗어난 것"이라고 생각합니다.
Madwyn

0
#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 기능'을 가진 도우미 클래스를 생성합니다.

실수를 줄이기 위해 모든 것이 하나의 ENUM_MAKE로만 정의됩니다.

이 코드의 장점은 이해하기 쉬운 매크로 코드를 자세히보고 반영하기 위해 자동으로 생성됩니다. 'enum to string', 'string to enum'성능은 모두 알고리즘 O (1)입니다.

단점은 처음 사용할 때 enum relection의 문자열 벡터 및 맵에 대한 도우미 클래스가 초기화되는 것입니다. 그러나 원하는 경우 사전 초기화됩니다. –


이 코드는 질문에 대답 할 수 있지만 다른 사람을 소개하지 않고 문제를 해결하는 방법과 사용 이유를 설명하는 것이 좋습니다. 코드 전용 답변은 장기적으로 유용하지 않습니다.
JAL

안녕하세요, 영어를 잘 못해서 죄송합니다.
desperado_98

ENUM_MAKE 매크로가 자동으로 'enum 클래스'와 'enum reflection 기능'을 가진 도우미 클래스를 생성합니다. / 실수를 줄이기 위해 한 번에 하나의 ENUM_MAKE로 모든 것을 정의합니다. 이 코드의 장점은 이해하기 쉬운 매크로 코드를 자세히보고 반영하기 위해 자동으로 생성됩니다. 'enum to string', 'string to enum'성능은 모두 알고리즘 O (1)입니다. 단점은 처음 사용할 때 enum relection의 문자열 벡터 및 맵에 대한 도우미 클래스가 초기화되는 것입니다. 그러나 원하는 경우 사전 초기화됩니다.
desperado_98

안녕하세요 desperado_98. 당신의 기여에 감사드립니다. 답변을 편집하고 의견 내용을 입력하십시오. 메타 프로그래밍 기법을 사용하면 컴파일러는 컴파일 타임에 예제를 계산할 수 있습니다 constexpr. 나는 기능을 의미 toName()하고 toType()컴파일시가 아닌 실행 (실행 시간) 동안 평가 될 수있다. 귀하의 답변에 C ++ 11 / 14 / 17 스타일을 채택하십시오. 건배 ;-)
올리버

또한 : 매크로는 enum class MyEnum : short { A, B, C };? 와 호환 됩니까?
olibre

0

내 솔루션은 매크로 사용이 없습니다.

장점 :

  • 당신은 정확히 당신이 무엇을 참조하십시오
  • 액세스는 해시 맵을 사용하므로 많은 가치있는 열거 형에 적합합니다.
  • 순서 또는 비 연속적인 값을 고려할 필요가 없음
  • enum에서 문자열로, string에서 enum으로 번역하는 동안 추가 된 enum 값은 한 곳에만 추가해야합니다.

단점 :

  • 모든 열거 형 값을 텍스트로 복제해야합니다.
  • 해시 맵의 액세스는 문자열 대소 문자를 고려해야합니다
  • 값을 추가하는 것이 어려운 경우 유지 관리-열거 형 및 직접 변환 맵을 모두 추가해야합니다.

그래서 ... C ++이 C # Enum.Parse 기능을 구현할 때까지 다음과 같이 붙어 있습니다.

            #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];

0

또 다른 옵션입니다. 일반적인 사용 사례는 문자열 버전 값뿐만 아니라 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;
  }

}

동사 클래스 :

// -----------------------------------------------------------
// -----------------------------------------------------------
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

1
메모리 사용량을 줄이려면 멤버를 const std::string text로 대체하면 됩니다 theStrings[v]. 그러나 질문은 C ++ 11 / C ++ 14 / C ++ 17 / C ++ 20의 기능에 대한 것입니다. 이러한 클래스를 직접 작성하지 않아도됩니다. :-/
olibre

0

내 대답은 여기에 있습니다.

문자열 값으로 열거 형 값 이름과 이러한 인덱스를 동시에 얻을 수 있습니다.

이 방법은 복사 및 붙여 넣기 및 편집이 거의 필요하지 않습니다.

얻은 결과는 enum 클래스 유형 값이 필요할 때 size_t에서 enum 클래스 유형으로 유형 캐스팅이 필요하지만 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;
}

0

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"

0

( 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


-5

간단한 스트리밍 과부하는 어떻습니까? 매크로 매직을 원하지 않으면 매핑을 유지해야하지만 원래 솔루션보다 깨끗합니다.

#include <cstdint>  // for std::uint_fast8_t
#include <array>
#include <string>
#include <iostream>

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

std::ostream& operator<<(std::ostream& str, MyEnum type)
{
    switch(type)
    {
    case MyEnum::AAA: str << "AAA"; break;
    case MyEnum::BBB: str << "BBB"; break;
    case MyEnum::CCC: str << "CCC"; break;
    default: break;
    }
    return str;
}

int main()
{
   std::cout << MyEnum::AAA <<'\n';
}

5
1) 그것은 더 많은 복제를 만듭니다 2) 그것은 스트림을 사용하도록 강요
Karoly Horvath

6
-1 @dau_sama 죄송하지만이 열거 형 대 문자열 반복 질문 의 목적은 열거 형 / 문자열 매핑의 유지 관리를 피하는 것입니다. 답변이 목적에 맞지 않다고 생각되면 답변을 삭제하십시오. 다음 답변에 행운을 빕니다;) 건배
올리버

-9

가장 쉬운 방법은?
Ada 사용 : Enumeration'Image( Value )원하는 것을 정확하게 수행합니다. C ++ 이 정말로 필요하다면 함수를 내보내보십시오.

Function To_String( Input : Enumeration ) return Interfaces.C.Strings.chars_ptr is
    ( Interfaces.C.Strings.New_String( Enumeration'Image(Input) ) )
    with Export, Convention => C;

4
이것은 어떻게 질문에 대답합니까? 이 질문은 현대 C ++에서 열거 형을 문자열로 변환하는 것을 분명히 나타냅니다.
마이클 최

1
@MichaelChoi — 물론, 작업에 적합한 도구를 사용하는 문제도 있습니다. C ++이 튜링 완료되어 모든 해결 가능한 문제를 해결할 수 있다고해서 솔루션이 빠르거나 유지 관리 가능하거나 효율적이라는 의미 는 아닙니다 . 적절한 / 원하는 기능을 가지고있는 언어를 사용하고 내보내기 이다 유효한 용액.
Shark8

3
질문의 첫 문장에서 "이 질문은 새로운 C ++ 기능 사용에 관한 것"입니다. "[C ++ 11, C ++ 14 또는 C ++ 17 새로운 기능을 사용하여 우아한 방법을 찾지 못했습니다" ". 필자는 분명히 C ++ 솔루션을 찾고있었습니다. Ada에서 해결책을 제시했기 때문에 질문에 대답하지 않았습니다. 질문 범위에 포함되지 않은 문제를 해결하기 위해 완전히 다른 종속성을 통합하는 것이 좋습니다.
마이클 최
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.