Enum. Continua C ++ 11

17

¿Hay alguna manera de verificar en C ++ 11 si una enumeración es continua ?

Es totalmente válido dar valores enum que no lo son. ¿Hay tal vez una característica como un rasgo de tipo en C ++ 14, C ++ 17 o tal vez C ++ 20 para verificar si la enumeración es continua? Esto se utilizará en un static_assert.

Sigue un pequeño ejemplo:

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
fuente
1
¿El medio continúa, que tiene un orden ascendente o significa que comienza con cero y luego +1 para cada valor?
RoQuOTriX
55
No hay forma de enumerar las etiquetas de enumeración, por lo que no es posible hacerlo desde el propio programa.
Algún tipo programador
1
Interesante. Estoy pensando en la línea de programación de plantillas en la línea de cómo puede obtener un compilador para calcular un factorial. Comenzaría con los dos límites A y C, y las funciones de la plantilla verifican a través de SFINAE la presencia o no de todos los valores entre ellos en el enum. Lamentablemente tengo un trabajo diario, así que no puedo intentar escribir esto, aunque votaré una respuesta basada en este enfoque. Estoy bastante seguro de que alguien como @barry o @sehe podría hacerlo.
Betsabé
1
@RoQuOTriX ¿Cómo asociaría un valor a una etiqueta? ¿Y cómo verificaría el orden de las etiquetas? ¿Y cómo podría hacerse en tiempo de compilación (que es necesario para static_assert)? Incluso si no puede hacer una "solución hermosa", escriba una respuesta de todos modos, ya que tengo mucha curiosidad por cómo se podría hacer de manera genérica.
Algún tipo programador
1
@Someprogrammerdude lo que describiste es la "bella" o buena solución. Lo que quise decir fue la solución de verificación "fácil", que tendrías que reescribir para cada enumeración y que Dios los bendiga, espero que nadie haga eso
RoQuOTriX

Respuestas:

7

Durante varios enums, probablemente puedas hackear esto usando la biblioteca Magic Enum . Por ejemplo:

#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;
    }
}

Tenga en cuenta que esto es, como el nombre de la biblioteca implica, "mágico": la biblioteca funciona en varios hacks específicos del compilador. Como tal, realmente no cumple con su requisito de "C ++ puro", pero probablemente sea tan bueno como podamos obtener hasta que tengamos facilidades de reflexión en el lenguaje.

N. Shead
fuente
De hecho, es mágico, pero esto se adaptaría mejor a mi situación.
Bart
7

Esto no es posible en C ++ puro, porque no hay forma de enumerar los valores de enumeración, o descubrir el número de valores y los valores mínimos y máximos. Pero podría intentar usar la ayuda de su compilador para implementar algo similar a lo que desea. Por ejemplo, en gcc es posible aplicar un error de compilación si una switchinstrucción no maneja todos los valores de una enumeración:

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, esto está especializado para una enumeración dada, pero la definición de tales funciones puede automatizarse con un preprocesador.

Andrey Semashev
fuente
Si entiendo correctamente, esto todavía requeriría escribir todos los valores de enumeración en el interruptor y la lista para minmax. Actualmente tengo varias enumeraciones, por lo que es posible pero no preferido para mi situación.
Bart
1

Me encantaría ver una respuesta sobre esto. Lo he estado necesitando también.

Desafortunadamente, no creo que esto sea posible utilizando las utilidades existentes. Si desea implementar un rasgo de tipo en esto, necesita soporte de su compilador, por lo que escribir una plantilla no parece factible.

Ya extendí la enumeración con una etiqueta específica para indicar que es contigua e inmediatamente le da el tamaño: enum class constructor c ++, ¿cómo pasar un valor específico?

Alternativamente, puedes escribir tu propio rasgo:

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

Esto debe especializarse cada vez que defina una enumeración contigua donde desea utilizar esto. Desafortunadamente, esto requiere un poco de mantenimiento y atención si se cambia la enumeración.

JVApen
fuente
1
Podría escribir un verificador de código, que verifica durante la compilación, si el tipo está configurado correctamente
RoQuOTriX
Si, de hecho. Si tienes la capacidad de escribirlo.
JVApen
1

Todas las enumeraciones son continuas. 0 siempre está permitido; el valor más alto permitido es el enumerador más alto redondeado al siguiente 1<<N -1(todos los bits uno), y todos los valores intermedios también están permitidos. ([dcl.enum] 9.7.1 / 5). Si hay enumeradores negativos definidos, el valor más bajo permitido se define de manera similar al redondear el enumerador más bajo.

Los enumeradores definidos en el enumson expresiones constantes con un valor en el rango y el tipo correcto, pero puede definir constantes adicionales fuera del enumque tienen las mismas propiedades:

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

MSalters
fuente
2
Aunque tiene razón, está claro en el OP que queremos saber esto para los valores definidos. (PD: el voto
negativo
1
@JVApen: Ese es el problema exacto. Los "valores definidos" no son una propiedad del tipo enum. El estándar es explícito cuáles son los valores de la enumeración.
MSalters