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


87

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

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

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

답변:


14

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


이를 확장하면 실제로 선형 증가 목록 인 경우 편집기의 매크로 도구를 사용하여 각 이름을 기록하고 문자열로 확인할 수 있습니다. 약간의 추가 입력이 필요하며 처음에 정의 할 필요가 없습니다. 복사 된 매크로의 마지막에 기록을 클릭하고 뒤에 따옴표를 추가 한 다음 다음 줄의 같은 위치로 진행합니다. 나는 정지를 누른다. 나는 실행을 X 번 누르고 많은 수 (또는 한 단계 만)를 수행합니다. 그런 다음 문자열 배열로 래핑 할 수 있습니다.
user2262111

70

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와 유사하지만,이 솔루션이 더 우아하다고 생각합니다. 새로운 enum을 정의해야 할 때 전혀 건드리지 않는 것이므로 새로운 enum 정의는 훨씬 짧고 깔끔합니다.


2
나는 확실히 당신이 더 잘 말할 수있는 방법을 모르겠어요 / X-매크로보다 더 -이 X-매크로. (가) SOME_ENUM(XX)정확하게 X 매크로이다 (엄밀하게는 통과 "사용자 양식" XX사용하지 않고 기능 #def #undef) 다음에 전체 X-MACRO이어서이를 사용 DEFINE_ENUM에 전달되는 회전. 솔루션에서 아무것도 빼지 않으려면 잘 작동합니다. X 매크로를 사용한다는 것을 명확히하기 위해서입니다.
BeeOnRope

1
@BeeOnRope 유의 한 차이점은 중요하며이 솔루션을 관용적 X 매크로 (예 : Wikipedia의 예 ) 와 구별합니다 . 전달의 장점 XX재 위에 #defineING는 이전 패턴 매크로 확장에 이용 될 수 있다는 것이다. 이 솔루션만큼 간결한 유일한 솔루션은 새 열거 형을 정의하기 위해 별도의 파일을 만들고 다중 포함해야합니다.
pmttavara

1
또 다른 트릭은 열거 형 이름 매크로 이름 으로 사용하는 것 입니다. 당신은 단순히 쓸 수 있습니다 #define DEFINE_ENUM(EnumType) ..., 교체 ENUM_DEF(...)EnumType(...), 사용자의 말을 #define SomeEnum(XX) .... C 전처리 기는 SomeEnum괄호가 뒤 따르면 매크로 호출로 확장 되고 그렇지 않으면 일반 토큰으로 확장됩니다. (사용자가 사용하기를 좋아하는 경우 물론,이 문제가 발생 SomeEnum(2)하기보다는 열거 형에 캐스트에 (SomeEnum)2static_cast<SomeEnum>(2).)
pmttavara

1
@pmttavara - 빠른 검색이 X-매크로의 표시를 가장 많이 사용 인 경우 확인과 함께 고정 된 내부 매크로 이름을 사용 #define하고 #undef. "사용자 양식"(예 : 이 기사 하단에 안됨)이 x- 매크로 유형 이라는 데 동의하지 않습니까? 나는 그것을 항상 x- 매크로라고 불렀고, 최근에 있었던 C 코드베이스에서 가장 일반적인 형태입니다 (분명히 편향된 관찰입니다). 그래도 OP를 잘못 구문 분석했을 수 있습니다.
BeeOnRope

2
@BeeOnRope 그 당시에는 x- 매크로라고 확신했듯이 현재 표현은 편집의 결과입니다. 당시에는 덜 사용 된 형식 (또는 기사에서 적어도 하나는 덜 언급 됨)이더라도 말입니다.
Suma

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

3
이것은 cpp가 만들어진 것입니다. +1.
Derrick Turk

6
이것은 좋은 대답입니다. 특별한 도구를 사용하지 않고도 할 수있는 최선의 방법 인 것 같습니다. 그리고 저는 이전에 이런 종류의 일을 해본 적이 있습니다. 하지만 여전히 '올바른'느낌이 들지 않고 저는이 일을 정말 좋아하지 않습니다 ...
Michael Burr

작은 변화 : #define ENUM_END(typ) }; extern const char * typ ## _name_table[];에서 defs.h파일 -이 당신이 그것을 사용하는 파일에 이름 테이블을 선언합니다. (그래도 테이블 크기를 선언하는 좋은 방법을 찾을 수 없습니다.) 또한 개인적으로 마지막 세미콜론을 생략하고 싶지만 장점은 어느 쪽이든 논란의 여지가 있습니다.
Chris Lutz

1
@ 빌, 왜 typ줄에 귀찮게 #define ENUM_END(typ) };?
Pacerier

내 매크로 "ONE = 5"로 정의 할 위치이 작동하지 않습니다
UKMonkey

13

이를 수행하는 방법이 있습니다. 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 매크로를 사용하여 문자열 배열 및 열거 형 인덱스 생성)이 있지만 가장 간단한 데모입니다.


8

몇 가지 확실한 답이 있다는 것을 알고 있지만 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]);
}

char const *kConstStr[]
Anne van Rossum

6

자주 필요했지만 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 문이 있으면 이러한 문제를 해결할 수 있습니다. 이것은 당신이 다른 것없이 하나를 변경할 가능성이 적다는 것을 의미합니다.


6

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

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


4

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

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는 완전히 이식 가능하지 않을 수 있습니다.


3

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

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

2

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

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

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

enum_to_string_map[enum1]

1

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 전처리기를 세계에서 가장 깔끔하게 사용하는 것은 아니지만 자료를 여러 번 작성하는 것을 방지합니다.



0

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



0

나는 간단한 템플릿 클래스를 만들었 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;
}

0

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

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

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

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

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

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


0

구조체와 클래스를 조정하기위한 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

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

이전 답변 :

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

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

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

}}

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

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


0

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

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
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.