Enumeração contínua C ++ 11

17

Existe uma maneira de verificar no C ++ 11 se uma enumeração é contínua ?

É totalmente válido fornecer valores de enumeração que não são. Existe talvez um recurso como um traço de tipo em C ++ 14, C ++ 17 ou C ++ 20 para verificar se o enum é contínuo? Isso deve ser usado em um static_assert.

Um pequeno exemplo a seguir:

enum class Types_Discontinuous {
  A = 10,
  B = 1,
  C = 100
};

enum class Types_Continuous {
  A = 0,
  B = 1,
  C = 2
};

static_assert(SOME_TEST<Types_Discontinuous>::value, "Enum should be continuous"); // Fails
static_assert(SOME_TEST<Types_Continuous>::value, "Enum should be continuous");    // Passes
Bart
fonte
11
Significa que continua, que tem ordem crescente ou significa que começa com zero e depois com +1 para cada valor?
RoQuOTriX
5
Não há como enumerar rótulos de enumeração, portanto não é possível fazer isso de dentro do próprio programa.
Algum programador
11
Interessante. Estou pensando nas linhas de programação de modelos, nas linhas de como você pode obter um compilador para calcular um fatorial. Você iniciaria o processo com os dois limites A e C, e as funções do modelo verificam via SFINAE a presença ou não de todos os valores entre eles no enum. Infelizmente, eu tenho um emprego diário, por isso não posso tentar escrever isso, apesar de aprovar uma resposta com base nessa abordagem. Tenho certeza de que alguém como @barry ou @sehe poderia fazê-lo.
Bathsheba
11
@RoQuOTriX Como você corresponderia um valor a um rótulo? E como você verificaria a ordem dos rótulos? E como isso poderia ser feito em tempo de compilação (o que é necessário static_assert)? Mesmo que você não consiga criar uma "solução bonita", escreva uma resposta de qualquer maneira, pois estou muito curioso para saber como isso pode ser feito de maneira genérica.
Algum cara programador
11
@Someprogrammerdude, o que você descreveu é a solução "bonita" ou boa. O que eu quis dizer foi a solução de verificação "fácil", que você teria que reescrever para cada enum e que Deus o abençoe, espero que ninguém faça isso
RoQuOTriX

Respostas:

7

Para vários enums, você provavelmente pode abrir caminho usando a biblioteca Magic Enum . Por exemplo:

#include "magic_enum.hpp"

template <typename Enum>
constexpr bool is_continuous(Enum = Enum{}) {
    // make sure we're actually testing an enum
    if constexpr (!std::is_enum_v<Enum>)
        return false;
    else {
        // get a sorted list of values in the enum
        const auto values = magic_enum::enum_values<Enum>();
        if (std::size(values) == 0)
            return true;

        // for every value, either it's the same as the last one or it's one larger
        auto prev = values[0];
        for (auto x : values) {
            auto next = static_cast<Enum>(magic_enum::enum_integer(prev) + 1);
            if (x != prev && x != next)
                return false;
            else
                prev = x;
        }
        return true;
    }
}

Observe que isso é realmente, como o nome da biblioteca implica, "magic" - a biblioteca funciona em vários hacks específicos do compilador. Como tal, ele realmente não atende aos seus requisitos de "C ++ puro", mas provavelmente é o melhor possível, até termos instalações de reflexão na linguagem.

N. Shead
fonte
É de fato mágico, mas isso seria o melhor para minha situação.
Bart
7

Isso não é possível no C ++ puro, porque não há como enumerar os valores da enumeração ou descobrir o número dos valores e os valores mínimo e máximo. Mas você pode tentar usar a ajuda do seu compilador para implementar algo parecido com o que você deseja. Por exemplo, no gcc, é possível aplicar um erro de compilação se uma switchinstrução não manipular todos os valores de uma enumeração:

enum class my_enum {
    A = 0,
    B = 1,
    C = 2
};

#pragma GCC diagnostic push
#if __GNUC__ < 5
#pragma GCC diagnostic error "-Wswitch"
#else
#pragma GCC diagnostic error "-Wswitch-enum"
#endif

constexpr bool is_my_enum_continuous(my_enum t = my_enum())
{
    // Check that we know all enum values. Effectively works as a static assert.
    switch (t)
    {
    // Intentionally no default case.
    // The compiler will give an error if not all enum values are listed below.
    case my_enum::A:
    case my_enum::B:
    case my_enum::C:
        break;
    }

    // Check that the enum is continuous
    auto [min, max] = std::minmax({my_enum::A, my_enum::B, my_enum::C});
    return static_cast< int >(min) == 0 && static_cast< int >(max) == 2;
}

#pragma GCC diagnostic pop

Obviamente, isso é especializado para um determinado enum, mas a definição de tais funções pode ser automatizada com o pré-processador.

Andrey Semashev
fonte
Se eu entendi corretamente, isso ainda exigiria escrever todos os valores de enumeração no switch e listar minmax. Atualmente, tenho várias enumerações, portanto é possível, mas não preferível, para minha situação.
Bart
1

Eu adoraria ver uma resposta sobre isso. Eu também preciso disso.

Infelizmente, acho que isso não é possível usando os utilitários existentes. Se você deseja implementar uma característica de tipo, precisa do suporte do seu compilador, portanto, escrever um modelo para ele não parece viável.

Eu já estendi a enumeração com uma tag específica para indicar que é contínua e imediatamente fornece o tamanho: construtor de classe enum c ++, como passar um valor específico?

Como alternativa, você pode escrever sua própria característica:

 template<T> struct IsContiguous : std::false_type {};

Isso precisa ser especializado sempre que você define uma enumeração contígua na qual deseja usá-la. Infelizmente, isso requer alguma manutenção e atenção se o enum for alterado.

JVApen
fonte
11
Você pode escrever um verificador de código, que verifica durante a compilação, se o tipo estiver definido corretamente
RoQuOTriX
Sim, de fato. Se você tem a capacidade de escrevê-lo.
JVApen
1

Todos os enum são contínuos. 0 é sempre permitido; o maior valor permitido é o maior enumerador arredondado para o próximo1<<N -1 (todos os bits um) e todos os valores intermediários também são permitidos. ([dcl.enum] 9.7.1 / 5). Se houver enumeradores negativos definidos, o valor mais baixo permitido é definido da mesma forma, arredondando para baixo o enumerador mais baixo.

Os enumeradores definidos em enumsão expressões constantes com um valor no intervalo e o tipo correto, mas você pode definir constantes adicionais fora das enumquais têm as mesmas propriedades:

constexpr enum class Types_Discontinuous = static_cast<Types_Discontinuous>(2)

MSalters
fonte
2
Embora você esteja correto, é claro pelo OP que queremos saber isso para os valores definidos. (PS: o voto negativo não é meu)
JVApen
11
@ JVApen: Esse é o problema exato. Os "valores definidos" não são uma propriedade do próprio tipo de enumeração. O padrão é explícito quais são os valores da enumeração.
MSalters