C ++ 열거 형 클래스의 요소 수를 확인할 수 있습니까?


85

그것은 C ++의 중요도를 결정하는 것이 가능하다 enum class:

enum class Example { A, B, C, D, E };

을 사용하려고했지만 sizeof열거 형 요소의 크기를 반환합니다.

sizeof(Example); // Returns 4 (on my architecture)

카디널리티를 얻는 표준 방법이 있습니까 (예에서는 5)?


특정 C ++ 11 메커니즘이있을 수 있다고 생각했습니다
bquenin

6
그건 그렇고, 이것은 중복이 아닙니다. enum그리고 enum classes는 매우 다른 개념입니다.
Shoe

@Shoe ... 정말 그래?
Kyle Strand

1
이것은 XY 문제인 것 같습니다. 오래 전의 문제라는 것을 알고 있지만 왜 이렇게해야했는지 기억하십니까? enum class값을 반복 할 수 없으므로 숫자를 아는 것이 어떤 이점이 있을까요?
Fantastic Mr Fox

답변:


71

직접적으로는 아니지만 다음 트릭을 사용할 수 있습니다.

enum class Example { A, B, C, D, E, Count };

그런 다음 카디널리티를 static_cast<int>(Example::Count).

물론 이것은 열거 형의 값을 0부터 자동으로 할당하는 경우에만 잘 작동합니다. 그렇지 않은 경우 Count에 올바른 카디널리티를 수동으로 할당 할 수 있습니다. 이는 별도의 상수를 유지해야하는 것과 다르지 않습니다. 어쨌든:

enum class Example { A = 1, B = 2, C = 4, D = 8, E = 16, Count = 5 };

한 가지 단점은 컴파일러가 Example::Count열거 형 값에 대한 인수 로 사용할 수 있도록 허용한다는 것입니다. 따라서 이것을 사용하는 경우주의하십시오! (나는 개인적으로 이것이 실제로 문제가되지 않는다고 생각한다.)


1
열거 형 값은 열거 형 클래스에서 형식이 안전하므로 'Count'는 여기에서 int가 아닌 Example 형식이됩니다. 크기에 사용하려면 먼저 'Count'를 int로 캐스팅해야합니다.
Man of One Way

@Man : 예,이 트릭은 enum class일반 enums 대신 es를 사용 하면 조금 더 지저분 합니다. 명확하게 캐스트에서 편집하겠습니다.
Cameron

11
이 열거 형과 함께 switch 문을 사용하면 괜찮은 컴파일러가 하나의 케이스가 누락되었음을 경고합니다. 이것이 많이 사용되면 매우 성 가실 수 있습니다 ..이 특정 경우에는 별도의 변수를 갖는 것이 더 나을 수 있습니다.
Fantastic Mr Fox

@FantasticMrFox 경험을 바탕으로 100 % 동의합니다. 그 컴파일러 경고도 중요합니다. 나는 당신의 추천 정신에 더 부합하는 대안적인 접근법을 게시했습니다.
arr_sea

26

C ++ 17의 경우 magic_enum::enum_countlib https://github.com/Neargye/magic_enum 에서 사용할 수 있습니다 .

magic_enum::enum_count<Example>() -> 4.

단점은 어디에 있습니까?

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

주어진 간격 범위를 살펴보고 이름이있는 모든 열거를 찾습니다. 이것이 카운트가됩니다. 제한 사항 에 대해 자세히 알아보기

이 해킹에 대한 자세한 내용은이 게시물 https://taylorconor.com/blog/enum-reflection .


2
굉장합니다! 열거 형 멤버 수를 계산하기 위해 기존 코드를 수정할 필요가 없습니다. 또한 이것은 매우 우아하게 구현 된 것처럼 보입니다 (코드를 훑어 보았습니다)!
andreee 19

링크 전용 답변은 일반적으로 권장되지 않습니다. 도서관에서 사용하는 기술에 대한 설명으로이를 확장 할 수 있습니까?
Adrian McCarthy

24
constexpr auto TEST_START_LINE = __LINE__;
enum class TEST { // Subtract extra lines from TEST_SIZE if an entry takes more than one 
    ONE = 7
  , TWO = 6
  , THREE = 9
};
constexpr auto TEST_SIZE = __LINE__ - TEST_START_LINE - 3;

이것은 UglyCoder의 답변 에서 파생 되었지만 세 가지 방법으로 개선되었습니다.

  • type_safe 열거 형 ( BEGINSIZE) 에는 추가 요소가 없습니다 ( Cameron의 대답 에도이 문제가 있습니다.)
    • 컴파일러는 switch 문에서 누락되었다고 불평하지 않습니다 (중요한 문제).
    • 열거 형을 예상하는 함수에 실수로 전달할 수 없습니다. (일반적인 문제가 아님)
  • 사용을 위해 주조 할 필요가 없습니다. ( Cameron의 대답에도이 문제가 있습니다.)
  • 빼기는 enum 클래스 유형의 크기를 엉망으로 만들지 않습니다.

열거 자에 임의의 값을 할당 할 수 있다는 Cameron의 대답에 비해 UglyCoder의 이점을 유지 합니다.

문제는 ( UglyCoder 와 공유 하지만 Cameron 과는 공유 하지 않음 ) 개행과 주석을 중요하게 만든다는 것입니다. 따라서 누군가는 TEST_SIZE의 계산 을 조정하지 않고 공백이나 주석이있는 항목을 추가 할 수 있습니다 .


7
enum class TEST
{
    BEGIN = __LINE__
    , ONE
    , TWO
    , NUMBER = __LINE__ - BEGIN - 1
};

auto const TEST_SIZE = TEST::NUMBER;

// or this might be better 
constexpr int COUNTER(int val, int )
{
  return val;
}

constexpr int E_START{__COUNTER__};
enum class E
{
    ONE = COUNTER(90, __COUNTER__)  , TWO = COUNTER(1990, __COUNTER__)
};
template<typename T>
constexpr T E_SIZE = __COUNTER__ - E_START - 1;

영리한! 물론 주석이나 비정상적인 간격을 가질 수 없으며, 정말 큰 소스 파일의 경우 기본 값 유형이 다른 경우보다 클 수 있습니다.
Kyle Strand

@Kyle Strand : 그 문제가 있습니다 : char를 사용하면 256 개 이상의 열거자가 있습니다. 그러나 컴파일러는 잘림 등을 알리는 좋은 방법이 있습니다. LINE 은 정수 리터럴이고 #line 사용은 [1, 2147483647]의 제한이 있습니다.
UglyCoder

아, 그래. 그래도, 그렇지 않으면 a short가 될 열거 형조차도 int예 를 들어 유니티 빌드를 할 때 올라갈 수 있습니다 . (그래도 난 당신의 제안 트릭보다 구축이 화합에 문제의 더 말하고 싶지만.)
카일 스트랜드

장난? :-) 나는 그것을 사용하지만 드물게 적절한 판단으로 사용합니다. 코딩의 모든 것과 마찬가지로 우리는 장단점, 특히 장기적인 유지 관리 관련 사항을 개선해야합니다. 최근에 C #defines (OpenGL wglExt.h) 목록에서 열거 형 클래스를 만드는 데 사용했습니다.
UglyCoder 2016 년

5

X () 매크로를 기반으로하는 한 가지 트릭이 있습니다. 이미지에는 다음과 같은 열거 형이 있습니다.

enum MyEnum {BOX, RECT};

다음으로 다시 포맷하십시오.

#define MyEnumDef \
    X(BOX), \
    X(RECT)

그런 다음 다음 코드는 열거 형 유형을 정의합니다.

enum MyEnum
{
#define X(val) val
    MyEnumDef
#undef X
};

다음 코드는 열거 형 요소의 수를 계산합니다.

template <typename ... T> void null(T...) {}

template <typename ... T>
constexpr size_t countLength(T ... args)
{
    null(args...); //kill warnings
    return sizeof...(args);
}

constexpr size_t enumLength()
{
#define XValue(val) #val
    return countLength(MyEnumDef);
#undef XValue
}

...
std::array<int, enumLength()> some_arr; //enumLength() is compile-time
std::cout << enumLength() << std::endl; //result is: 2
...

이것은에서 쉼표를 제거하여 쉽게 만들 수 있습니다 #define MyEnumDef(그리고에 넣어 #define X(val) val그냥 사용 요소의 수를 계산 할 수있는) #define X(val) +1 constexpr std::size_t len = MyEnumDef;.
HolyBlackCat

4

시도 할 수있는 한 가지 트릭은 목록 끝에 열거 형 값을 추가하고이를 크기로 사용하는 것입니다. 귀하의 예에서

enum class Example { A, B, C, D, E, ExampleCount };

일반 enums 의 동작과 비교할 때 이것은 ExampleCount유형 그대로 작동하지 않습니다 Example. 의 요소 수를 얻으려면 Example, ExampleCount정수 유형으로 변환되어야 할 것이다.
applesoup

3

boost의 전 처리기 유틸리티를 사용하는 경우 다음을 사용하여 개수를 얻을 수 있습니다. BOOST_PP_SEQ_SIZE(...) .

예를 들어 CREATE_ENUM다음과 같이 매크로를 정의 할 수 있습니다.

#include <boost/preprocessor.hpp>

#define ENUM_PRIMITIVE_TYPE std::int32_t

#define CREATE_ENUM(EnumType, enumValSeq)                                  \
enum class EnumType : ENUM_PRIMITIVE_TYPE                                  \
{                                                                          \
   BOOST_PP_SEQ_ENUM(enumValSeq)                                           \
};                                                                         \
static constexpr ENUM_PRIMITIVE_TYPE EnumType##Count =                     \
                 BOOST_PP_SEQ_SIZE(enumValSeq);                            \
// END MACRO   

그런 다음 매크로를 호출합니다.

CREATE_ENUM(Example, (A)(B)(C)(D)(E));

다음 코드를 생성합니다.

enum class Example : std::int32_t 
{
   A, B, C, D, E 
};
static constexpr std::int32_t ExampleCount = 5;

이것은 부스트 ​​전 처리기 도구와 관련하여 표면을 긁는 것입니다. 예를 들어, 매크로는 강력한 형식의 열거 형에 대한 문자열 변환 유틸리티 및 ostream 연산자를 정의 할 수도 있습니다.

부스트 전 처리기 도구에 대한 자세한 내용은 https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html


제쳐두고, 나는 Count수락 된 답변에 사용 된 추가 열거 값이 switch문을 사용하는 경우 컴파일러 경고 두통 을 유발할 것이라는 @FantasticMrFox에 강력하게 동의 합니다. unhandled case더 안전한 코드 유지 관리에 컴파일러 경고가 매우 유용하다는 것을 알게 되었으므로이를 훼손하고 싶지 않습니다.


@FantasticMrFox 수락 된 답변과 관련된 문제를 지적 해 주셔서 감사합니다. 여기에 귀하의 추천 정신에 더 부합하는 대안적인 접근 방식을 제공했습니다.
arr_sea

2

std :: initializer_list를 사용하여 트릭으로 해결할 수 있습니다.

#define TypedEnum(Name, Type, ...)                                \
struct Name {                                                     \
    enum : Type{                                                  \
        __VA_ARGS__                                               \
    };                                                            \
    static inline const size_t count = []{                        \
        static Type __VA_ARGS__; return std::size({__VA_ARGS__}); \
    }();                                                          \
};

용법:

#define Enum(Name, ...) TypedEnum(Name, int, _VA_ARGS_)
Enum(FakeEnum, A = 1, B = 0, C)

int main()
{
    std::cout << FakeEnum::A     << std::endl
              << FakeEnun::count << std::endl;
}

2

줄 수 또는 템플릿에 의존하지 않는 또 다른 방법이 있습니다. 유일한 요구 사항은 enum 값을 자체 파일에 고정하고 전 처리기 / 컴파일러가 다음과 같이 계산하도록 만드는 것입니다.

my_enum_inc.h

ENUMVAL(BANANA)
ENUMVAL(ORANGE=10)
ENUMVAL(KIWI)
...
#undef ENUMVAL

my_enum.h

typedef enum {
  #define ENUMVAL(TYPE) TYPE,
  #include "my_enum_inc.h"
} Fruits;

#define ENUMVAL(TYPE) +1
const size_t num_fruits =
  #include "my_enum_inc.h"
  ;

이를 통해 열거 형 값으로 주석을 달고 값을 다시 할당 할 수 있으며 코드에서 무시 / 설명해야하는 잘못된 'count'열거 형 값을 삽입하지 않습니다.

댓글에 관심이 없다면 추가 파일이 필요하지 않으며 위에서 언급 한 사람처럼 할 수 있습니다. 예 :

#define MY_ENUM_LIST \
    ENUMVAL(BANANA) \
    ENUMVAL(ORANGE = 7) \
    ENUMVAL(KIWI)

그리고 교체 #include "my_enum_inc.h"MY_ENUM_LIST와 지시를하지만 당신은해야합니다 #undef ENUMVAL사용 후.


1

이에 대한 또 다른 종류의 "어리석은"해결책은 다음과 같습니다.

enum class Example { A, B, C, D, E };

constexpr int ExampleCount = [] {
  Example e{};
  int count = 0;
  switch (e) {
    case Example::A:
      count++;
    case Example::B:
      count++;
    case Example::C:
      count++;
    case Example::D:
      count++;
    case Example::E:
      count++;
  }

  return count;
}();

이것을 컴파일하면 -Werror=switch스위치 케이스를 생략하거나 복제하면 컴파일러 경고가 표시됩니다. 또한 constexpr이므로 컴파일 타임에 계산됩니다.

그러나 en enum class의 경우에도 enum의 첫 번째 값이 0이 아니더라도 기본 초기화 값은 0입니다. 따라서 0에서 시작하거나 명시 적으로 첫 번째 값을 사용해야합니다.


0

아니요, 코드에 작성해야합니다.


0

static_cast<int>(Example::E) + 1추가 요소를 제거하는 것도 고려할 수 있습니다 .


8
이 대답은이 특정 프로그래밍 문제에 대해 정확하지만 일반적으로 우아하고 오류가 발생하기 쉬운 것과는 거리가 멀습니다. 열거 형은 나중에 Example::E열거 형의 마지막 값으로 대체 할 수있는 새 값으로 확장 할 수 있습니다 . 그렇지 않더라도 Example::E의 리터럴 값이 변경 될 수 있습니다.
Matthias

0

반사 TS : 열거 형 (및 기타 유형)의 정적 반사

Reflection TS , 특히 최신 버전의 Reflection TS 드래프트의 [reflect.ops.enum] / 2 는 다음 get_enumerators TransformationTrait작업을 제공합니다 .

[reflect.ops.enum] / 2

template <Enum T> struct get_enumerators

의 모든 전문화 get_enumerators<T>TransformationTrait요구 사항 (20.10.1)을 충족해야합니다 . 이름이 type지정된 중첩 유형은에 의해 반영된 열거 유형의 열거자를 ObjectSequence충족 Enumerator시키고 반영 하는 요소를 포함하는 만족하는 메타 오브젝트 유형을 지정합니다 T.

초안의 [reflect.ops.objseq]는 ObjectSequence작업을 다루며 , 특히 [reflect.ops.objseq] / 1은 다음을 get_size충족하는 메타 객체에 대한 요소 수를 추출하는 특성을 다룹니다 ObjectSequence.

[reflect.ops.objseq] / 1

template <ObjectSequence T> struct get_size;

의 모든 전문화는 기본 특성이 인 요구 사항 (20.10.1)을 get_size<T>충족해야합니다. UnaryTypeTrait여기서은 객체 시퀀스의 요소 수입니다.integral_constant<size_t, N>N

따라서 Reflection TS에서는 현재 형식으로 수용되고 구현되어야했으며, 컴파일 타임에 다음과 같이 열거 형의 요소 수를 계산할 수 있습니다.

enum class Example { A, B, C, D, E };

using ExampleEnumerators = get_enumerators<Example>::type;

static_assert(get_size<ExampleEnumerators>::value == 5U, "");

우리는 가능성이 별칭 템플릿을 볼 수있는 곳 get_enumerators_vget_type_v추가로 반사를 단순화하기 위해 :

enum class Example { A, B, C, D, E };

using ExampleEnumerators = get_enumerators_t<Example>;

static_assert(get_size_v<ExampleEnumerators> == 5U, "");

반사 TS에 대한 상태

Herb Sutter의 여행 보고서 : 2018 년 6 월 9 일 ISO C ++위원회 여름 회의에서 여름 ISO C ++ 표준 회의 (Rapperswil)에 명시된 바와 같이 Reflection TS는 기능 완성으로 선언되었습니다.

Reflection TS는 기능 완성입니다 . Reflection TS는 기능 완성 으로 선언되었으며 여름 동안 주요 의견 투표를 위해 발송되었습니다. TS의 현재 템플릿 메타 프로그래밍 기반 구문은 자리 표시 자일뿐입니다. 요청 된 피드백은 디자인의 핵심 "배짱"에 있으며위원회는 표면 구문을 <>메타 프로그래밍이 아닌 일반적인 컴파일 타임 코드를 사용하는 더 간단한 프로그래밍 모델로 대체하려는 의도를 이미 알고 있습니다 .

되었다 처음에 C ++ (20)에 대한 계획이었습니다 만, 반사 TS는 여전히 C ++ 20 버전에 그것을 만들 수있는 기회를 가질 것입니다 경우는 다소 불분명하다.

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