Bir c ++ enum sınıfının eleman sayısını belirlemek mümkün müdür?


86

Bir c ++ 'nın önemini belirlemek mümkün mü enum class:

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

Kullanmaya çalıştım sizeof, ancak bir enum öğesinin boyutunu döndürüyor.

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

Kardinaliteyi elde etmenin standart bir yolu var mı (benim örneğimde 5)?


Belirli bir c ++ 11 mekanizması olabileceğini düşündüm
bquenin

6
Bu arada, bu bir kopya değil. enumve enum classes çok farklı kavramlardır.
Ayakkabı

@Shoe ... gerçekten öyle mi?
Kyle Strand

1
Bu bir XY problemi gibi görünüyor, uzun zaman öncesine ait olduğunu biliyorum, ama bunu neden yapmanız gerektiğini hatırlıyor musunuz? Bir enum classdeğeri yineleyemezsiniz , o halde sayıyı bilmenin faydası ne olur?
Fantastik Bay Fox

Yanıtlar:


73

Doğrudan değil, ancak şu numarayı kullanabilirsiniz:

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

O zaman kardinalite olarak mevcuttur static_cast<int>(Example::Count).

Elbette, bu yalnızca enum değerlerinin 0'dan başlayarak otomatik olarak atanmasına izin verirseniz güzel bir şekilde çalışır. Durum bu değilse, doğru kardinaliteyi Count'a manuel olarak atayabilirsiniz, bu gerçekten ayrı bir sabit tutmak zorunda olmaktan farklı değildir. neyse:

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

Tek dezavantajı, derleyicinin Example::Countbir enum değeri için argüman olarak kullanmanıza izin vermesidir - bu yüzden bunu kullanırsanız dikkatli olun! (Yine de kişisel olarak bunu pratikte bir sorun olarak görmüyorum.)


1
Enum değerleri bir enum sınıfında güvenli türdendir, bu nedenle 'Count' burada Örnek türünde olacaktır ve int değil, değil mi? Boyut için kullanmak için önce bir int'e 'Count' çevirmeniz gerekir.
Man of One Way

@Man: Evet, bu numara enum classdüz enums yerine es ile biraz daha karışık . Net olması için bir alçıda kurgulayacağım.
Cameron

11
Bu numaralandırmayla bir switch deyimi kullanırsanız, düzgün bir derleyici sizi bir vakanın eksik olduğu konusunda uyaracaktır. Bu çok kullanılırsa çok can sıkıcı olabilir .. Bu özel durumda ayrı bir değişkene sahip olmak daha iyi olabilir.
Fantastik Bay Fox

@FantasticMrFox Deneyime dayanarak% 100 katılıyorum. Bu derleyici uyarısı da önemlidir. Önerinizin ruhuna daha uygun olarak alternatif bir yaklaşım yayınladım.
arr_sea

28

C ++ 17 magic_enum::enum_countiçin lib https://github.com/Neargye/magic_enum adresinden kullanabilirsiniz :

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

Dezavantaj nerede?

Bu kitaplık , Clang> = 5, MSVC> = 15.3 ve GCC> = 9 üzerinde çalışan, derleyiciye özgü bir hack ( __PRETTY_FUNCTION__/ temelli __FUNCSIG__) kullanır .

Verilen aralık aralığını gözden geçiririz ve bir ada sahip tüm numaralandırmaları buluruz, bu onların sayısı olacaktır. Sınırlamalar hakkında daha fazlasını okuyun

Bu yazıda bu saldırı hakkında daha birçok şey var https://taylorconor.com/blog/enum-reflection .


2
Bu harika! Numaralandırma üye sayısını saymak için mevcut kodu değiştirmeye gerek yoktur. Ayrıca bu çok zarif bir şekilde uygulanmış gibi görünüyor (sadece kodun gözden geçirilmesi)!
andreee

Yalnızca bağlantı yanıtları genellikle önerilmez. Bunu, kütüphanenizin kullandığı tekniğin bir açıklamasıyla genişletebilir misiniz?
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;

Bu UglyCoder'in cevabından türetilmiştir, ancak bunu üç şekilde iyileştirmektedir.

  • Type_safe numaralandırmasında ( BEGINve SIZE) fazladan öğe yok ( Cameron'un cevabında da bu sorun var.)
    • Derleyici, anahtar ifadesinin eksik olduğundan şikayet etmeyecektir (önemli bir sorun)
    • Sizin numaranızı bekleyen işlevlere yanlışlıkla aktarılamazlar. (yaygın bir sorun değil)
  • Kullanım için döküm gerektirmez. ( Cameron'ın cevabında da bu sorun var.)
  • Çıkarma, enum sınıf türünün boyutuyla uğraşmaz.

UglyCoder'in , Cameron'ın numaralandırıcılara keyfi değerler atanabileceği cevabına göre avantajını koruyor .

Bir sorun ( UglyCoder ile paylaşılır ancak Cameron ile paylaşılmaz ), yeni satırları ve yorumları önemli hale getirmesidir ... ki bu beklenmedik bir durumdur. Böylece birisi, TEST_SIZEhesaplamasını ayarlamadan boşluklu bir girdi veya yorum ekleyebilir .


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;

Zekice! Elbette herhangi bir yorum veya olağandışı boşluk olamaz ve gerçekten büyük kaynak dosyalar için temel değer türü, aksi halde olacağından daha büyük olabilir.
Kyle Strand

@Kyle Strand: Bu sorun var: char kullanmak ve 256'dan fazla numaralandırıcınız da var. Ancak derleyici, kesilmeleri vb. Size bildirmek için iyi bir tavır sergilemektedir. LINE tam sayıdır ve #line kullanımının sınırı [1, 2147483647]
UglyCoder

Ah tamam. Yine de, aksi halde a olacak bir enum bile short, intörneğin bir birlik oluşturma yapılırken çarpılabilir. (Bunun, önerdiğiniz numaradan çok birlik yapılarıyla ilgili bir sorun olduğunu söyleyebilirim.)
Kyle Strand

Hile? :-) Kullanıyorum, ancak nadiren ve gerekli muhakeme ile. Kodlamadaki her şey gibi, artıları ve eksileri ve özellikle uzun vadeli bakım sonuçlarını yükseltmemiz gerekiyor. Son zamanlarda C # tanımları listesinden (OpenGL wglExt.h) bir enum sınıfı oluşturmak için kullandım.
UglyCoder

5

X () - makrolar: görüntüye dayalı bir numara var, aşağıdaki numaralandırmaya sahipsiniz:

enum MyEnum {BOX, RECT};

Şu şekilde yeniden biçimlendirin:

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

Daha sonra aşağıdaki kod enum türünü tanımlar:

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

Ve aşağıdaki kod, enum elemanlarının sayısını hesaplar:

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

Bu, virgülden virgül kaldırılarak #define MyEnumDef(ve içine koyarak #define X(val) val) daha kolay hale getirilebilir , bu da yalnızca kullanarak öğe sayısını saymanıza olanak tanır #define X(val) +1 constexpr std::size_t len = MyEnumDef;.
HolyBlackCat

4

Deneyebileceğiniz bir numara, listenizin sonuna bir enum değeri eklemek ve bunu boyut olarak kullanmaktır. Senin örneğinde

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

1
Düz enums içeren davranışla karşılaştırıldığında , bu ExampleCounttürdeki gibi çalışmayacaktır Example. İçindeki elemanların sayısını elde etmek için Example, ExampleCountbir tamsayı türüne çevrilmesi gerekir.
elma grubu

3

Boost'un önişlemci yardımcı programlarını kullanırsanız, sayımı kullanarak elde edebilirsiniz BOOST_PP_SEQ_SIZE(...).

Örneğin, CREATE_ENUMmakro aşağıdaki gibi tanımlanabilir :

#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   

Ardından makroyu çağırın:

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

aşağıdaki kodu üretir:

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

Bu, yalnızca destek önişlemci araçlarıyla ilgili olarak yüzeyi çiziyor. Örneğin, makronuz aynı zamanda kesinlikle yazılmış numaralandırmanız için dize dönüştürme yardımcı programlarını ve ostream operatörlerini de tanımlayabilir.

Yükseltme önişlemci araçlarıyla ilgili daha fazla bilgiyi burada bulabilirsiniz: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html


Bir kenara, @FantasticMrFox'a, kabul edilen cevapta Countkullanılan ek numaralandırılmış değerin, bir switchifade kullanılırsa derleyici uyarı baş ağrısına neden olacağı konusunda kesinlikle hemfikirim . unhandled caseDerleyici uyarısını daha güvenli kod bakımı için oldukça yararlı buluyorum , bu yüzden onu baltalamak istemem.


@FantasticMrFox Kabul edilen yanıtla ilgili bir soruna işaret ettiğiniz için teşekkür ederiz. Burada, tavsiyenizin ruhuna daha uygun bir alternatif yaklaşım sundum.
arr_sea

2

Std :: initializer_list ile bir hile ile çözülebilir:

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

Kullanım:

#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

Satır sayılarına veya şablonlara dayanmayan başka bir yol daha var. Tek gereksinim, enum değerlerini kendi dosyalarına yapıştırmak ve önişlemcinin / derleyicinin sayımı şu şekilde yapmasını sağlamaktır:

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

Bu, enum değerleriyle açıklamalar koymanıza, değerleri yeniden atamanıza ve kodda göz ardı edilmesi / hesaba katılması gereken geçersiz bir 'sayım' sıralama değeri eklemenize olanak tanır.

Yorumları umursamıyorsanız, fazladan bir dosyaya ihtiyacınız yoktur ve yukarıda bahsedilen birini yapabilirsiniz, örneğin:

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

#include "my_enum_inc.h"direktifleri MY_ENUM_LIST ile değiştirin, ancak #undef ENUMVALher kullanımdan sonra yapmanız gerekecektir .


1

Buna bir başka "aptalca" çözüm şudur:

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

Bunu derleyerek -Werror=switch herhangi bir anahtar durumunu atlar veya çoğaltırsanız bir derleyici uyarısı aldığınızdan emin olun. Ayrıca constexpr olduğundan bu derleme zamanında hesaplanır.

Ancak enum class, enum'un ilk değeri 0 olmasa bile, bir en için bile varsayılan başlatılmış değerin 0 olduğunu unutmayın. Bu nedenle, 0'dan başlamanız veya ilk değeri açıkça kullanmanız gerekir.


0

Hayır, koda yazmalısın.


0

static_cast<int>(Example::E) + 1Ekstra öğeyi hangisinin ortadan kaldırdığını da düşünebilirsiniz .


8
Bu cevap, bu özel programlama problemi için doğrudur, ancak genel olarak zarif ve hataya açık olmaktan uzaktır. Numaralandırma, gelecekte Example::Enumaralandırmadaki son değer olarak değiştirilebilecek yeni değerlerle genişletilebilir . Durum böyle olmasa bile, Example::E'nin değişmez değeri değişebilir.
Matthias

0

Yansıma TS: numaralandırmaların (ve diğer türlerin) statik yansıması

Yansıma TS , özellikle Reflection TS taslağının en son sürümünün [reflektör.ops.enum] / 2 get_enumerators TransformationTraitişlemi şu işlemleri sunar :

[reflektör.ops.enum] / 2

template <Enum T> struct get_enumerators

Tüm uzmanlık get_enumerators<T>karşılamalıdır TransformationTraitgereksinimleri (20.10.1). Adlandırılan yuvalanmış tür , tarafından yansıtılan numaralandırma türünün numaralandırıcılarını tatmin eden ve yansıtan öğeleri içeren typetatmin edici bir meta nesne türü ObjectSequencebelirtir .EnumeratorT

Taslağın [reflektör.ops.objseq] ObjectSequenceişlemleri, özellikle [reflekt.ops.objseq] / 1'in, get_sizetatmin edici bir meta-nesne için öğe sayısını çıkarmak için özelliği kapsadığı işlemleri kapsar ObjectSequence:

[reflektör.ops.objseq] / 1

template <ObjectSequence T> struct get_size;

Her uzmanlık get_size<T>karşılayacaktır UnaryTypeTraitbir taban özelliğine sahip şartları (20.10.1) integral_constant<size_t, N>, Nnesne sırayla eleman sayısıdır.

Böylelikle, Reflection TS mevcut haliyle kabul edilecek ve uygulanacaktı, bir numaralandırmanın eleman sayısı derleme zamanında aşağıdaki gibi hesaplanabilir:

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

using ExampleEnumerators = get_enumerators<Example>::type;

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

takma ad şablonlarını görmemiz get_enumerators_vve get_type_vyansımayı daha da basitleştirmemiz muhtemeldir :

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

using ExampleEnumerators = get_enumerators_t<Example>;

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

Reflection TS'de Durum

Herb Sutter'ın Gezi raporunda belirtildiği gibi : 9 Haziran 2018 ISO C ++ komitesi yaz toplantısından Yaz ISO C ++ standartları toplantısı (Rapperswil) , Reflection TS özellik-tamamlandı olarak ilan edildi

Reflection TS özelliği tamamlandı : Reflection TS özelliği tamamlandı olarak ilan edildi ve yaz boyunca ana yorum oylamasına gönderiliyor. Bir kez daha TS'nin metaprogramlamaya dayalı mevcut şablon sözdiziminin yalnızca bir yer tutucu olduğunu unutmayın; talep edilen geri bildirim tasarımın özünde yer alır ve komite yüzey sözdizimini sıradan derleme zamanı kodu ve <>stil olmayan metaprogramlama kullanan daha basit bir programlama modeliyle değiştirmeyi planladığını zaten biliyor .

ve başlangıçta C ++ 20 için planlanmıştı , ancak Reflection TS'nin yine de C ++ 20 sürümüne dahil etme şansı olup olmayacağı belirsiz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.