Programing

C에서 enum 유형의 변수를 문자열로 사용하는 쉬운 방법은 무엇입니까?

lottogame 2020. 9. 13. 11:36
반응형

C에서 enum 유형의 변수를 문자열로 사용하는 쉬운 방법은 무엇입니까?


내가하려는 것은 다음과 같습니다.

typedef enum { ONE, TWO, THREE } Numbers;

다음과 유사한 스위치 케이스를 수행하는 함수를 작성하려고합니다.

char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, num); //some way to get the symbolic constant name in here?
    } break;
    default:
      return 0; //no match
  return 1;
}

모든 경우에 정의하는 대신 위에서 시도한 것처럼 열거 형 변수를 사용하여 설정하는 방법이 있습니까?


기본 제공 솔루션이 없습니다. 가장 쉬운 방법은 char*열거 형의 int 값이 해당 열거 형의 설명 적 이름을 포함하는 문자열로 색인 되는 배열을 사용하는 것입니다 . enum일부 int매핑이 배열 기반 매핑을 비실용적으로 만들만큼 충분히 높은 스파 스 (0에서 시작하지 않거나 번호 매기기에 간격이있는)가있는 경우 대신 해시 테이블을 사용할 수 있습니다.


C 식별자와 문자열을 모두 만드는 기술 ? 여기에서 사용할 수 있습니다.

이러한 전 처리기 항목과 마찬가지로 전 처리기 부분을 작성하고 이해하는 것은 어려울 수 있으며 매크로를 다른 매크로로 전달하고 # 및 ## 연산자를 사용하는 것이 포함되지만 사용하기는 정말 쉽습니다. 이 스타일은 동일한 목록을 두 번 유지하는 것이 정말 번거로울 수있는 긴 열거 형에 매우 유용합니다.

공장 코드-한 번만 입력되며 일반적으로 헤더에 숨겨집니다.

enumFactory.h :

// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,

// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;

// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;

/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
  enum EnumType { \
    ENUM_DEF(ENUM_VALUE) \
  }; \
  const char *GetString(EnumType dummy); \
  EnumType Get##EnumType##Value(const char *string); \

/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
  const char *GetString(EnumType value) \
  { \
    switch(value) \
    { \
      ENUM_DEF(ENUM_CASE) \
      default: return ""; /* handle input error */ \
    } \
  } \
  EnumType Get##EnumType##Value(const char *str) \
  { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; /* handle input error */ \
  } \

공장 사용

someEnum.h :

#include "enumFactory.h"
#define SOME_ENUM(XX) \
    XX(FirstValue,) \
    XX(SecondValue,) \
    XX(SomeOtherValue,=50) \
    XX(OneMoreValue,=100) \

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp :

#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)

이 기술은 XX 매크로가 더 많은 인수를 받도록 쉽게 확장 할 수 있으며,이 샘플에서 제공 한 세 가지와 유사하게 다른 필요에 따라 XX를 대체 할 더 많은 매크로를 준비 할 수도 있습니다.

#include / #define / #undef를 사용한 X-Macros와의 비교

이것은 다른 사람들이 언급 한 X-Macros와 유사하지만,이 솔루션은 #undefing 아무것도 필요하지 않다는 점에서 더 우아하다고 생각합니다. 따라서 복잡한 것들을 더 많이 숨길 수있는 헤더 파일 인 헤더 파일이 공장에 있습니다. 새로운 enum을 정의해야 할 때 전혀 건드리지 않는 것이므로 새로운 enum 정의는 훨씬 짧고 깔끔합니다.


// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
    ENUM(ONE),
    ENUM(TWO),
    ENUM(FOUR)
ENUM_END( Numbers )

// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };

// Now in one and only one .c file, redefine the ENUM macros and reinclude
//  the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED   // whatever you need to do to enable reinclusion
#include "numbers.h"

// Now you can do exactly what you want to do, with no retyping, and for any
//  number of enumerated types defined with the ENUM macro family
//  Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
    } break;
    default:
      return 0; //no match
  return 1;
}

// Sweet no ? After being frustrated by this for years, I finally came up
//  with this solution for my most recent project and plan to reuse the idea
//  forever

이 작업을 수행하는 방법이 있습니다. X () 매크로를 사용하십시오 . 이러한 매크로는 C 전처리기를 사용하여 소스 데이터 목록에서 열거 형, 배열 및 코드 블록을 생성합니다. X () 매크로를 포함하는 #define에 새 항목을 추가하기 만하면됩니다. switch 문은 자동으로 확장됩니다.

귀하의 예는 다음과 같이 작성할 수 있습니다.

 // Source data -- Enum, String
 #define X_NUMBERS \
    X(ONE,   "one") \
    X(TWO,   "two") \
    X(THREE, "three")

 ...

 // Use preprocessor to create the Enum
 typedef enum {
  #define X(Enum, String)       Enum,
   X_NUMBERS
  #undef X
 } Numbers;

 ...

 // Use Preprocessor to expand data into switch statement cases
 switch(num)
 {
 #define X(Enum, String) \
     case Enum:  strcpy(num_str, String); break;
 X_NUMBERS
 #undef X

     default: return 0; break;
 }
 return 1;

더 효율적인 방법 (예 : X 매크로를 사용하여 문자열 배열 및 열거 형 인덱스 생성)이 있지만 이것이 가장 간단한 데모입니다.


몇 가지 확실한 답이 있다는 것을 알고 있지만 C 전 처리기의 # 연산자에 대해 알고 있습니까?

이를 통해 다음을 수행 할 수 있습니다.

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}

자주 필요했지만 C 또는 C ++에서는이 기능을 제공하지 않습니다.

다음 코드는 비 희소 열거 형에 가장 적합하지만 작동합니다.

typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);

희소하지 않다는 것은 형식이 아님을 의미합니다.

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

그 안에 큰 간격이 있기 때문입니다.

이 방법의 장점은 열거 형과 문자열의 정의를 서로 가깝게 배치한다는 것입니다. 함수에 switch 문을 사용하면 이러한 문제를 해결할 수 있습니다. 이것은 당신이 다른 것없이 하나를 변경할 가능성이 적다는 것을 의미합니다.


키스. 열거 형으로 모든 종류의 다른 스위치 / 케이스 작업을 수행 할 것이므로 인쇄가 왜 달라야합니까? 케이스를 잊을 수있는 다른 장소가 약 100 곳이 있다는 점을 고려할 때 인쇄 루틴에서 케이스를 잊는 것은 큰 문제가 아닙니다. -Wall을 컴파일하면 전체 대소 문자 일치를 경고합니다. "기본값"을 사용하지 마십시오. 그러면 스위치가 완전하게되고 경고가 표시되지 않습니다. 대신 스위치를 종료하고 다음과 같이 기본 케이스를 처리하십시오.

const char *myenum_str(myenum e)
{
    switch(e) {
    case ONE: return "one";
    case TWO: return "two";
    }
    return "invalid";
}

C ++ 열거 형을 문자열로 변환 해보십시오 . 코멘트 열거 항목은 임의의 값을 가질 때 문제를 해결 개선이있다.


의 사용 부스트 :: 프리 프로세서는 다음과 같은 가능한 우아한 해결책을한다 :

1 단계 : 헤더 파일 포함 :

#include "EnumUtilities.h"

2 단계 : 다음 구문을 사용하여 열거 형 개체를 선언합니다.

MakeEnum( TestData,
         (x)
         (y)
         (z)
         );

3 단계 : 데이터 사용 :

요소 수 얻기 :

td::cout << "Number of Elements: " << TestDataCount << std::endl;

관련 문자열 가져 오기 :

std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl;
std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl;
std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl;

관련 문자열에서 열거 형 값 가져 오기 :

std::cout << "Value of x is " << TestData2Enum("x") << std::endl;
std::cout << "Value of y is " << TestData2Enum("y") << std::endl;
std::cout << "Value of z is " << TestData2Enum("z") << std::endl;

포함 할 추가 파일없이 깔끔하고 간결 해 보입니다. EnumUtilities.h에서 작성한 코드는 다음과 같습니다.

#include <boost/preprocessor/seq/for_each.hpp>
#include <string>

#define REALLY_MAKE_STRING(x) #x
#define MAKE_STRING(x) REALLY_MAKE_STRING(x)
#define MACRO1(r, data, elem) elem,
#define MACRO1_STRING(r, data, elem)    case elem: return REALLY_MAKE_STRING(elem);
#define MACRO1_ENUM(r, data, elem)      if (REALLY_MAKE_STRING(elem) == eStrEl) return elem;


#define MakeEnum(eName, SEQ) \
    enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \
    last_##eName##_enum}; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    }; \
    static enum eName eName##2Enum(const std::string eStrEl) \
    { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \
        return (enum eName)0; \
    };

몇 가지 제한이 있습니다. 예를 들어 boost :: preprocessor의 제한이 있습니다. 이 경우 상수 목록은 64 개 요소보다 클 수 없습니다.

동일한 논리에 따라 희소 열거 형을 생성 할 수도 있습니다.

#define EnumName(Tuple)                 BOOST_PP_TUPLE_ELEM(2, 0, Tuple)
#define EnumValue(Tuple)                BOOST_PP_TUPLE_ELEM(2, 1, Tuple)
#define MACRO2(r, data, elem)           EnumName(elem) EnumValue(elem),
#define MACRO2_STRING(r, data, elem)    case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem));

#define MakeEnumEx(eName, SEQ) \
    enum eName { \
    BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \
    last_##eName##_enum }; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    };  

이 경우 구문은 다음과 같습니다.

MakeEnumEx(TestEnum,
           ((x,))
           ((y,=1000))
           ((z,))
           );

사용법은 위와 유사합니다 (이전 구문에서 외삽 할 수있는 eName ## 2Enum 함수 제외).

Mac과 Linux에서 테스트했지만 boost :: preprocessor는 완전히 이식 가능하지 않을 수 있습니다.


여기에있는 몇 가지 기술을 병합하여 가장 간단한 형식을 만들었습니다.

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}

gcc를 사용하는 경우 다음을 사용할 수 있습니다.

const char * enum_to_string_map[]={ [enum1]='string1', [enum2]='string2'};

그런 다음 예를 들어 전화하십시오.

enum_to_string_map[enum1]

Mu Dynamics Research Labs-Blog Archive 에서 아이디어를 확인하십시오 . 나는 올해 초에 이것을 발견했다. 나는 그것을 발견 한 정확한 맥락을 잊어 버려서이 코드에 적용했다. 우리는 앞에 E를 추가하는 것의 장점에 대해 토론 할 수 있습니다. 해결 된 특정 문제에 적용 할 수 있지만 일반적인 솔루션의 일부는 아닙니다. 나는 이것을 내 'vignettes'폴더에 숨겼습니다-나중에 원할 경우를 대비하여 흥미로운 코드 스크랩을 보관합니다. 이 아이디어가 그 당시에 어디서 왔는지 기록하지 않았다고 말하는 것이 부끄럽습니다.

헤더 : paste1.h

/*
@(#)File:           $RCSfile: paste1.h,v $
@(#)Version:        $Revision: 1.1 $
@(#)Last changed:   $Date: 2008/05/17 21:38:05 $
@(#)Purpose:        Automated Token Pasting
*/

#ifndef JLSS_ID_PASTE_H
#define JLSS_ID_PASTE_H

/*
 * Common case when someone just includes this file.  In this case,
 * they just get the various E* tokens as good old enums.
 */
#if !defined(ETYPE)
#define ETYPE(val, desc) E##val,
#define ETYPE_ENUM
enum {
#endif /* ETYPE */

   ETYPE(PERM,  "Operation not permitted")
   ETYPE(NOENT, "No such file or directory")
   ETYPE(SRCH,  "No such process")
   ETYPE(INTR,  "Interrupted system call")
   ETYPE(IO,    "I/O error")
   ETYPE(NXIO,  "No such device or address")
   ETYPE(2BIG,  "Arg list too long")

/*
 * Close up the enum block in the common case of someone including
 * this file.
 */
#if defined(ETYPE_ENUM)
#undef ETYPE_ENUM
#undef ETYPE
ETYPE_MAX
};
#endif /* ETYPE_ENUM */

#endif /* JLSS_ID_PASTE_H */

소스 예 :

/*
@(#)File:           $RCSfile: paste1.c,v $
@(#)Version:        $Revision: 1.2 $
@(#)Last changed:   $Date: 2008/06/24 01:03:38 $
@(#)Purpose:        Automated Token Pasting
*/

#include "paste1.h"

static const char *sys_errlist_internal[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) desc,
#include "paste1.h"
    0
#undef ETYPE
};

static const char *xerror(int err)
{
    if (err >= ETYPE_MAX || err <= 0)
        return "Unknown error";
    return sys_errlist_internal[err];
}

static const char*errlist_mnemonics[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) [E ## val] = "E" #val,
#include "paste1.h"
#undef ETYPE
};

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < ETYPE_MAX; i++)
    {
        printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
    }
    return(0);
}

반드시 C 전처리기를 세계에서 가장 깔끔하게 사용하는 것은 아니지만 자료를 여러 번 쓰는 것을 방지합니다.


C 식별자와 문자열 모두 만들기


열거 형 인덱스가 0부터 시작하는 경우 char * 배열에 이름을 넣고 열거 형 값으로 인덱스 할 수 있습니다.


#define stringify( name ) # name

enum MyEnum {
    ENUMVAL1
};
...stuff...

stringify(EnumName::ENUMVAL1);  // Returns MyEnum::ENUMVAL1

이 방법에 대한 추가 논의

신규 이민자를위한 전 처리기 지시어 트릭


나는 간단한 템플릿 클래스를 만들었 streamable_enum가 사용하는 연산자를 스트리밍 것을 <<하고 >>과를 기반으로 std::map<Enum, std::string>:

#ifndef STREAMABLE_ENUM_HPP
#define STREAMABLE_ENUM_HPP

#include <iostream>
#include <string>
#include <map>

template <typename E>
class streamable_enum
{
public:
    typedef typename std::map<E, std::string> tostr_map_t;
    typedef typename std::map<std::string, E> fromstr_map_t;

    streamable_enum()
    {}

    streamable_enum(E val) :
        Val_(val)
    {}

    operator E() {
        return Val_;
    }

    bool operator==(const streamable_enum<E>& e) {
        return this->Val_ == e.Val_;
    }

    bool operator==(const E& e) {
        return this->Val_ == e;
    }

    static const tostr_map_t& to_string_map() {
        static tostr_map_t to_str_(get_enum_strings<E>());
        return to_str_;
    }

    static const fromstr_map_t& from_string_map() {
        static fromstr_map_t from_str_(reverse_map(to_string_map()));
        return from_str_;
    }
private:
    E Val_;

    static fromstr_map_t reverse_map(const tostr_map_t& eToS) {
        fromstr_map_t sToE;
        for (auto pr : eToS) {
            sToE.emplace(pr.second, pr.first);
        }
        return sToE;
    }
};

template <typename E>
streamable_enum<E> stream_enum(E e) {
    return streamable_enum<E>(e);
}

template <typename E>
typename streamable_enum<E>::tostr_map_t get_enum_strings() {
    // \todo throw an appropriate exception or display compile error/warning
    return {};
}

template <typename E>
std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) {
    auto& mp = streamable_enum<E>::to_string_map();
    auto res = mp.find(e);
    if (res != mp.end()) {
        os << res->second;
    } else {
        os.setstate(std::ios_base::failbit);
    }
    return os;
}

template <typename E>
std::istream& operator>>(std::istream& is, streamable_enum<E>& e) {
    std::string str;
    is >> str;
    if (str.empty()) {
        is.setstate(std::ios_base::failbit);
    }
    auto& mp = streamable_enum<E>::from_string_map();
    auto res = mp.find(str);
    if (res != mp.end()) {
        e = res->second;
    } else {
        is.setstate(std::ios_base::failbit);
    }
    return is;
}

#endif

용법:

#include "streamable_enum.hpp"

using std::cout;
using std::cin;
using std::endl;

enum Animal {
    CAT,
    DOG,
    TIGER,
    RABBIT
};

template <>
streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() {
    return {
        { CAT, "Cat"},
        { DOG, "Dog" },
        { TIGER, "Tiger" },
        { RABBIT, "Rabbit" }
    };
}

int main(int argc, char* argv []) {
    cout << "What animal do you want to buy? Our offering:" << endl;
    for (auto pr : streamable_enum<Animal>::to_string_map()) {          // Use from_string_map() and pr.first instead
        cout << " " << pr.second << endl;                               // to have them sorted in alphabetical order
    }
    streamable_enum<Animal> anim;
    cin >> anim;
    if (!cin) {
        cout << "We don't have such animal here." << endl;
    } else if (anim == Animal::TIGER) {
        cout << stream_enum(Animal::TIGER) << " was a joke..." << endl;
    } else {
        cout << "Here you are!" << endl;
    }

    return 0;
}

다음은 다음과 같은 기능을 가진 매크로를 사용하는 솔루션입니다.

  1. 열거 형의 각 값을 한 번만 작성하므로 유지할 이중 목록이 없습니다.

  2. 나중에 #included되는 별도의 파일에 열거 형 값을 유지하지 마십시오. 원하는 곳에 쓸 수 있습니다.

  3. 열거 형 자체를 바꾸지 말고 열거 형 유형을 정의하고 싶지만 그 외에도 모든 열거 형 이름을 해당 문자열에 매핑 할 수 있기를 원합니다 (레거시 코드에 영향을주지 않음).

  4. 검색은 빨라야합니다. 따라서 그 거대한 열거 형에 대해서는 스위치 케이스가없는 것이 좋습니다.

https://stackoverflow.com/a/20134475/1812866


구조체와 클래스를 조정하기위한 Boost.Fusion 1과 같은 솔루션이 좋을 것이라고 생각했습니다. 그들은 심지어 enum을 융합 시퀀스로 사용하는 것을 어느 시점에서 가지고있었습니다.

그래서 열거 형을 인쇄하는 코드를 생성하기 위해 작은 매크로를 만들었습니다. 이것은 완벽하지 않으며 Boost.Fusion에서 생성 한 상용구 코드에서는 볼 수 없지만 Boost Fusion 매크로처럼 사용할 수 있습니다. 구조체 멤버의 이름을 인쇄 할 수있는이 인프라에 통합하기 위해 Boost.Fusion에 필요한 유형을 생성하고 싶지만 나중에 발생합니다. 지금은 매크로 일뿐입니다.

#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP

#include <swissarmyknife/detail/config.hpp>

#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>


#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
    R, unused, ENUMERATION_ENTRY)                                               \
    case ENUMERATION_ENTRY:                                                     \
      return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
    break;                                                                      

/**
 * \brief Adapts ENUM to reflectable types.
 *
 * \param ENUM_TYPE To be adapted
 * \param ENUMERATION_SEQ Sequence of enum states
 */
#define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ)                   \
    inline std::string to_string(const ENUM_TYPE& enum_value) {                 \
      switch (enum_value) {                                                     \
      BOOST_PP_SEQ_FOR_EACH(                                                    \
          SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C,                   \
          unused, ENUMERATION_SEQ)                                              \
        default:                                                                \
          return BOOST_PP_STRINGIZE(ENUM_TYPE);                                 \
      }                                                                         \
    }                                                                           \
                                                                                \
    inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
      os << to_string(value);                                                   \
      return os;                                                                \
    }

#endif

아래의 이전 답변은 꽤 나쁩니다. 사용하지 마십시오. :)

이전 답변 :

enums 선언 구문을 너무 많이 변경하지 않고이 문제를 해결하는 방법을 찾고 있습니다. 전처리기를 사용하여 문자열 화 된 열거 형 선언에서 문자열을 검색하는 솔루션을 찾았습니다.

다음과 같이 비 희소 열거 형을 정의 할 수 있습니다.

SMART_ENUM(State, 
    enum State {
        RUNNING,
        SLEEPING, 
        FAULT, 
        UNKNOWN
    })

그리고 다른 방식으로 그들과 상호 작용할 수 있습니다.

// With a stringstream
std::stringstream ss;
ss << State::FAULT;
std::string myEnumStr = ss.str();

//Directly to stdout
std::cout << State::FAULT << std::endl;

//to a string
std::string myStr = State::to_string(State::FAULT);

//from a string
State::State myEnumVal = State::from_string(State::FAULT);

다음 정의를 기반으로합니다.

#define SMART_ENUM(enumTypeArg, ...)                                                     \
namespace enumTypeArg {                                                                  \
    __VA_ARGS__;                                                                         \
    std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) {                 \
            os << swissarmyknife::enums::to_string(#__VA_ARGS__, val);                   \
            return os;                                                                   \
    }                                                                                    \
                                                                                     \
    std::string to_string(const enumTypeArg& val) {                                      \
            return swissarmyknife::enums::to_string(#__VA_ARGS__, val);                  \
    }                                                                                    \
                                                                                     \
    enumTypeArg from_string(const std::string &str) {                                    \
            return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str);   \
    }                                                                                    \
}                                                                                        \


namespace swissarmyknife { namespace enums {

    static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            if (enumVal == count) {
                std::string identifiersSubset = identifiers.substr(0, found);
                size_t beginId = identifiersSubset.find_last_of("{,");
                identifiersSubset = identifiersSubset.substr(beginId+1);
                boost::algorithm::trim(identifiersSubset);
                return identifiersSubset;
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("The enum declaration provided doesn't contains this state.");
    }                                                  

    template <typename EnumType>
    static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            std::string identifiersSubset = identifiers.substr(0, found);
            size_t beginId = identifiersSubset.find_last_of("{,");
            identifiersSubset = identifiersSubset.substr(beginId+1);
            boost::algorithm::trim(identifiersSubset);

            if (identifiersSubset == enumStr) {
                return static_cast<EnumType>(count);
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("No valid enum value for the provided string");
    }                      

}}

sparse enum에 대한 지원이 필요하고 더 많은 시간이있을 때 boost :: xpressive를 사용 하여 to_stringfrom_string 구현을 개선 할 것입니다. 그러나 수행되는 중요한 템플릿과 생성 된 실행 파일이 다음과 같이 컴파일 시간에 비용이 듭니다. 정말 더 클 것입니다. 그러나 이것은 추악한 수동 문자열 조작 코드보다 더 읽기 쉽고 관리하기 쉽다는 장점이 있습니다. :디

그렇지 않으면 항상 boost :: bimap을 사용하여 열거 형 값과 문자열 간의 매핑을 수행했지만 수동으로 유지 관리해야합니다.


모든 일반적인 이유로 매크로를 사용하지 않는 것을 선호하기 때문에 열거 형 선언 매크로를 자유롭게 유지하는 이점이있는보다 제한된 매크로 솔루션을 사용했습니다. 단점은 각 열거 형에 대한 매크로 정의를 복사하여 붙여넣고 열거 형에 값을 추가 할 때 매크로 호출을 명시 적으로 추가해야한다는 것입니다.

std::ostream& operator<<(std::ostream& os, provenance_wrapper::CaptureState cs)
{
#define HANDLE(x) case x: os << #x; break;
    switch (cs) {
    HANDLE(CaptureState::UNUSED)
    HANDLE(CaptureState::ACTIVE)
    HANDLE(CaptureState::CLOSED)
    }
    return os;
#undef HANDLE
}

참고 URL : https://stackoverflow.com/questions/147267/easy-way-to-use-variables-of-enum-types-as-string-in-c

반응형