IT이야기

열거형 변수를 문자열로 변환하는 방법

cyworld 2022. 5. 12. 22:02
반응형

열거형 변수를 문자열로 변환하는 방법

열거형 유형의 변수 값을 표시하기 위해 인쇄하는 방법예를 들어,

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

그리고 내가 필요한 건 뭐랄까

printenum(OS_type, "My OS is %s", myOS);

정수가 아닌 "리눅스" 문자열을 표시해야 한다.

먼저 값 인덱싱된 문자열 배열을 만들어야겠죠.하지만 그게 가장 아름다운 방법인지 모르겠어.과연 가능할까?

물론 순진한 해결책은 문자열로의 변환을 수행하는 각 열거에 대해 다음과 같은 함수를 쓰는 것이다.

enum OS_type { Linux, Apple, Windows };

inline const char* ToString(OS_type v)
{
    switch (v)
    {
        case Linux:   return "Linux";
        case Apple:   return "Apple";
        case Windows: return "Windows";
        default:      return "[Unknown OS_type]";
    }
}

그러나 이것은 유지보수의 재앙이다.부스트의 도움으로.C와 C++ 코드와 함께 사용할 수 있는 전처리기 라이브러리에서는 전처리기기를 쉽게 활용할 수 있으며, 이 기능을 생성할 수 있다.세대 매크로는 다음과 같다.

#include <boost/preprocessor.hpp>

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem)    \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                \
    enum name {                                                               \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    inline const char* ToString(name v)                                       \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,          \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }

첫 번째 매크로(시작하기)X_는 두 는 두 번째에 의해 내부적으로 사용된다.두 번째 매크로에서 먼저 열거형을 생성한 다음ToString해당 유형의 개체를 가져다가 열거자 이름을 문자열로 반환하는 함수(이 구현에서는 열거자가 고유한 값으로 매핑해야 함)

C++에서는 다음을 구현할 수 있다.ToString의 역할을 하다operator<<대신 과부하가 걸리지만, 명시적인 "을 요구하는 것은 좀 더 깨끗한 것 같다.ToString" 값을 문자열 형식으로 변환한다.

의 회사는OS_type열거는 다음과 같이 정의된다.

DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows))

매크로가 처음에는 일이 많은 것처럼 보는 반면, 매크로의 정의는 다음과 같다.OS_type다소 이국적으로 보인다. 매크로를 한 번 작성해야 한다. 그러면 매크로를 모든 열거에 사용할 수 있다.추가적인 기능(예: 열거 변환을 위한 문자열 양식)을 큰 문제 없이 추가할 수 있으며, 매크로를 실행할 때 이름을 한 번만 제공하면 되기 때문에 유지관리 문제를 완벽하게 해결한다.

그런 다음 열거형을 정상적으로 정의한 것처럼 사용할 수 있다.

#include <iostream>

int main()
{
    OS_type t = Windows;
    std::cout << ToString(t) << " " << ToString(Apple) << std::endl;
}

이 게시물에는 코드 스니펫이 있는데, 이 코드 스니펫은 다음 코드부터 시작한다.#include <boost/preprocessor.hpp>솔루션을 입증하기 위해 게시된 대로 컴파일할 수 있는 라인.

이 특정 솔루션은 C++ 특정 구문(예: no)을 사용하기 때문에 C++용이다.typedef enum 및 , 이을 하는 와 함수의 과부하, 그러나 C로도 이 작업을 하는 것이 간단할 것이다.

이것을 하는 데는 정말 아름다운 방법이 없다.열거형으로 인덱싱된 문자열 배열을 설정하십시오.

출력을 많이 하면 열거형 매개 변수를 사용하고 조회를 수행하는 연산자를 정의할 수 있다.

이미 좋은 해답이 많지만 매직_enum은 볼만하다.

그것은 그 자체를 -라고 묘사하고 있다.

현대의 C+++에 대한 Enum( 문자열에서 문자열로, 반복으로)에 대한 정적 반영은 매크로 또는 보일러플레이트 코드 없이 모든 열거형에서 작동한다.

헤더 전용 C++17 라이브러리는 매크로 또는 보일러 플레이트 코드 없이 열거형 작업, 에누럼에 대한 정적 반사를 제공한다.

사용 예

enum Color { RED = 2, BLUE = 4, GREEN = 8 };


Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"

std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name);
if (color.has_value()) {
  // color.value() -> Color::GREEN
}

이것은 사전 프로세서 블록이다.

#ifndef GENERATE_ENUM_STRINGS
    #define DECL_ENUM_ELEMENT( element ) element
    #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME
    #define END_ENUM( ENUM_NAME ) ENUM_NAME; \
            char* getString##ENUM_NAME(enum tag##ENUM_NAME index);
#else
    #define DECL_ENUM_ELEMENT( element ) #element
    #define BEGIN_ENUM( ENUM_NAME ) char* gs_##ENUM_NAME [] =
    #define END_ENUM( ENUM_NAME ) ; char* getString##ENUM_NAME(enum \
            tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; }
#endif

열거 정의

BEGIN_ENUM(OsType)
{
    DECL_ENUM_ELEMENT(WINBLOWS),
    DECL_ENUM_ELEMENT(HACKINTOSH),
} END_ENUM(OsType)

통화 사용

getStringOsType(WINBLOWS);

여기서 가져왔어.얼마나 멋있니? :)

나 자신의 취향은 반복적인 타이핑과 매크로를 모두 최소화하고 일반 컴파일러 공간에 매크로 정의를 도입하는 것을 피하는 것이다.

헤더 파일에서 다음 작업을 수행하십시오.

enum Level{
        /**
        * zero reserved for internal use
        */
        verbose = 1,
        trace,
        debug,
        info,
        warn,
        fatal
    };

static Level readLevel(const char *);

cpp 구현은 다음과 같다.

 Logger::Level Logger::readLevel(const char *in) { 
 #  define MATCH(x) if (strcmp(in,#x) ==0) return x; 
    MATCH(verbose);
    MATCH(trace);
    MATCH(debug);
    MATCH(info);
    MATCH(warn);
    MATCH(fatal);
 # undef MATCH
    std::string s("No match for logging level ");
    s += in;
    throw new std::domain_error(s);
 }

매크로가 완료되는 즉시 #defend of the macro에 주목하십시오.

이렇게 해보셨나요?

#define stringify( name ) # name

enum enMyErrorValue
  {
  ERROR_INVALIDINPUT = 0,
  ERROR_NULLINPUT,
  ERROR_INPUTTOOMUCH,
  ERROR_IAMBUSY
  };

const char* enMyErrorValueNames[] = 
  {
  stringify( ERROR_INVALIDINPUT ),
  stringify( ERROR_NULLINPUT ),
  stringify( ERROR_INPUTTOOMUCH ),
  stringify( ERROR_IAMBUSY )
  };

void vPrintError( enMyErrorValue enError )
  {
  cout << enMyErrorValueNames[ enError ] << endl;
  }

int main()
  {
  vPrintError((enMyErrorValue)1);
  }

stringify()매크로는 당신의 코드의 어떤 텍스트도 문자열로 바꾸는 데 사용될 수 있지만, 괄호 사이의 정확한 텍스트만 사용할 수 있다.가변적인 비참조, 거시적인 대체물 또는 다른 종류의 실행된 것은 없다.

http://www.cplusplus.com/forum/general/2949/

C99에는 다음과 같은 것이 있다.P99_DECLARE_ENUMP99에서 당신이 간단히 선언할 수 있는enum다음과 같은 경우:

P99_DECLARE_ENUM(color, red, green, blue);

그리고 나서 사용하다color_getname(A)색 이름으로 된 문자열을 얻기 위해서입니다.

는 제임스, 하워드, 에더의 솔루션을 결합하여 보다 일반적인 구현을 만들어냈다.

  • int 값 및 사용자 지정 문자열 표현은 각 열거형 요소에 대해 선택적으로 정의될 수 있다.
  • "class"를 사용한다.

전체 코드는 아래에 기록된다(Unum을 정의하려면 "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" 사용)(온라인 데모).

#include <boost/preprocessor.hpp>
#include <iostream>

// ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ implementation is taken from:
// http://lists.boost.org/boost-users/2012/09/76055.php
//
// This macro do the following:
// input:
//      (Element1, "Element 1 string repr", 2) (Element2) (Element3, "Element 3 string repr")
// output:
//      ((Element1, "Element 1 string repr", 2)) ((Element2)) ((Element3, "Element 3 string repr"))
#define HELPER1(...) ((__VA_ARGS__)) HELPER2
#define HELPER2(...) ((__VA_ARGS__)) HELPER1
#define HELPER1_END
#define HELPER2_END
#define ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(sequence) BOOST_PP_CAT(HELPER1 sequence,_END)


// CREATE_ENUM_ELEMENT_IMPL works in the following way:
//  if (elementTuple.GetSize() == 4) {
//      GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)),
//  } else {
//      GENERATE: elementTuple.GetElement(0),
//  }
// Example 1:
//      CREATE_ENUM_ELEMENT_IMPL((Element1, "Element 1 string repr", 2, _))
//  generates:
//      Element1 = 2,
//
// Example 2:
//      CREATE_ENUM_ELEMENT_IMPL((Element2, _))
//  generates:
//      Element1,
#define CREATE_ENUM_ELEMENT_IMPL(elementTuple)                                          \
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 4),                       \
    BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple),        \
    BOOST_PP_TUPLE_ELEM(0, elementTuple)                                                \
),

// we have to add a dummy element at the end of a tuple in order to make 
// BOOST_PP_TUPLE_ELEM macro work in case an initial tuple has only one element.
// if we have a tuple (Element1), BOOST_PP_TUPLE_ELEM(2, (Element1)) macro won't compile.
// It requires that a tuple with only one element looked like (Element1,).
// Unfortunately I couldn't find a way to make this transformation, so
// I just use BOOST_PP_TUPLE_PUSH_BACK macro to add a dummy element at the end
// of a tuple, in this case the initial tuple will look like (Element1, _) what
// makes it compatible with BOOST_PP_TUPLE_ELEM macro
#define CREATE_ENUM_ELEMENT(r, data, elementTuple)                                      \
    CREATE_ENUM_ELEMENT_IMPL(BOOST_PP_TUPLE_PUSH_BACK(elementTuple, _))

#define DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, element)                                        \
    case enumName::element : return BOOST_PP_STRINGIZE(element);
#define DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, element, stringRepresentation)  \
    case enumName::element : return stringRepresentation;

// GENERATE_CASE_FOR_SWITCH macro generates case for switch operator.
// Algorithm of working is the following
//  if (elementTuple.GetSize() == 1) {
//      DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, elementTuple.GetElement(0))
//  } else {
//      DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, elementTuple.GetElement(0), elementTuple.GetElement(1))
//  }
//
// Example 1:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element1, "Element 1 string repr", 2))
//  generates:
//      case EnumName::Element1 : return "Element 1 string repr";
//
// Example 2:
//      GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element2))
//  generates:
//      case EnumName::Element2 : return "Element2";
#define GENERATE_CASE_FOR_SWITCH(r, enumName, elementTuple)                                                                                                 \
    BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 1),                                                                                       \
        DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple)),                                                          \
        DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple), BOOST_PP_TUPLE_ELEM(1, elementTuple))     \
    )


// DEFINE_ENUM_CLASS_WITH_ToString_METHOD final macro witch do the job
#define DEFINE_ENUM_CLASS_WITH_ToString_METHOD(enumName, enumElements)          \
enum class enumName {                                                           \
    BOOST_PP_SEQ_FOR_EACH(                                                      \
        CREATE_ENUM_ELEMENT,                                                    \
        0,                                                                      \
        ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)                     \
    )                                                                           \
};                                                                              \
inline const char* ToString(const enumName element) {                           \
        switch (element) {                                                      \
            BOOST_PP_SEQ_FOR_EACH(                                              \
                GENERATE_CASE_FOR_SWITCH,                                       \
                enumName,                                                       \
                ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements)             \
            )                                                                   \
            default: return "[Unknown " BOOST_PP_STRINGIZE(enumName) "]";       \
        }                                                                       \
}

DEFINE_ENUM_CLASS_WITH_ToString_METHOD(Elements,
(Element1)
(Element2, "string representation for Element2 ")
(Element3, "Element3 string representation", 1000)
(Element4, "Element 4 string repr")
(Element5, "Element5", 1005)
(Element6, "Element6 ")
(Element7)
)
// Generates the following:
//      enum class Elements {
//          Element1, Element2, Element3 = 1000, Element4, Element5 = 1005, Element6,
//      };
//      inline const char* ToString(const Elements element) {
//          switch (element) {
//              case Elements::Element1: return "Element1";
//              case Elements::Element2: return "string representation for Element2 ";
//              case Elements::Element3: return "Element3 string representation";
//              case Elements::Element4: return "Element 4 string repr";
//              case Elements::Element5: return "Element5";
//              case Elements::Element6: return "Element6 ";
//              case Elements::Element7: return "Element7";
//              default: return "[Unknown " "Elements" "]";
//          }
//      }

int main() {
    std::cout << ToString(Elements::Element1) << std::endl;
    std::cout << ToString(Elements::Element2) << std::endl;
    std::cout << ToString(Elements::Element3) << std::endl;
    std::cout << ToString(Elements::Element4) << std::endl;
    std::cout << ToString(Elements::Element5) << std::endl;
    std::cout << ToString(Elements::Element6) << std::endl;
    std::cout << ToString(Elements::Element7) << std::endl;

    return 0;
}

나는 양방향으로 작동하기 위해 이것이 필요했고 그리고 나는 종종 내 에넘버를 포함하는 수업에 포함시켰다. 그래서 나는 제임스 맥넬리스에 의한 해답으로 시작했지만, 이 해답은 최상위에 있었다. 그러나 나는 이 해답을 만들었다.참고 또한 나는 단순히 열거형보다는 열거형 클래스를 선호하는데, 이것은 답을 다소 복잡하게 만든다.

#define X_DEFINE_ENUMERATION(r, datatype, elem) case datatype::elem : return BOOST_PP_STRINGIZE(elem);

// The data portion of the FOR_EACH should be (variable type)(value)
#define X_DEFINE_ENUMERATION2(r, dataseq, elem) \
    if (BOOST_PP_SEQ_ELEM(1, dataseq) == BOOST_PP_STRINGIZE(elem) ) return BOOST_PP_SEQ_ELEM(0, dataseq)::elem;

#define DEFINE_ENUMERATION_MASTER(modifier, name, toFunctionName, enumerators)    \
    enum class name {                                                         \
        Undefined,                                                            \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    modifier const char* ToString(const name & v)                               \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUMERATION,                                         \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }                                                                         \
                                                                              \
    modifier const name toFunctionName(const std::string & value)               \
    {                                                                         \
        BOOST_PP_SEQ_FOR_EACH(                                                \
            X_DEFINE_ENUMERATION2,                                            \
            (name)(value),                                                    \
            enumerators                                                       \
        )                                                                     \
        return name::Undefined;                                               \
    }

#define DEFINE_ENUMERATION(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(inline, name, toFunctionName, enumerators)

#define DEFINE_ENUMERATION_INSIDE_CLASS(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(static, name, toFunctionName, enumerators)

수업 시간에 사용하기 위해 다음과 같은 작업을 할 수 있다.

class ComponentStatus {
public:
    /** This is a simple bad, iffy, and good status. See other places for greater details. */
    DEFINE_ENUMERATION_INSIDE_CLASS(Status, toStatus, (RED)(YELLOW)(GREEN)
}

그리고 나는 CppUnit 테스트를 작성했는데, 어떻게 사용하는지를 보여준다.

void
ComponentStatusTest::testSimple() {
    ComponentStatus::Status value = ComponentStatus::Status::RED;

    const char * valueStr = ComponentStatus::ToString(value);

    ComponentStatus::Status convertedValue = ComponentStatus::toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

DEFINE_ENUMERATION(Status, toStatus, (RED)(YELLOW)(GREEN))

void
ComponentStatusTest::testOutside() {
    Status value = Status::RED;

    const char * valueStr = ToString(value);

    Status convertedValue = toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

DEFING_ENUMERATION 또는 DEFING_ENUMERATION_INSIDE_CLASS 중에서 사용할 매크로를 선택해야 한다.ComponentStatus를 정의할 때 후자를 사용했다는 것을 알 수 있을 것이다.:상태 그러나 나는 상태를 정의할 때 전자를 사용했다.차이는 간단하다.클래스 내에서 수신/발신 메서드에 "정적"으로 접두사를 붙이고, 클래스에 없으면 "인라인"을 사용한다.사소한 차이점, 하지만 필요한 것.

불행히도, 나는 이렇게 하는 것을 피할 수 있는 깨끗한 방법이 없다고 생각한다.

const char * valueStr = ComponentStatus::ToString(value);

클래스 정의 후에 수동으로 인라인 메서드를 만들 수 있지만, 다음과 같은 방식으로 클래스 메서드에 연결하십시오.

inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }

사용하다std::map<OS_type, std::string>그리고 키로 열거형, 값으로 문자열 표현을 채우면 다음과 같은 작업을 할 수 있다.

printf("My OS is %s", enumMap[myOS].c_str());
std::cout << enumMap[myOS] ;

제임스의 대답을 확장하기 위해 누군가가 int값으로 열거형 정의를 지원하는 몇 가지 예시 코드를 원하는데, 나 또한 이러한 요구 사항이 있기 때문에, 다음과 같이 내 방법이 있다.

첫째, 는 FOR_EAKE에 의해 사용되는 내부 사용 매크로 입니다.

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE(r, data, elem)         \
    BOOST_PP_IF(                                                                \
        BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elem), 2),                           \
        BOOST_PP_TUPLE_ELEM(0, elem) = BOOST_PP_TUPLE_ELEM(1, elem),            \
        BOOST_PP_TUPLE_ELEM(0, elem) ),

매크로 정의:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)                  \
    enum name {                                                                 \
        BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \
                              0, enumerators) };

그래서 그것을 사용할 때, 다음과 같이 쓰는 것을 좋아할 수 있다.

DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum,
    ((FIRST, 1))
    ((SECOND))
    ((MAX, SECOND)) )

다음 항목으로 확장:

enum MyEnum
{
    FIRST = 1,
    SECOND,
    MAX = SECOND,
};

기본 아이디어는 모든 요소가 TUPLE인 SEQ를 정의하여 열거 멤버에 대한 추가 값을 넣을 수 있도록 하는 것이다.FOR_EACH 루프에서 TUPLE 크기를 확인하고 크기가 2인 경우 KEY = VALUE로 코드를 확장하거나 TUPLE의 첫 번째 요소를 유지하십시오.

입력 SEQ는 실제로 TUPLE이므로 STRINGIZE 함수를 정의하려면 먼저 입력 열거자를 사전 처리해야 할 수 있으므로, 다음은 작업을 수행하기 위한 매크로:

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM(r, data, elem)           \
    BOOST_PP_TUPLE_ELEM(0, elem),

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ(enumerators)         \
    BOOST_PP_SEQ_SUBSEQ(                                                        \
        BOOST_PP_TUPLE_TO_SEQ(                                                  \
            (BOOST_PP_SEQ_FOR_EACH(                                             \
                DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM, 0, enumerators) \
            )),                                                                 \
            0,                                                                  \
            BOOST_PP_SEQ_SIZE(enumerators))

매크로DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ모든 TUPLE에서 첫 번째 요소만 유지하고 나중에 SEQ로 변환하여 이제 제임스의 코드를 수정하면 당신은 모든 권한을 갖게 될 것이다.

나의 구현은 아마도 가장 간단한 것이 아닐 것이다. 그러므로 만약 당신이 어떤 깨끗한 코드를 찾지 못한다면, 참조를 위해 내 것이다.

부스트를 사용하지 않는 나 자신의 대답은 - 무거운 정의 마법을 사용하지 않고 나만의 접근법을 사용하는 것이며, 이 해결책은 특정한 열거값을 정의할 수 없다는 한계를 가지고 있다.

#pragma once
#include <string>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name> {                                         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };

/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

        printf( EnumToString(enumValue3).c_str() );

    From string to enumeration:

       enumName value;

       if( !StringToEnum("enumValue4", value) )
            printf("Conversion failed...");

    WARNING: At the moment assigning enum value to specific number is not supported.
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = (int)t;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (id == 0)
            return std::string(token, next);
        id--;
    } while (*next != 0);

    return std::string();
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = 0;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (strncmp(token, enumName, next - token) == 0)
        {
            t = (T)id;
            return true;
        }

        id++;
    } while (*next != 0);

    return false;
}

최신 버전은 여기 github에서 찾을 수 있다.

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h

개인적으로, 나는 간단한 것을 원했고 그렇게 하기 위해 교환원을 이용했다.

다음 열거형을 고려하십시오.

enum WeekDay { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };

그를 연자자로 할 수 .std::ostream.

std::ostream &operator<<(std::ostream &stream, const WeekDay day) {
  switch (day) {
    case MONDAY:
      stream << "Monday";
      break;
    case TUESDAY:
      stream << "Tuesday";
      break;
    case WEDNESDAY:
      stream << "Wednesday";
      break;
    case THURSDAY:
      stream << "Thursday";
      break;
    case FRIDAY:
      stream << "Friday";
      break;
    case SATURDAY:
      stream << "Saturday";
      break;
    case SUNDAY:
      stream << "Sunday";
      break;
  }

  return stream;
}

이 실에 제시된 다른 방법들에 비해 보일러 판 코드는 실제로 꽤 크다.하지만, 그것은 꽤 간단하고 사용하기 쉽다는 전제를 가지고 있다.

std::cout << "First day of the week is " << WeekDay::Monday << std::endl;

부스트를 사용하지 않는 내 솔루션:

#ifndef EN2STR_HXX_
#define EN2STR_HXX_

#define MAKE_STRING_1(str     ) #str
#define MAKE_STRING_2(str, ...) #str, MAKE_STRING_1(__VA_ARGS__)
#define MAKE_STRING_3(str, ...) #str, MAKE_STRING_2(__VA_ARGS__)
#define MAKE_STRING_4(str, ...) #str, MAKE_STRING_3(__VA_ARGS__)
#define MAKE_STRING_5(str, ...) #str, MAKE_STRING_4(__VA_ARGS__)
#define MAKE_STRING_6(str, ...) #str, MAKE_STRING_5(__VA_ARGS__)
#define MAKE_STRING_7(str, ...) #str, MAKE_STRING_6(__VA_ARGS__)
#define MAKE_STRING_8(str, ...) #str, MAKE_STRING_7(__VA_ARGS__)

#define PRIMITIVE_CAT(a, b) a##b
#define MAKE_STRING(N, ...) PRIMITIVE_CAT(MAKE_STRING_, N)     (__VA_ARGS__)


#define PP_RSEQ_N() 8,7,6,5,4,3,2,1,0
#define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__)
#define PP_NARG( ...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N())

#define MAKE_ENUM(NAME, ...) enum NAME { __VA_ARGS__ };            \
  struct NAME##_str {                                              \
    static const char * get(const NAME et) {                       \
      static const char* NAME##Str[] = {                           \
                MAKE_STRING(PP_NARG(__VA_ARGS__), __VA_ARGS__) };  \
      return NAME##Str[et];                                        \
      }                                                            \
    };

#endif /* EN2STR_HXX_ */

그리고 여기 그것을 사용하는 방법이 있다.

int main()
  {
  MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d);
  pippo c = d;
  cout << pippo_str::get(c) << "\n";
  return 0;
  }

파티에 늦게 온 또 다른 사람은 전처리기사 사용:

 1  #define MY_ENUM_LIST \
 2      DEFINE_ENUM_ELEMENT(First) \
 3      DEFINE_ENUM_ELEMENT(Second) \
 4      DEFINE_ENUM_ELEMENT(Third) \
 5  
 6  //--------------------------------------
 7  #define DEFINE_ENUM_ELEMENT(name) , name
 8  enum MyEnum {
 9      Zeroth = 0
10      MY_ENUM_LIST
11  };
12  #undef DEFINE_ENUM_ELEMENT
13 
14  #define DEFINE_ENUM_ELEMENT(name) , #name
15  const char* MyEnumToString[] = {
16      "Zeroth"
17      MY_ENUM_LIST
18  };
19  #undef DEFINE_ENUM_ELEMENT
20
21  #define DEFINE_ENUM_ELEMENT(name) else if (strcmp(s, #name)==0) return name;
22  enum MyEnum StringToMyEnum(const char* s){
23      if (strcmp(s, "Zeroth")==0) return Zeroth;
24      MY_ENUM_LIST
25      return NULL;
26  }
27  #undef DEFINE_ENUM_ELEMENT

(나는 그냥 줄 번호만 넣어서 얘기하기 편해.)1-4행은 열거형의 요소를 정의하기 위해 편집하는 것이다.(사물의 목록을 만드는 매크로니까, 나는 그것을 "list macro"라고 불렀다.@룬딘은 이것들이 X-매크로스라 불리는 잘 알려진 기술이라고 내게 알려준다.)

라인 7은 내부 매크로를 정의하여 8-11 라인의 실제 열거 선언을 채운다.라인 12는 내부 매크로를 정의하지 않는다( 컴파일러 경고를 침묵시키기 위해서만).

14행은 열거 요소 이름의 문자열 버전을 만들기 위해 내부 매크로를 정의한다.그런 다음 15-18행은 열거값을 해당 문자열로 변환할 수 있는 배열을 생성한다.

라인 21-27은 문자열을 열거값으로 변환하는 함수를 생성하거나 문자열이 일치하지 않으면 NULL을 반환한다.

이것은 0원소를 처리하는 방식이 좀 번거롭다.나는 사실 과거에 그것에 대해 일해 본 적이 있다.

나는 이 기술이 전처리기 자체가 당신을 위해 코드를 쓰도록 프로그램될 수 있다고 생각하고 싶지 않은 사람들을 괴롭힌다는 것을 인정한다.나는 그것이 가독성과 유지성의 차이를 강하게 보여준다고 생각한다.코드는 읽기 어렵지만 열거형에 수백 개의 요소가 있는 경우 요소를 추가, 제거 또는 재배열할 수 있으며 생성된 코드에 오류가 없는지 확인하십시오.

여기엔 좋은 답들이 많지만, 나는 몇몇 사람들이 내 답들이 유용하다고 생각할지도 모른다고 생각했다.매크로를 정의할 때 사용하는 인터페이스가 최대한 간단해서 좋아.또한 별도의 라이브러리를 포함하지 않아도 되기 때문에 편리하다. 모든 라이브러리는 C++와 함께 제공되며 최신 버전도 필요하지 않다.온라인에서 여러 곳에서 조각을 뽑아내서 전부 다 신용할 수는 없지만, 새로운 답을 얻을 수 있을 만큼 독특하다고 생각한다.

먼저 헤더 파일을 만드십시오...EnumMacros.h 또는 그 비슷한 것으로 부르고, 이것을 그 안에 넣으세요:

// Search and remove whitespace from both ends of the string
static std::string TrimEnumString(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it)) { it++; }
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit)) { rit++; }
    return std::string(it, rit.base());
}

static void SplitEnumArgs(const char* szArgs, std::string Array[], int nMax)
{
    std::stringstream ss(szArgs);
    std::string strSub;
    int nIdx = 0;
    while (ss.good() && (nIdx < nMax)) {
        getline(ss, strSub, ',');
        Array[nIdx] = TrimEnumString(strSub);
        nIdx++;
    }
};
// This will to define an enum that is wrapped in a namespace of the same name along with ToString(), FromString(), and COUNT
#define DECLARE_ENUM(ename, ...) \
    namespace ename { \
        enum ename { __VA_ARGS__, COUNT }; \
        static std::string _Strings[COUNT]; \
        static const char* ToString(ename e) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            return _Strings[e].c_str(); \
        } \
        static ename FromString(const std::string& strEnum) { \
            if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
            for (int i = 0; i < COUNT; i++) { if (_Strings[i] == strEnum) { return (ename)i; } } \
            return COUNT; \
        } \
    }

그럼, 당신의 메인 프로그램에서 당신은 이것을 할 수 있다...

#include "EnumMacros.h"
DECLARE_ENUM(OsType, Windows, Linux, Apple)

void main() {
    OsType::OsType MyOs = OSType::Apple;
    printf("The value of '%s' is: %d of %d\n", OsType::ToString(MyOs), (int)OsType::FromString("Apple"), OsType::COUNT);
}

출력이 >일 위치) '애플'의 값은: 4의 2

즐겨라!

파티에 조금 늦었지만, 여기 C++11 해결책이 있다.

namespace std {
    template<> struct hash<enum_one> {
        std::size_t operator()(const enum_one & e) const {
            return static_cast<std::size_t>(e);
        }
    };
    template<> struct hash<enum_two> { //repeat for each enum type
        std::size_t operator()(const enum_two & e) const {
            return static_cast<std::size_t>(e);
        }
    };
}

const std::string & enum_name(const enum_one & e) {
    static const std::unordered_map<enum_one, const std::string> names = {
    #define v_name(n) {enum_one::n, std::string(#n)}
        v_name(value1),
        v_name(value2),
        v_name(value3)
    #undef v_name
    };
    return names.at(e);
}

const std::string & enum_name(const enum_two & e) { //repeat for each enum type
    .................
}

C enums의 문제는 C++에 나오는 것처럼 자신의 유형이 아니라는 것이다.C의 열거형은 식별자를 적분 값에 매핑하는 방법이다.그것만.그렇기 때문에 열거형 값은 정수 값과 바꿀 수 있다.

당신이 정확하게 추측했듯이, 좋은 방법은 열거값과 문자열 사이에 매핑을 만드는 것이다.예를 들면 다음과 같다.

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};

열거형이 이미 정의되어 있다고 가정할 때 다음과 같은 쌍 배열을 만들 수 있다.

std::pair<QTask::TASK, QString> pairs [] = {
std::pair<OS_type, string>(Linux, "Linux"),
std::pair<OS_type, string>(Windows, "Windows"),
std::pair<OS_type, string>(Apple, "Apple"),
};

이제 지도를 만들 수 있다.

std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));

이제, 지도를 사용해도 된다.열거형이 변경된 경우 어레이 쌍[]에서 쌍을 추가/제거해야 한다.나는 그것이 C++의 열거형에서 끈을 얻는 가장 우아한 방법이라고 생각한다.

이 간단한 예는 나에게 효과가 있었다.이게 도움이 되길 바래.

#include <iostream>
#include <string>

#define ENUM_TO_STR(ENUM) std::string(#ENUM)

enum DIRECTION{NORTH, SOUTH, WEST, EAST};

int main()
{
  std::cout << "Hello, " << ENUM_TO_STR(NORTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(SOUTH) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(EAST) << "!\n";
  std::cout << "Hello, " << ENUM_TO_STR(WEST) << "!\n";
}

열거값을 지원하지 않는 답을 하나 추가했는데, 이제는 열거값 할당을 지원하는 지원도 추가했다.이전 해법에서와 같이, 이것은 최소 정의 마법을 사용한다.

헤더 파일:

#pragma once
#include <string>
#include <map>
#include <regex>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
    static std::map<std::string, int> enum2int;
    static std::map<int, std::string> int2enum;

    static void EnsureEnumMapReady( const char* enumsInfo )
    {
        if (*enumsInfo == 0 || enum2int.size() != 0 )
            return;

        // Should be called once per each enumeration.
        std::string senumsInfo(enumsInfo);
        std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
        std::smatch sm;
        int value = 0;

        for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
        {
            string enumName = sm[1].str();
            string enumValue = sm[2].str();

            if (enumValue.length() != 0)
                value = atoi(enumValue.c_str());

            enum2int[enumName] = value;
            int2enum[value] = enumName;
        }
    }
};

template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;

template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;


#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name>: public EnumReflectBase<##name> {         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

        printf( EnumToString(enumValue3).c_str() );

    From string to enumeration:

       enumName value;

       if( !StringToEnum("enumValue4", value) )
            printf("Conversion failed...");
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& int2enum = EnumReflect<T>::int2enum;
    auto it = int2enum.find(t);

    if (it == int2enum.end())
        return "";

    return it->second;
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& enum2int = EnumReflect<T>::enum2int;
    auto it = enum2int.find(enumName);

    if (it == enum2int.end())
        return false;

    t = (T) it->second;
    return true;
}

다음은 테스트 적용 예:

DECLARE_ENUM(TestEnum,
    ValueOne,
    ValueTwo,
    ValueThree = 5,
    ValueFour = 7
);

DECLARE_ENUM(TestEnum2,
    ValueOne2 = -1,
    ValueTwo2,
    ValueThree2 = -4,
    ValueFour2
);

void main(void)
{
    string sName1 = EnumToString(ValueOne);
    string sName2 = EnumToString(ValueTwo);
    string sName3 = EnumToString(ValueThree);
    string sName4 = EnumToString(ValueFour);

    TestEnum t1, t2, t3, t4, t5 = ValueOne;
    bool b1 = StringToEnum(sName1.c_str(), t1);
    bool b2 = StringToEnum(sName2.c_str(), t2);
    bool b3 = StringToEnum(sName3.c_str(), t3);
    bool b4 = StringToEnum(sName4.c_str(), t4);
    bool b5 = StringToEnum("Unknown", t5);

    string sName2_1 = EnumToString(ValueOne2);
    string sName2_2 = EnumToString(ValueTwo2);
    string sName2_3 = EnumToString(ValueThree2);
    string sName2_4 = EnumToString(ValueFour2);

    TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
    bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
    bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
    bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
    bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
    bool b2_5 = StringToEnum("Unknown", t2_5);

동일한 헤더 파일의 업데이트된 버전이 여기에 유지됨:

https://github.com/tapika/cppreflect/blob/master/cppreflect/enumreflect.h

여기 내 C++ 코드:

/* 
 * File:   main.cpp
 * Author: y2k1234
 *
 * Created on June 14, 2013, 9:50 AM
 */

#include <cstdlib>
#include <stdio.h>

using namespace std;


#define MESSAGE_LIST(OPERATOR)                          \
                                       OPERATOR(MSG_A), \
                                       OPERATOR(MSG_B), \
                                       OPERATOR(MSG_C)
#define GET_LIST_VALUE_OPERATOR(msg)   ERROR_##msg##_VALUE
#define GET_LIST_SRTING_OPERATOR(msg)  "ERROR_"#msg"_NAME"

enum ErrorMessagesEnum
{
   MESSAGE_LIST(GET_LIST_VALUE_OPERATOR)
};
static const char* ErrorMessagesName[] = 
{
   MESSAGE_LIST(GET_LIST_SRTING_OPERATOR)
};

int main(int argc, char** argv) 
{

    int totalMessages = sizeof(ErrorMessagesName)/4;

    for (int i = 0; i < totalMessages; i++)
    {
        if (i == ERROR_MSG_A_VALUE)
        {
                printf ("ERROR_MSG_A_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_B_VALUE)
        {
                printf ("ERROR_MSG_B_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else if (i == ERROR_MSG_C_VALUE)
        {
                printf ("ERROR_MSG_C_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
        else
        {
                printf ("??? => [%d]=[%s]\n", i, ErrorMessagesName[i]);
        }
    }   

    return 0;
}

Output:

ERROR_MSG_A_VALUE => [0]=[ERROR_MSG_A_NAME]

ERROR_MSG_B_VALUE => [1]=[ERROR_MSG_B_NAME]

ERROR_MSG_C_VALUE => [2]=[ERROR_MSG_C_NAME]

RUN SUCCESSFUL (total time: 126ms)

여기에 C 프리프로세서만을 이용한 Old Skool 방법(gcc에서 광범위하게 사용됨)이 있다.이산형 데이터 구조를 생성하지만 그 간 순서를 일치시켜야 하는 경우에 유용하다.mylist.tbl의 항목은 물론 훨씬 더 복잡한 것으로 확장될 수 있다.

test.cpp:

enum {
#undef XX
#define XX(name, ignore) name ,
#include "mylist.tbl"
  LAST_ENUM
};

char * enum_names [] = {
#undef XX
#define XX(name, ignore) #name ,
#include "mylist.tbl"
   "LAST_ENUM"
};

그리고 mylist.tbl:

/*    A = enum                  */
/*    B = some associated value */
/*     A        B   */
  XX( enum_1 , 100)
  XX( enum_2 , 100 )
  XX( enum_3 , 200 )
  XX( enum_4 , 900 )
  XX( enum_5 , 500 )

c++와 같은 경우:

enum OS_type{Linux, Apple, Windows};

std::string ToString( const OS_type v )
{
  const std::map< OS_type, std::string > lut =
    boost::assign::map_list_of( Linux, "Linux" )(Apple, "Apple )( Windows,"Windows");
  std::map< OS_type, std::string >::const_iterator it = lut.find( v );
  if ( lut.end() != it )
    return it->second;
  return "NOT FOUND";
}
#include <EnumString.h>

http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C에서 그 이후에.

enum FORM {
    F_NONE = 0,
    F_BOX,
    F_CUBE,
    F_SPHERE,
};

삽입하다

Begin_Enum_String( FORM )
{
    Enum_String( F_NONE );
    Enum_String( F_BOX );
    Enum_String( F_CUBE );
    Enum_String( F_SPHERE );
}
End_Enum_String;

열거형의 값이 중복되지 않은 경우 정상 작동한다.

열거형 값을 문자열로 변환하기 위한 샘플 코드:

enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );

반대편에 대한 샘플 코드:

assert( EnumString< FORM >::To( f, str ) );

James에게 너의 제안에 감사한다.그것은 매우 유용했기 때문에 나는 어떤 방식으로든 기여하기 위해 다른 방법을 실행했다.

#include <iostream>
#include <boost/preprocessor.hpp>

using namespace std;

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data,  elem) \
    case data::elem : return BOOST_PP_STRINGIZE(elem);

#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF(r, data, elem) \
    if (BOOST_PP_SEQ_TAIL(data) ==                                     \
            BOOST_PP_STRINGIZE(elem)) return                           \
            static_cast<int>(BOOST_PP_SEQ_HEAD(data)::elem); else

#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators)         \
    enum class name {                                                  \
        BOOST_PP_SEQ_ENUM(enumerators)                                 \
    };                                                                 \
                                                                       \
    inline const char* ToString(name v)                                \
    {                                                                  \
        switch (v)                                                     \
        {                                                              \
            BOOST_PP_SEQ_FOR_EACH(                                     \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE,   \
                name,                                                  \
                enumerators                                            \
            )                                                          \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";  \
        }                                                              \
    }                                                                  \
                                                                       \
    inline int ToEnum(std::string s)                                   \
    {                                                                  \
        BOOST_PP_SEQ_FOR_EACH(                                         \
                X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF,       \
                (name)(s),                                             \
                enumerators                                            \
            )                                                          \
        return -1;                                                     \
    }


DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows));

int main(void)
{
    OS_type t = OS_type::Windows;

    cout << ToString(t) << " " << ToString(OS_type::Apple) << " " << ToString(OS_type::Linux) << endl;

    cout << ToEnum("Windows") << " " << ToEnum("Apple") << " " << ToEnum("Linux") << endl;

    return 0;
}

순수 표준 C의 깨끗하고 안전한 용액:

#include <stdio.h>

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

/* list of enum constants */
#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N
} test_t;

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

int main()
{  
  _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
                 "Incorrect number of items in enum or look-up table");

  printf("%d %s\n", hello, test_str[hello]);
  printf("%d %s\n", world, test_str[world]);
  test_t x = world;
  printf("%d %s\n", x, test_str[x]);

  return 0;
}

출력

0 hello
1 world
1 world

이론적 근거

핵심 문제를 "해당 문자열이 있는 열거형 상수"로 해결할 때, 분별 있는 프로그래머는 다음과 같은 요건을 제시한다.

  • 코드 반복("DRY" 원칙)을 피하십시오.
  • 코드는 열거형 내부에서 항목을 추가하거나 제거하더라도 확장 가능하고 유지보수가 가능하며 안전해야 한다.
  • 모든 코드는 읽기 쉽고 유지 관리가 쉬운 고품질이어야 한다.

첫 번째 요건, 어쩌면 두 번째 요건도 악명 높은 "x 매크로" 속임수나 다른 형태의 매크로 마법과 같은 여러 가지 지저분한 매크로 솔루션으로 충족될 수 있다.그러한 해결책의 문제는 그들이 당신에게 완전히 읽을 수 없는 수수께끼의 매크로의 엉망진창으로 남겨준다는 것이다. 그들은 위의 세 번째 요건을 충족시키지 못한다.

여기서 필요한 것은 실제로 문자열 조회 테이블을 갖추는 것 뿐인데, 열거 변수를 인덱스로 사용하면 접근할 수 있다.그러한 표는 당연히 열거자와 직접 일치해야 하며 그 반대의 경우도 마찬가지여야 한다.둘 중 하나가 업데이트되면 다른 것도 업데이트해야 하며 그렇지 않으면 작동하지 않을 것이다.


코드에 대한 설명

우리가 다음과 같은 열거형을 가지고 있다고 가정하자.

typedef enum
{
  hello,
  world
} test_t;

이것은 다음으로 변경할 수 있다.

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

이러한 매크로 상수를 다른 곳에서 사용할 수 있다는 이점으로, 예를 들어 문자열 조회 표를 생성한다.사전 프로세서 상수를 문자열로 변환하는 작업은 "stringify" 매크로로 수행할 수 있다.

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

const char* test_str[]=
{
  STRINGIFY(TEST_0),
  STRINGIFY(TEST_1),
};

그리고 이것이 마지막입니다.를 통해hello값이 0인 열거형 상수를 구한다.를 통해test_str[hello]는 "이라는 줄을 안녕'이라는 문자열을 얻는다

열거형과 조회표가 직접적으로 일치하도록 하기 위해서는, 우리는 그것들에 동일한 양의 항목이 포함되어 있는지 확인해야 한다.만약 누군가가 코드를 유지하고 열거형만 변경하고 조회 테이블은 변경하지 않는다면, 또는 그 반대로 이 방법은 작동하지 않을 것이다.

해결책은 열거형을 두어 그것이 얼마나 많은 항목을 포함하고 있는지를 알려주는 것이다.여기에는 일반적으로 사용되는 C 트릭이 있다. 단지 마지막에 항목을 추가하기만 하면 열거된 항목이 몇 개인지 알려주는 목적만 채울 뿐이다.

typedef enum
{
  TEST_0,
  TEST_1,
  TEST_N  // will have value 2, there are 2 enum constants in this enum
} test_t;

이제 컴파일 시간에 열거된 항목의 수가 조회 테이블의 항목 수만큼 되는지 확인할 수 있으며, 가급적 C11 정적 주장을 통해 다음과 같이 확인할 수 있다.

_Static_assert(sizeof test_str / sizeof *test_str == TEST_N, 
               "Incorrect number of items in enum or look-up table");

(누군가가 공룡 컴파일러 사용을 고집한다면, 이전 버전의 C 표준에서도 정적인 주장을 하는 추악하지만 완전히 기능적인 방법이 있다.C++에 대해서는 정적 주장도 지지한다.)


참고로, C11에서는 문자열 매크로를 변경하여 보다 높은 유형의 안전을 달성할 수 있다.

#define STRINGIFY(x) _Generic((x), int : STRF(x))

(int열거 상수가 실제로 유형이기 때문에int, 아니다.test_t)

이렇게 하면 다음과 같은 코드가 방지된다.STRINGIFY(random_stuff)편찬에서

내가 만든 것은 내가 여기서 본 것과 이 사이트에서 본 비슷한 질문들을 조합한 것이다.나는 2013년 Visual Studio를 만들었다.나는 그것을 다른 컴파일러들과 시험해 본 적이 없다.

우선 나는 묘기를 부릴 매크로의 집합을 정의한다.

// concatenation macros
#define CONCAT_(A, B) A ## B
#define CONCAT(A, B)  CONCAT_(A, B)

// generic expansion and stringification macros
#define EXPAND(X)           X
#define STRINGIFY(ARG)      #ARG
#define EXPANDSTRING(ARG)   STRINGIFY(ARG)        

// number of arguments macros
#define NUM_ARGS_(X100, X99, X98, X97, X96, X95, X94, X93, X92, X91, X90, X89, X88, X87, X86, X85, X84, X83, X82, X81, X80, X79, X78, X77, X76, X75, X74, X73, X72, X71, X70, X69, X68, X67, X66, X65, X64, X63, X62, X61, X60, X59, X58, X57, X56, X55, X54, X53, X52, X51, X50, X49, X48, X47, X46, X45, X44, X43, X42, X41, X40, X39, X38, X37, X36, X35, X34, X33, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, ...) N
#define NUM_ARGS(...) EXPAND(NUM_ARGS_(__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1))

// argument extraction macros
#define FIRST_ARG(ARG, ...) ARG
#define REST_ARGS(ARG, ...) __VA_ARGS__

// arguments to strings macros
#define ARGS_STR__(N, ...)  ARGS_STR_##N(__VA_ARGS__)
#define ARGS_STR_(N, ...)   ARGS_STR__(N, __VA_ARGS__)
#define ARGS_STR(...)       ARGS_STR_(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

#define ARGS_STR_1(ARG)     EXPANDSTRING(ARG)
#define ARGS_STR_2(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_1(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_3(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_2(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_4(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_3(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_5(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_4(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_6(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_5(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_7(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_6(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_8(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_7(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_9(...)     EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_8(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_10(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_9(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_11(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_10(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_12(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_11(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_13(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_12(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_14(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_13(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_15(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_14(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_16(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_15(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_17(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_16(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_18(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_17(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_19(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_18(EXPAND(REST_ARGS(__VA_ARGS__)))
#define ARGS_STR_20(...)    EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_19(EXPAND(REST_ARGS(__VA_ARGS__)))
// expand until _100 or as much as you need

다음으로 열거 클래스와 문자열을 가져오는 함수를 생성할 단일 매크로를 정의하십시오.

#define ENUM(NAME, ...)                                                                                             \
    enum class NAME                                                                                                 \
    {                                                                                                               \
        __VA_ARGS__                                                                                                 \
    };                                                                                                              \
                                                                                                                    \
    static const std::array<std::string, NUM_ARGS(__VA_ARGS__)> CONCAT(NAME, Strings) = { ARGS_STR(__VA_ARGS__) };  \
                                                                                                                    \
    inline const std::string& ToString(NAME value)                                                                  \
    {                                                                                                               \
        return CONCAT(NAME, Strings)[static_cast<std::underlying_type<NAME>::type>(value)];                         \
    }                                                                                                               \
                                                                                                                    \
    inline std::ostream& operator<<(std::ostream& os, NAME value)                                                   \
    {                                                                                                               \
        os << ToString(value);                                                                                      \
        return os;                                                                                                  \
    }

이제 열거형 유형과 그것을 위한 문자열을 정의하는 것은 정말 쉬워진다.당신이 해야 할 일은:

ENUM(MyEnumType, A, B, C);

다음과 같은 라인을 사용하여 테스트할 수 있다.

int main()
{
    std::cout << MyEnumTypeStrings.size() << std::endl;

    std::cout << ToString(MyEnumType::A) << std::endl;
    std::cout << ToString(MyEnumType::B) << std::endl;
    std::cout << ToString(MyEnumType::C) << std::endl;

    std::cout << MyEnumType::A << std::endl;
    std::cout << MyEnumType::B << std::endl;
    std::cout << MyEnumType::C << std::endl;

    auto myVar = MyEnumType::A;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::B;
    std::cout << myVar << std::endl;
    myVar = MyEnumType::C;
    std::cout << myVar << std::endl;

    return 0;
}

출력:

3
A
B
C
A
B
C
A
B
C

나는 그것이 매우 깨끗하고 사용하기 쉽다고 믿는다.다음과 같은 몇 가지 제한 사항이 있다.

  • 열거형 멤버에는 값을 할당할 수 없다.
  • 열거 멤버의 값은 인덱스로 사용되지만, 모든 것이 단일 매크로로 정의되기 때문에 괜찮아야 한다.
  • 클래스 내에서 열거형 유형을 정의하는 데 사용할 수 없다.

만약 당신이 이 문제를 해결할 수 있다면.나는 특히 어떻게 사용하는지, 이것은 멋지고 갸름하다고 생각한다.장점:

  • 사용하기 쉽다.
  • 런타임에 분할할 문자열이 필요하지 않다.
  • 컴파일 시 별도의 문자열을 사용할 수 있다.
  • 읽기 쉽다.첫 번째 매크로 세트는 1초가 더 필요할지도 모르지만, 그렇게 복잡하지는 않다.

이 문제에 대한 명확한 해결책은 다음과 같다.

#define RETURN_STR(val, e) {if (val == e) {return #e;}}

std::string conv_dxgi_format_to_string(int value) {
    RETURN_STR(value, DXGI_FORMAT_UNKNOWN);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_FLOAT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_UINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_SINT);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_TYPELESS);
    RETURN_STR(value, DXGI_FORMAT_R32G32B32_FLOAT);

    /* ... */

    return "<UNKNOWN>";
}

이 솔루션의 좋은 점은 간단하고 기능 구성도 복사 및 교체를 통해 쉽게 할 수 있다는 것이다.변환을 많이 하려고 하는데 열거형에 가능한 값이 너무 많으면 이 솔루션이 CPU 집약적이 될 수 있다는 점에 유의하십시오.

좀 늦었지만, 여기 g++와 표준 도서관만 사용하는 해결책이 있어.네임스페이스 오염을 최소화하고 열거형 이름을 다시 입력할 필요가 없도록 노력했다.

헤더 파일 "my_enum.hpp"는 다음과 같다.

#include <cstring>

namespace ENUM_HELPERS{
    int replace_commas_and_spaces_with_null(char* string){
        int i, N;
        N = strlen(string);
        for(i=0; i<N; ++i){
            if( isspace(string[i]) || string[i] == ','){
                string[i]='\0';
            }
        }
        return(N);
    }

    int count_words_null_delim(char* string, int tot_N){
        int i;
        int j=0;
        char last = '\0';
        for(i=0;i<tot_N;++i){
            if((last == '\0') && (string[i]!='\0')){
                ++j;
            }
            last = string[i];
        }
        return(j);
    }

    int get_null_word_offsets(char* string, int tot_N, int current_w){
        int i;
        int j=0;
        char last = '\0';
        for(i=0; i<tot_N; ++i){
            if((last=='\0') && (string[i]!='\0')){
                if(j == current_w){
                    return(i);
                }
                ++j;
            }
            last = string[i];
        }
        return(tot_N); //null value for offset
    }

    int find_offsets(int* offsets, char* string, int tot_N, int N_words){
        int i;
        for(i=0; i<N_words; ++i){
            offsets[i] = get_null_word_offsets(string, tot_N, i);
        }
        return(0);
    }
}


#define MAKE_ENUM(NAME, ...)                                            \
namespace NAME{                                                         \
    enum ENUM {__VA_ARGS__};                                            \
    char name_holder[] = #__VA_ARGS__;                                  \
    int name_holder_N =                                                 \
        ENUM_HELPERS::replace_commas_and_spaces_with_null(name_holder); \
    int N =                                                             \
        ENUM_HELPERS::count_words_null_delim(                           \
            name_holder, name_holder_N);                                \
    int offsets[] = {__VA_ARGS__};                                      \
    int ZERO =                                                          \
        ENUM_HELPERS::find_offsets(                                     \
            offsets, name_holder, name_holder_N, N);                    \
    char* tostring(int i){                                              \
       return(&name_holder[offsets[i]]);                                \
    }                                                                   \
}

사용 예:

#include <cstdio>
#include "my_enum.hpp"

MAKE_ENUM(Planets, MERCURY, VENUS, EARTH, MARS)

int main(int argc, char** argv){    
    Planets::ENUM a_planet = Planets::EARTH;
    printf("%s\n", Planets::tostring(Planets::MERCURY));
    printf("%s\n", Planets::tostring(a_planet));
}

출력:

MERCURY
EARTH

모든 것을 한 번만 정의하면 되고, 네임스페이스가 오염되어서는 안 되며, 모든 계산은 한 번만 하면 된다( 나머지는 그냥 조회다).그러나 열거형 클래스의 유형 안전성을 얻지 못하고(아직은 정수만 짧을 뿐), 열거형에 값을 할당할 수 없으며 네임스페이스를 정의할 수 있는 곳(예: 전역)에서 열거형을 정의해야 한다.

이것에 대한 성과가 얼마나 좋은지, 혹은 좋은 생각인지 잘 모르겠다(C++보다 먼저 C를 배웠기 때문에 뇌는 여전히 그런 식으로 작용한다).이것이 왜 나쁜 생각인지 아는 사람이 있다면 얼마든지 지적해 보아라.

참조URL: https://stackoverflow.com/questions/5093460/how-to-convert-an-enum-type-variable-to-a-string

반응형