Có thể xác định số phần tử của một lớp enum c ++ không?


84

Có thể xác định bản số của c ++ không enum class:

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

Tôi đã cố gắng sử dụng sizeof, tuy nhiên, nó trả về kích thước của một phần tử enum.

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

Có cách tiêu chuẩn nào để lấy cardinality (5 trong ví dụ của tôi) không?


Tôi nghĩ rằng có thể đã là một cụ c ++ 11 cơ chế
bquenin

6
Nhân tiện, đây không phải là một bản sao. enumenum classes là những khái niệm rất khác nhau.
Giày

@Shoe ... họ có thực sự không?
Kyle Strand

1
Đây có vẻ như là một vấn đề XY, tôi biết nó đã xảy ra từ lâu, nhưng bạn có nhớ tại sao bạn cần làm điều này không? Bạn không thể lặp lại một enum classgiá trị, vì vậy sẽ có ích gì khi biết số đó?
Fantastic Mr Fox

Câu trả lời:


70

Không trực tiếp, nhưng bạn có thể sử dụng thủ thuật sau:

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

Sau đó, cardinality có sẵn dưới dạng static_cast<int>(Example::Count).

Tất nhiên, điều này chỉ hoạt động tốt nếu bạn để các giá trị của enum được gán tự động, bắt đầu từ 0. Nếu không phải như vậy, bạn có thể gán thẻ số chính xác cho Count theo cách thủ công, điều này thực sự không khác gì việc phải duy trì một hằng số riêng biệt dù sao:

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

Một nhược điểm là trình biên dịch sẽ cho phép bạn sử dụng Example::Countlàm đối số cho giá trị enum - vì vậy hãy cẩn thận nếu bạn sử dụng điều này! (Cá nhân tôi thấy điều này không phải là một vấn đề trong thực tế.)


1
Các giá trị enum là kiểu an toàn trong một lớp enum nên 'Count' sẽ thuộc kiểu Ví dụ ở đây chứ không phải kiểu int, phải không? Trước tiên, bạn sẽ phải ép kiểu 'Count' thành một int để sử dụng nó cho kích thước.
Man of One Way

@Man: Vâng, thủ thuật này hơi lộn xộn hơn với enum classes thay vì enums đơn giản . Tôi sẽ chỉnh sửa trong một diễn viên để rõ ràng.
Cameron

11
Nếu bạn sử dụng câu lệnh switch với enum này, bất kỳ trình biên dịch tốt nào cũng sẽ cảnh báo bạn rằng bạn đang thiếu một trường hợp. Nếu điều này được sử dụng nhiều có thể rất khó chịu .. Có thể tốt hơn là chỉ có một biến riêng biệt trong trường hợp cụ thể này.
Fantastic Mr Fox

@FantasticMrFox Tôi đồng ý 100%, dựa trên kinh nghiệm. Cảnh báo trình biên dịch đó cũng là một cảnh báo quan trọng. Tôi đã đăng một cách tiếp cận thay thế, phù hợp hơn với tinh thần đề xuất của bạn.
arr_sea

26

Đối với C ++ 17, bạn có thể sử dụng magic_enum::enum_counttừ lib https://github.com/Neargye/magic_enum :

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

Hạn chế là ở đâu?

Thư viện này sử dụng một bản hack dành riêng cho trình biên dịch (dựa trên __PRETTY_FUNCTION__/ __FUNCSIG__), hoạt động trên Clang> = 5, MSVC> = 15.3 và GCC> = 9.

Chúng tôi đi qua phạm vi khoảng thời gian đã cho, và tìm tất cả các kiểu liệt kê có tên, đây sẽ là số lượng của chúng. Đọc thêm về các giới hạn

Nhiều thông tin khác về vụ hack này trong bài đăng này https://taylorconor.com/blog/enum-reflection .


2
Điều này thật tuyệt! Không cần phải sửa đổi mã hiện có để đếm số lượng thành viên enum. Ngoài ra, điều này có vẻ được triển khai rất thanh lịch (chỉ cần lướt qua mã)!
andreee

Các câu trả lời chỉ có liên kết thường không được khuyến khích. Bạn có thể mở rộng điều này với mô tả về kỹ thuật mà thư viện của bạn sử dụng không?
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;

Điều này bắt nguồn từ câu trả lời của UglyCoder nhưng cải thiện nó theo ba cách.

  • Không có phần tử bổ sung nào trong enum type_safe ( BEGINSIZE) ( câu trả lời của Cameron cũng có vấn đề này.)
    • Trình biên dịch sẽ không phàn nàn về việc chúng bị thiếu trong một câu lệnh switch (một vấn đề quan trọng)
    • Chúng không thể vô tình được chuyển đến các hàm mong đợi enum của bạn. (không phải là vấn đề chung)
  • Nó không yêu cầu đúc để sử dụng. ( Câu trả lời của Cameron cũng có vấn đề này.)
  • Phép trừ không gây rối với kích thước của kiểu lớp enum.

Nó giữ lại lợi thế của UglyCoder so với câu trả lời của Cameron rằng các điều tra viên có thể được gán các giá trị tùy ý.

Một vấn đề (được chia sẻ với UglyCoder nhưng không phải với Cameron ) là nó làm cho các dòng mới và nhận xét có ý nghĩa ... điều không thể ngờ tới. Vì vậy, ai đó có thể thêm một mục nhập có khoảng trắng hoặc nhận xét mà không cần điều chỉnh TEST_SIZEtính toán.


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;

Tài giỏi! Tất nhiên, không thể có bất kỳ nhận xét hoặc khoảng cách bất thường nào và đối với các tệp nguồn thực sự lớn, loại giá trị cơ bản có thể lớn hơn so với cách khác.
Kyle Strand

@Kyle Strand: có vấn đề đó là: sử dụng char và bạn cũng có hơn 256 điều tra viên. Nhưng trình biên dịch có cách cư xử tốt để thông báo cho bạn về những sự cắt, vv ĐƯỜNG DÂY là một literal số nguyên và sử dụng #line có một giới hạn của [1, 2147483647]
UglyCoder

À được rồi. Tuy nhiên, ngay cả một enum mà nếu không phải là một shortcó thể được đưa lên intví dụ khi thực hiện xây dựng thống nhất. (Tôi muốn nói đây là chi tiết của một vấn đề với đoàn kết xây dựng hơn so với đề xuất của bạn lừa, mặc dù.)
Kyle Strand

Lừa? :-) Tôi sử dụng nó, nhưng hiếm khi và với sự phán xét thích đáng. Giống như mọi thứ trong mã hóa, chúng ta cần tìm ra những ưu và khuyết điểm và đặc biệt là các tác động duy trì lâu dài. Gần đây tôi đã sử dụng nó để tạo một lớp enum từ danh sách C #defines (OpenGL wglExt.h).
UglyCoder

5

Có một thủ thuật dựa trên X () - macro: image, bạn có enum sau:

enum MyEnum {BOX, RECT};

Định dạng lại nó thành:

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

Sau đó, đoạn mã sau định nghĩa kiểu enum:

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

Và đoạn mã sau đây tính số phần tử enum:

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

Điều này có thể được thực hiện dễ dàng hơn bằng cách xóa dấu phẩy khỏi #define MyEnumDef(và đặt nó vào #define X(val) val), điều này cho phép bạn đếm số phần tử chỉ bằng cách sử dụng #define X(val) +1 constexpr std::size_t len = MyEnumDef;.
HolyBlackCat

4

Một mẹo bạn có thể thử là thêm một giá trị enum vào cuối danh sách của bạn và sử dụng nó làm kích thước. Trong ví dụ của bạn

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

So với hành vi với enums đơn giản , điều này sẽ không hoạt động như ExampleCountlà loại Example. Để có được số phần tử trong Example, ExampleCountsẽ phải được ép kiểu sang kiểu số nguyên.
appleoup

3

Nếu bạn sử dụng các tiện ích tiền xử lý của boost, bạn có thể nhận được số lượng bằng cách sử dụng BOOST_PP_SEQ_SIZE(...).

Ví dụ, người ta có thể xác định CREATE_ENUMmacro như sau:

#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   

Sau đó, gọi macro:

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

sẽ tạo ra mã sau:

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

Điều này chỉ làm trầy xước bề mặt liên quan đến các công cụ tăng cường tiền xử lý. Ví dụ: macro của bạn cũng có thể xác định đến / từ các tiện ích chuyển đổi chuỗi và toán tử ostream cho enum được gõ mạnh của bạn.

Thông tin thêm về các công cụ tiền xử lý tăng cường tại đây: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendA-AnIntroductiontiontoPreprocessorMetaprogramming.html


Ngoài ra, tôi hoàn toàn đồng ý với @FantasticMrFox rằng Countgiá trị được liệt kê bổ sung được sử dụng trong câu trả lời được chấp nhận sẽ tạo ra cảnh báo trình biên dịch đau đầu hơn nếu sử dụng một switchcâu lệnh. Tôi thấy unhandled casecảnh báo trình biên dịch khá hữu ích để bảo trì mã an toàn hơn, vì vậy tôi không muốn phá hoại nó.


@FantasticMrFox Cảm ơn bạn đã chỉ ra một vấn đề liên quan với câu trả lời được chấp nhận. Tôi đã cung cấp một cách tiếp cận thay thế ở đây phù hợp hơn với tinh thần đề xuất của bạn.
arr_sea

2

Nó có thể được giải quyết bằng một thủ thuật với 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__}); \
    }();                                                          \
};

Sử dụng:

#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

Có một cách khác không dựa vào số lượng dòng hoặc mẫu. Yêu cầu duy nhất là gắn các giá trị enum trong tệp riêng của chúng và làm cho bộ tiền xử lý / trình biên dịch thực hiện đếm như vậy:

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

Điều này cho phép bạn đặt các nhận xét với các giá trị enum, gán lại các giá trị và không đưa giá trị enum 'đếm' không hợp lệ cần được bỏ qua / tính toán trong mã.

Nếu bạn không quan tâm đến nhận xét, bạn không cần tệp bổ sung và có thể làm như ai đó ở trên đã đề cập, ví dụ:

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

và thay thế các #include "my_enum_inc.h"chỉ thị bằng MY_ENUM_LIST nhưng bạn sẽ cần #undef ENUMVALsau mỗi lần sử dụng.


1

Một loại giải pháp "ngu ngốc" khác cho điều này là:

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

Bằng cách biên dịch này, -Werror=switchbạn đảm bảo nhận được cảnh báo trình biên dịch nếu bạn bỏ qua hoặc sao chép bất kỳ trường hợp chuyển đổi nào. Nó cũng là constexpr nên điều này được tính toán tại thời điểm biên dịch.

Nhưng lưu ý rằng ngay cả đối với en enum class, giá trị khởi tạo mặc định là 0 ngay cả khi giá trị đầu tiên của enum không phải là 0. Vì vậy, bạn phải bắt đầu bằng 0 hoặc sử dụng giá trị đầu tiên một cách rõ ràng.


0

Không, bạn phải viết nó trong mã.


0

Bạn cũng có thể xem xét static_cast<int>(Example::E) + 1loại bỏ yếu tố thừa.


8
Câu trả lời này đúng cho vấn đề lập trình cụ thể này, nhưng nói chung là xa lạ và dễ xảy ra lỗi. Enum có thể được mở rộng với các giá trị mới trong tương lai có thể thay thế Example::Enhư giá trị cuối cùng trong enum. Ngay cả khi đây không phải là trường hợp, Example::Egiá trị theo nghĩa đen của có thể thay đổi.
Matthias

0

Reflection TS: phản xạ tĩnh của enum (và các loại khác)

Reflection TS , đặc biệt là [Reflection.ops.enum] / 2 của phiên bản mới nhất của bản thảo Reflection TS cung cấp get_enumerators TransformationTraitthao tác:

[phản ánh.ops.enum] / 2

template <Enum T> struct get_enumerators

Tất cả các chuyên môn của get_enumerators<T>phải đáp ứng các TransformationTraityêu cầu (20.10.1). Kiểu lồng nhau có tên typechỉ định kiểu siêu đối tượng thỏa mãn ObjectSequence, chứa các phần tử đáp ứng Enumeratorvà phản ánh các đối tượng của kiểu liệt kê được phản ánh bởi T.

[phản ánh.ops.objseq] của bản nháp bao gồm các ObjectSequencehoạt động, trong đó đặc biệt là [phản ánh.ops.objseq] / 1 bao gồm get_sizeđặc điểm trích xuất số lượng phần tử cho một siêu đối tượng đáp ứng ObjectSequence:

[phản ánh.ops.objseq] / 1

template <ObjectSequence T> struct get_size;

Tất cả các chuyên ngành của get_size<T>phải đáp ứng các UnaryTypeTraityêu cầu (20.10.1) với một đặc tính cơ bản của integral_constant<size_t, N>, nơi Nlà số phần tử trong dãy đối tượng.

Do đó, trong Reflection TS đã được chấp nhận và thực hiện ở dạng hiện tại của nó, số phần tử của một enum có thể được tính toán, tại thời điểm biên dịch, như sau:

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

using ExampleEnumerators = get_enumerators<Example>::type;

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

nơi chúng ta có thể thấy các mẫu bí danh get_enumerators_vget_type_vđể đơn giản hóa phản ánh hơn nữa:

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

using ExampleEnumerators = get_enumerators_t<Example>;

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

Trạng thái phản ánh TS

Như đã nêu trong báo cáo Chuyến đi của Herb Sutter : Cuộc họp tiêu chuẩn ISO C ++ mùa hè (Rapperswil) từ cuộc họp mùa hè của ủy ban ISO C ++ ngày 9 tháng 6 năm 2018, Reflection TS đã được tuyên bố là hoàn chỉnh về tính năng

Reflection TS đã hoàn chỉnh về tính năng : Reflection TS đã được tuyên bố là đã hoàn thành về tính năng và đang được gửi đi để bỏ phiếu bình luận chính trong mùa hè. Lưu ý một lần nữa rằng cú pháp dựa trên lập trình siêu chương trình mẫu hiện tại của TS chỉ là một trình giữ chỗ; phản hồi được yêu cầu nằm ở “ruột” cốt lõi của thiết kế và ủy ban đã biết họ có ý định thay thế cú pháp bề mặt bằng một mô hình lập trình đơn giản hơn sử dụng mã thời gian biên dịch thông thường và <>lập trình siêu mẫu không theo kiểu.

ban đầu được lên kế hoạch cho C ++ 20 , nhưng không rõ liệu Reflection TS có còn cơ hội đưa nó vào bản phát hành C ++ 20 hay không.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.