É possível determinar o número de elementos de uma classe c ++ enum?

86

É possível determinar a cardinalidade de um c ++ enum class:

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

Tentei usar sizeof, no entanto, ele retorna o tamanho de um elemento enum.

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

Existe uma maneira padrão de obter a cardinalidade (5 no meu exemplo)?

bquenin
fonte
Achei que poderia haver um mecanismo específico do c ++ 11
bquenin de
6
A propósito, esta não é uma duplicata. enume enum classesses são conceitos muito diferentes.
Sapato
@Shoe ... eles são mesmo?
Kyle Strand
1
Parece um problema XY, sei que é de muito tempo atrás, mas lembra por que precisava fazer isso? Você não pode iterar sobre um enum classvalor, então qual seria o benefício em saber o número?
Fantástico Sr. Fox

Respostas:

73

Não diretamente, mas você pode usar o seguinte truque:

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

Então a cardinalidade está disponível como static_cast<int>(Example::Count).

Claro, isso só funciona bem se você permitir que os valores do enum sejam atribuídos automaticamente, começando de 0. Se não for o caso, você pode atribuir manualmente a cardinalidade correta para Count, o que realmente não é diferente de ter que manter uma constante separada de qualquer forma:

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

A única desvantagem é que o compilador permitirá que você use Example::Countcomo um argumento para um valor enum - portanto, tenha cuidado ao usar isso! (Eu pessoalmente acho que isso não é um problema na prática, no entanto.)

Cameron
fonte
1
Os valores enum são seguros para o tipo em uma classe enum, então 'Count' será do tipo Exemplo aqui e não int, certo? Você teria que lançar 'Count' para um int primeiro para usá-lo para o tamanho.
Man of One Way
@Man: Sim, este truque é um pouco mais confuso com enum classes em vez de enums simples . Vou editar em um elenco para ficar claro.
Cameron
11
Se você usar uma instrução switch com este enum, qualquer compilador decente irá avisá-lo de que está faltando um caso. Se isso for usado muito, pode ser muito chato. Seria melhor ter apenas uma variável separada neste caso específico.
Fantástico Sr. Fox
@FantasticMrFox Concordo 100%, com base na experiência. Esse aviso do compilador também é importante. Publiquei uma abordagem alternativa, mais de acordo com o espírito de sua recomendação.
arr_sea 01 de
28

Para C ++ 17, você pode usar magic_enum::enum_countde lib https://github.com/Neargye/magic_enum :

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

Onde está a desvantagem?

Esta biblioteca usa um hack específico do compilador (baseado em __PRETTY_FUNCTION__/ __FUNCSIG__), que funciona no Clang> = 5, MSVC> = 15.3 e GCC> = 9.

Percorremos o intervalo de intervalo dado e encontramos todas as enumerações com um nome, esta será a sua contagem. Leia mais sobre as limitações

Muito mais sobre este hack neste post https://taylorconor.com/blog/enum-reflection .

Neargye
fonte
2
Isso é incrível! Não há necessidade de modificar o código existente para contar o número de membros enum. Além disso, isso parece muito elegantemente implementado (apenas folheando o código)!
andreee
Respostas apenas com links geralmente são desencorajadas. Você poderia expandir isso com uma descrição da técnica que sua biblioteca usa?
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;

Isso é derivado da resposta do UglyCoder, mas a melhora de três maneiras.

  • Não há elementos extras no enum type_safe ( BEGINe SIZE) ( a resposta de Cameron também tem esse problema.)
    • O compilador não reclamará sobre eles estarem faltando em uma instrução switch (um problema significativo)
    • Eles não podem ser passados ​​inadvertidamente para funções que esperam seu enum. (não é um problema comum)
  • Não requer fundição para uso. ( A resposta de Cameron também tem esse problema.)
  • A subtração não interfere no tamanho do tipo de classe enum.

Ele mantém a vantagem de UglyCoder sobre a resposta de Cameron de que os enumeradores podem receber valores arbitrários.

Um problema (compartilhado com o UglyCoder, mas não com o Cameron ) é que ele cria novas linhas e comentários significativos ... o que é inesperado. Assim, alguém poderia adicionar uma entrada com espaço em branco ou um comentário sem ajustar TEST_SIZEo cálculo de.

Epônimo
fonte
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;
UglyCoder
fonte
Esperto! Não pode ter comentários ou espaçamento incomum, é claro, e para arquivos de origem realmente grandes, o tipo de valor subjacente pode ser maior do que seria de outra forma.
Kyle Strand,
@Kyle Strand: aí está aquele problema: usando char e você tem mais de 256 enumeradores também. Mas o compilador tem as boas maneiras de notificá-lo sobre truncamentos etc. LINE é um literal inteiro e usando #line tem um limite de [1, 2147483647]
UglyCoder
Ah ok. Ainda assim, mesmo um enum que de outra forma seria um shortpode ser aumentado, por intexemplo, ao fazer uma construção de unidade. (Eu diria que este é mais um problema com a construção de unidade do que com seu truque proposto, no entanto.)
Kyle Strand
Truque? :-) Eu uso, mas raramente e com o devido julgamento. Como tudo na codificação, precisamos examinar os prós e os contras e, especialmente, as implicações de manutenção de longo prazo. Usei-o recentemente para criar uma classe enum de uma lista de C # define (OpenGL wglExt.h).
UglyCoder
5

Existe um truque baseado em X () - macros: imagem, você tem o seguinte enum:

enum MyEnum {BOX, RECT};

Reformate para:

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

Em seguida, o código a seguir define o tipo de enum:

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

E o código a seguir calcula o número de elementos 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
...
Kirill Suetnov
fonte
Isso pode ser facilitado removendo a vírgula de #define MyEnumDef(e inserindo-a #define X(val) val), que permite contar o número de elementos usando apenas #define X(val) +1 constexpr std::size_t len = MyEnumDef;.
HolyBlackCat
4

Um truque que você pode tentar é adicionar um valor enum no final da sua lista e usá-lo como o tamanho. No seu exemplo

enum class Example { A, B, C, D, E, ExampleCount };
David Nehme
fonte
1
Comparado ao comportamento com o enums simples , isso não funcionará como ExampleCountestá Example. Para obter o número de elementos em Example, ExampleCountteria que ser convertido em um tipo inteiro.
sopa de maçã de
3

Se você fizer uso dos utilitários de pré-processador do boost, poderá obter a contagem usando BOOST_PP_SEQ_SIZE(...).

Por exemplo, pode-se definir a CREATE_ENUMmacro da seguinte forma:

#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   

Em seguida, chamando a macro:

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

geraria o seguinte código:

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

Isso é apenas arranhar a superfície com relação às ferramentas do pré-processador boost. Por exemplo, sua macro também pode definir de / para utilitários de conversão de string e operadores ostream para seu enum fortemente tipado.

Mais sobre as ferramentas de pré-processador de boost aqui: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/ApethoscopeA-AnIntroductiontoPreprocessorMetaprogramming.html


Como um aparte, eu concordo totalmente com @FantasticMrFox que o Countvalor enumerado adicional empregado na resposta aceita criará muitas dores de cabeça de advertência do compilador se usar uma switchinstrução. Acho o unhandled caseaviso do compilador bastante útil para uma manutenção de código mais segura, portanto, não quero prejudicá-lo.

arr_sea
fonte
@FantasticMrFox Obrigado por apontar um problema preocupante com a resposta aceita. Forneci aqui uma abordagem alternativa que está mais de acordo com o espírito de sua recomendação.
arr_sea 01 de
2

Isso pode ser resolvido por um truque com 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__}); \
    }();                                                          \
};

Uso:

#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;
}
ixjxk
fonte
2

Existe outra maneira que não depende de contagens de linha ou modelos. O único requisito é colar os valores enum em seu próprio arquivo e fazer com que o pré-processador / compilador faça a contagem assim:

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

Isso permite que você coloque comentários com os valores de enum, reatribua valores e não injeta um valor de enum 'contagem' inválido que precisa ser ignorado / contabilizado no código.

Se você não se importa com os comentários, não precisa de um arquivo extra e pode fazer como alguém mencionado acima, por exemplo:

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

e substitua as #include "my_enum_inc.h"diretivas por MY_ENUM_LIST, mas você precisará fazer isso #undef ENUMVALapós cada uso.

MetalTurnip
fonte
1

Outro tipo de solução "estúpida" para isso é:

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

Compilando isso com -Werror=switchvocê, certifique-se de obter um aviso do compilador se omitir ou duplicar qualquer caso de switch. Também é constexpr, portanto, é calculado em tempo de compilação.

Mas observe que mesmo para um en, enum classo valor inicializado padrão é 0, mesmo se o primeiro valor do enum não for 0. Portanto, você deve iniciar em 0 ou usar explicitamente o primeiro valor.

Zitrax
fonte
0

Não, você tem que escrever no código.


fonte
0

Você também pode considerar o static_cast<int>(Example::E) + 1que elimina o elemento extra.

Fabio A. Correa
fonte
8
Esta resposta está correta para este problema específico de programação, mas em geral está longe de ser elegante e propensa a erros. O enum pode ser estendido com novos valores no futuro que podem substituir Example::Eo último valor no enum. Mesmo que esse não seja o caso, Example::Eo valor literal de pode mudar.
Matthias de
0

Reflexão TS: reflexão estática de enums (e outros tipos)

O Reflection TS , particularmente [reflect.ops.enum] / 2 da versão mais recente do rascunho do Reflection TS oferece a get_enumerators TransformationTraitoperação:

[reflect.ops.enum] / 2

template <Enum T> struct get_enumerators

Todas as especializações de get_enumerators<T>devem atender aos TransformationTraitrequisitos (20.10.1). O tipo aninhado denominado typedesigna um tipo de meta-objeto satisfatório ObjectSequence, contendo elementos que satisfazem Enumeratore refletem os enumeradores do tipo de enumeração refletido por T.

[reflect.ops.objseq] do rascunho cobre as ObjectSequenceoperações, onde particularmente [reflect.ops.objseq] / 1 cobre o get_sizetraço para extrair o número de elementos para um meta-objeto que satisfaça ObjectSequence:

[reflect.ops.objseq] / 1

template <ObjectSequence T> struct get_size;

Todas as especializações de get_size<T>devem atender aos UnaryTypeTraitrequisitos (20.10.1) com uma característica base de integral_constant<size_t, N>, onde Né o número de elementos na sequência de objetos.

Assim, no Reflection TS para ser aceito e implementado em sua forma atual, o número de elementos de um enum pode ser calculado, em tempo de compilação, da seguinte forma:

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

using ExampleEnumerators = get_enumerators<Example>::type;

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

onde provavelmente veremos modelos de alias get_enumerators_ve get_type_vpara simplificar ainda mais a reflexão:

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

using ExampleEnumerators = get_enumerators_t<Example>;

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

Status no Reflection TS

Conforme declarado pelo relatório de viagem de Herb Sutter : Reunião de padrões ISO C ++ de verão (Rapperswil) da reunião de verão do comitê ISO C ++ de 9 de junho de 2018, o Reflection TS foi declarado como recurso completo

O Reflection TS tem recursos completos : O Reflection TS foi declarado como recursos completos e está sendo enviado para sua votação principal de comentários durante o verão. Observe novamente que a sintaxe baseada em metaprogramação de template atual do TS é apenas um espaço reservado; o feedback que está sendo solicitado está nas “entranhas” centrais do projeto, e o comitê já sabe que pretende substituir a sintaxe de superfície por um modelo de programação mais simples que usa código de tempo de compilação comum e não <>metaprogramação no estilo.

e foi inicialmente planejado para C ++ 20 , mas não está claro se o Reflection TS ainda terá a chance de torná-lo no lançamento do C ++ 20.

dfrib
fonte