เป็นไปได้หรือไม่ที่จะกำหนดจำนวนองค์ประกอบของคลาส c ++ enum


86

เป็นไปได้หรือไม่ที่จะกำหนดจำนวนสมาชิกของ c ++ enum class :

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

ฉันพยายามที่จะใช้ sizeofอย่างไรก็ตามมันส่งคืนขนาดขององค์ประกอบ enum

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

มีวิธีมาตรฐานในการรับคาร์ดินาลลิตี้ (5 ในตัวอย่างของฉัน) หรือไม่


ฉันคิดว่าอาจมีกลไก c ++ 11 ที่เฉพาะเจาะจง
bquenin

6
นี่ไม่ใช่การทำซ้ำ แต่อย่างใด enumและenum classes เป็นแนวคิดที่แตกต่างกันมาก
รองเท้า

@ รองเท้า ... พวกเขาจริงเหรอ?
Kyle Strand

1
นี่ดูเหมือนปัญหา XY ฉันรู้ว่ามันเกิดมานานแล้ว แต่คุณจำได้ไหมว่าทำไมคุณต้องทำสิ่งนี้? คุณไม่สามารถวนซ้ำenum classค่าต่างๆได้ดังนั้นประโยชน์ที่จะได้รับในการรู้ตัวเลขคืออะไร?
Fantastic Mr Fox

คำตอบ:


73

ไม่โดยตรง แต่คุณสามารถใช้เคล็ดลับต่อไปนี้:

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

จากนั้น cardinality static_cast<int>(Example::Count)ที่มีอยู่เป็น

แน่นอนว่าจะได้ผลดีก็ต่อเมื่อคุณปล่อยให้ค่าของ enum ถูกกำหนดโดยอัตโนมัติโดยเริ่มจาก 0 หากไม่เป็นเช่นนั้นคุณสามารถกำหนดจำนวนสมาชิกที่ถูกต้องให้กับ Count ได้ด้วยตนเองซึ่งไม่แตกต่างจากการต้องรักษาค่าคงที่แยกต่างหาก อย่างไรก็ตาม:

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

ข้อเสียอย่างหนึ่งคือคอมไพเลอร์จะอนุญาตให้คุณใช้Example::Countเป็นอาร์กิวเมนต์สำหรับค่า enum - ดังนั้นโปรดระวังหากคุณใช้สิ่งนี้! (โดยส่วนตัวแล้วฉันคิดว่านี่ไม่ใช่ปัญหาในทางปฏิบัติ)


1
ค่า enum เป็นประเภทที่ปลอดภัยในคลาส enum ดังนั้น 'Count' จะเป็นประเภท Example ที่นี่ไม่ใช่ int ใช่ไหม คุณจะต้องแคสต์ 'Count' เป็น int ก่อนเพื่อที่จะใช้เป็นขนาด
Man of One Way

@ แมน: ใช่เคล็ดลับนี้ค่อนข้างยุ่งกับenum classes แทนที่จะenumเป็น s ธรรมดา ฉันจะแก้ไขในการแคสต์ให้ชัดเจน
Cameron

11
หากคุณใช้คำสั่ง switch กับ enum นี้คอมไพเลอร์ที่เหมาะสมจะเตือนคุณว่าคุณทำเคสหายไป หากมีการใช้มากจนน่ารำคาญ .. อาจจะดีกว่าถ้ามีตัวแปรคั่นในกรณีเฉพาะนี้
Fantastic Mr Fox

@FantasticMrFox ฉันเห็นด้วย 100% ขึ้นอยู่กับประสบการณ์ คำเตือนของคอมไพเลอร์นั้นเป็นสิ่งสำคัญเช่นกัน ฉันได้โพสต์วิธีการอื่นซึ่งสอดคล้องกับเจตนารมณ์ของคำแนะนำของคุณ
arr_sea

28

สำหรับ C ++ 17 คุณสามารถใช้ได้magic_enum::enum_countจาก lib https://github.com/Neargye/magic_enum :

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

ข้อเสียเปรียบอยู่ที่ไหน?

ไลบรารีนี้ใช้แฮ็คเฉพาะคอมไพเลอร์ (อ้างอิงจาก__PRETTY_FUNCTION__/__FUNCSIG__ ) ซึ่งทำงานบน Clang> = 5, MSVC> = 15.3 และ GCC> = 9

เราผ่านช่วงช่วงเวลาที่กำหนดและค้นหาการแจงนับทั้งหมดที่มีชื่อซึ่งจะเป็นการนับ อ่านเพิ่มเติมเกี่ยวกับข้อ จำกัด

อื่น ๆ อีกมากมายเกี่ยวกับการสับนี้ในโพสต์นี้https://taylorconor.com/blog/enum-reflection


2
นี่มันเจ๋งมาก! ไม่จำเป็นต้องแก้ไขรหัสที่มีอยู่เพื่อนับจำนวนสมาชิก enum นอกจากนี้ดูเหมือนว่าจะใช้งานได้อย่างหรูหรามาก (เพียงแค่อ่านโค้ด)!
andreee

โดยทั่วไปคำตอบแบบลิงก์เท่านั้น คุณช่วยขยายคำอธิบายเทคนิคที่ห้องสมุดของคุณใช้ได้ไหม
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 enum ( BEGINและSIZE) ( คำตอบของคาเมรอนก็มีปัญหานี้เช่นกัน)
    • คอมไพเลอร์จะไม่บ่นว่าพวกเขาหายไปจากคำสั่ง switch (ปัญหาสำคัญ)
    • ไม่สามารถส่งผ่านไปยังฟังก์ชันที่คาดหวังจาก enum ของคุณโดยไม่ได้ตั้งใจ (ไม่ใช่ปัญหาทั่วไป)
  • ไม่จำเป็นต้องมีการหล่อเพื่อใช้งาน ( คำตอบของคาเมรอนก็มีปัญหานี้เช่นกัน)
  • การลบไม่ยุ่งกับขนาดของประเภทคลาส enum

มันยังคงได้เปรียบ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: มีปัญหาคือการใช้ถ่านและคุณมีตัวนับมากกว่า 256 ตัวด้วย แต่คอมไพเลอร์มีมารยาทที่ดีในการแจ้งให้คุณทราบถึงการตัดทอนเป็นต้นLINEเป็นตัวอักษรจำนวนเต็มและการใช้ #line มีขีด จำกัด [1, 2147483647]
UglyCoder

อ่าโอเค. ถึงกระนั้นแม้แต่ enum ที่เป็นอย่างอื่นshortก็อาจได้รับผลintกระทบเช่นเมื่อสร้างความสามัคคี (ฉันจะบอกว่านี่เป็นปัญหาเกี่ยวกับการสร้างเอกภาพมากกว่าเคล็ดลับที่คุณเสนอ)
Kyle Strand

เคล็ดลับ? :-) ฉันใช้ แต่ไม่ค่อยและด้วยวิจารณญาณ เช่นเดียวกับทุกสิ่งในการเขียนโค้ดเราจำเป็นต้องหาข้อดีข้อเสียและโดยเฉพาะอย่างยิ่งผลกระทบในการบำรุงรักษาในระยะยาว ฉันเพิ่งใช้มันเพื่อสร้างคลาส enum จากรายการ C #defines (OpenGL wglExt.h)
UglyCoder

5

มีเคล็ดลับอย่างหนึ่งตาม X () - มาโคร: รูปภาพคุณมี enum ต่อไปนี้:

enum MyEnum {BOX, RECT};

ฟอร์แมตใหม่เป็น:

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

จากนั้นรหัสต่อไปนี้กำหนดประเภท enum:

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

และรหัสต่อไปนี้จะคำนวณจำนวนองค์ประกอบ 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
...

นี้สามารถทำได้ง่ายขึ้นโดยการเอาเครื่องหมายจุลภาคจาก#define MyEnumDef(และวางไว้ใน#define X(val) val) #define X(val) +1 constexpr std::size_t len = MyEnumDef;ซึ่งช่วยให้คุณสามารถนับจำนวนขององค์ประกอบที่ใช้เพียง
HolyBlackCat

4

เคล็ดลับอย่างหนึ่งที่คุณสามารถลองทำได้คือเพิ่มค่า enum ที่ท้ายรายการของคุณและใช้เป็นขนาด ในตัวอย่างของคุณ

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

1
เมื่อเทียบกับพฤติกรรมธรรมดาenums นี้จะไม่ทำงานตามที่เป็นประเภทExampleCount Exampleที่จะได้รับจำนวนขององค์ประกอบในExample, ExampleCountจะต้องมีการส่งข้อมูลไปยังชนิดจำนวนเต็ม
applesoup

3

หากคุณใช้ยูทิลิตีพรีโปรเซสเซอร์ของบูสต์คุณสามารถรับการนับได้โดยใช้ 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 สำหรับ enum ที่คุณพิมพ์มากเกินไป

เพิ่มเติมเกี่ยวกับเครื่องมือเร่งกระบวนการก่อนหน้านี้: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html


นอกจากนี้ฉันเห็นด้วยอย่างยิ่งกับ @FantasticMrFox ว่าCountค่าที่ระบุเพิ่มเติมที่ใช้ในคำตอบที่ยอมรับจะสร้างอาการปวดหัวคำเตือนของคอมไพเลอร์มากมายหากใช้switchคำสั่ง ฉันพบว่า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"
  ;

สิ่งนี้ช่วยให้คุณสามารถใส่ความคิดเห็นที่มีค่า enum กำหนดค่าใหม่และไม่แทรกค่า enum '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 ดังนั้นจึงคำนวณในเวลาคอมไพล์

แต่โปรดทราบว่าแม้ว่าenum classค่าเริ่มต้นเริ่มต้นจะเป็น 0 แม้ว่าค่าแรกของ enum จะไม่ใช่ 0 ก็ตามดังนั้นคุณต้องเริ่มต้นที่ 0 หรือใช้ค่าแรกอย่างชัดเจน


0

ไม่คุณต้องเขียนลงในโค้ด


0

คุณยังสามารถพิจารณาว่าstatic_cast<int>(Example::E) + 1จะกำจัดองค์ประกอบพิเศษใดออกไป


8
คำตอบนี้ถูกต้องสำหรับปัญหาการเขียนโปรแกรมเฉพาะนี้ แต่โดยทั่วไปแล้วยังห่างไกลจากความสง่างามและข้อผิดพลาด enum สามารถขยายด้วยค่าใหม่ในอนาคตที่สามารถแทนที่Example::Eเป็นค่าสุดท้ายใน enum แม้ว่าจะไม่ใช่กรณีนี้Example::Eค่าตัวอักษรของอาจเปลี่ยนแปลงได้
Matthias

0

Reflection TS: การสะท้อนคงที่ของ enums (และประเภทอื่น ๆ )

Reflection TSโดยเฉพาะ [reflect.ops.enum] / 2 ของร่าง Reflection TS เวอร์ชันล่าสุดนำเสนอการget_enumerators TransformationTraitดำเนินการ:

[reflect.ops.enum] / 2

template <Enum T> struct get_enumerators

ความเชี่ยวชาญทั้งหมดget_enumerators<T>ต้องเป็นไปตาม TransformationTraitข้อกำหนด (20.10.1) ชนิดที่ซ้อนกันชื่อ typeกำหนดประเภทวัตถุ meta ความพึงพอใจ ObjectSequenceที่มีองค์ประกอบที่พอใจEnumeratorและสะท้อน enumerators Tประเภทนับสะท้อน

[reflect.ops.objseq] ของแบบร่างครอบคลุมObjectSequenceการดำเนินการโดยเฉพาะ [reflect.ops.objseq] / 1 จะครอบคลุมget_sizeลักษณะการแยกจำนวนองค์ประกอบสำหรับ meta-object ที่ตรงตามความต้องการObjectSequence:

[reflect.ops.objseq] / 1

template <ObjectSequence T> struct get_size;

เฉพาะทั้งหมดget_size<T>จะตอบสนองความ UnaryTypeTraitต้องการ (20.10.1) มีลักษณะฐานของ integral_constant<size_t, N>ที่Nเป็นจำนวนขององค์ประกอบในลำดับวัตถุ

ดังนั้นใน Reflection TS ต้องได้รับการยอมรับและนำไปใช้ในรูปแบบปัจจุบันจำนวนองค์ประกอบของ enum สามารถคำนวณได้ในเวลารวบรวมดังนี้:

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

using ExampleEnumerators = get_enumerators<Example>::type;

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

ที่ซึ่งเราน่าจะเห็นเทมเพลตนามแฝงget_enumerators_vและget_type_vทำให้การสะท้อนง่ายขึ้น:

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

using ExampleEnumerators = get_enumerators_t<Example>;

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

สถานะบน Reflection TS

ตามที่ระบุไว้ในรายงานการเดินทางของ Herb Sutter : การประชุมมาตรฐาน ISO C ++ ในช่วงฤดูร้อน(Rapperswil)จากการประชุมฤดูร้อนของคณะกรรมการ ISO C ++ ในวันที่ 9 มิถุนายน 2018 Reflection TS ได้รับการประกาศว่าเป็นคุณลักษณะที่สมบูรณ์

Reflection TS เป็นคุณลักษณะที่สมบูรณ์ : Reflection TS ได้รับการประกาศว่าเป็นคุณลักษณะที่สมบูรณ์และจะถูกส่งออกไปเพื่อลงคะแนนความคิดเห็นหลักในช่วงฤดูร้อน โปรดสังเกตอีกครั้งว่าไวยากรณ์ที่ใช้ metaprogramming ของเทมเพลตปัจจุบันของ TS เป็นเพียงตัวยึดเท่านั้น คำติชมที่ร้องขอนั้นอยู่ใน“ ความกล้า” หลักของการออกแบบและคณะกรรมการทราบดีอยู่แล้วว่ามีความตั้งใจที่จะแทนที่ไวยากรณ์พื้นผิวด้วยรูปแบบการเขียนโปรแกรมที่ง่ายกว่าซึ่งใช้รหัสเวลาคอมไพล์ธรรมดาและการเขียนโปรแกรมแบบไม่ใช้<>สไตล์

และได้รับการวางแผนไว้สำหรับ C ++ 20 ในตอนแรกแต่ค่อนข้างไม่ชัดเจนว่า Reflection TS จะยังมีโอกาสที่จะทำให้เป็นรุ่น C ++ 20 หรือไม่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.