2011-02-11 24 views
36

Estoy leyendo un valor enum de un archivo binario y me gustaría comprobar si el valor es realmente parte de los valores enum. ¿Cómo puedo hacerlo?¿Cómo verificar si el valor enum es válido?

#include <iostream> 

enum Abc 
{ 
    A = 4, 
    B = 8, 
    C = 12 
}; 

int main() 
{ 
    int v1 = 4; 
    Abc v2 = static_cast<Abc>(v1); 

    switch (v2) 
    { 
     case A: 
      std::cout<<"A"<<std::endl; 
      break; 
     case B: 
      std::cout<<"B"<<std::endl; 
      break; 
     case C: 
      std::cout<<"C"<<std::endl; 
      break; 
     default : 
      std::cout<<"no match found"<<std::endl; 
    } 
} 

¿Tengo que utilizar el operador switch o hay una mejor manera?

EDITAR

tengo establecen valores de enumeración y por desgracia no puedo modificarlos. Para empeorar las cosas, que no son continuas (sus valores va de 0, 75,76,80,85,90,95,100, etc.)

+3

Cualquier enumeración es solo un número, por lo que no creo que haya una mejor manera de verificarlo. Probablemente deba definir una estructura más rígida para sus tipos de datos. – Rizo

Respuesta

21

enum El valor es válido en C++ si está dentro del rango [A, B], que se define mediante la regla estándar a continuación. Por lo tanto, en el caso de enum X { A = 1, B = 3 }, el valor de 2 se considera un valor de enum válido.

Considérese 7,2/6 de la norma:

Para una enumeración donde emin es el empadronador más pequeño y Emax es el más grande, los valores de la enumeración son los valores del tipo subyacente en el bmin gama para Bmax, donde bmin y bmax son, respectivamente, los valores más pequeños y más grandes del campo de bits más pequeño que puede almacenar emin y emax. Es posible definir una enumeración que tiene valores no definidos por ninguno de sus enumeradores.

No hay retrospección en C++. Un enfoque a seguir es enumerar los valores enum en una matriz adicionalmente y escribir una envoltura que haría la conversión y posiblemente lanzar una excepción en caso de falla.

Consulte Similar Question acerca de cómo convertir int a enum para obtener más detalles.

+1

ha malinterpretado la cotización estándar, hay más de '[A, B]' en los valores válidos. –

+5

De hecho, por ejemplo, si los valores son 1 y 5, este último requiere al menos 3 bits, por lo que 6 y 7 también serán valores válidos del enumerador. – visitor

+0

@ Matthieu: Gracias, corrigió la respuesta. – Leonid

9

utilizan Tal enumeración de esta manera:

enum MyEnum 
{ 
A, 
B, 
C 
}; 

y para comprobar

if (v2 >= A && v2 <= C) 

Si no se especifica valores para las constantes de enumeración, los valores comienzan en cero y aumentará en uno con cada movimiento abajo en la lista. Por ejemplo, dada enum MyEnumType { ALPHA, BETA, GAMMA }; alfa tiene un valor de 0, beta tiene un valor de 1, y gamma tiene un valor de 2.

+1

Me gusta la simplicidad de esto y lo expandí definiendo siempre el primer elemento en una enumeración como SOMETYPE_UNKNOWN y el último como SOMETYPE_MAX. Entonces la prueba siempre será AssertTrue (v2> = SOMETYPE_UNKNOWN && v2 <= SOMETYPE_MAX). Por supuesto, solo agregue elementos después de UNKNOWN y antes de MAX. –

+0

No entendí tu idea al principio, pero de hecho es un excelente truco de mantenimiento cero. – pfabri

2

Al hablar de una lengua, no hay mejor manera, existen los valores de enumeración tiempo de compilación solo y no hay forma de enumerarlos programáticamente. Sin embargo, con una infraestructura bien pensada, aún podrá evitar enumerar todos los valores varias veces. Ver Easy way to use variables of enum types as string in C?

Su muestra a continuación, se puede volver a escribir utilizando el "enumFactory.h" siempre que haya como:

#include "enumFactory.h" 

#define ABC_ENUM(XX) \ 
    XX(A,=4) \ 
    XX(B,=8) \ 
    XX(C,=12) \ 

DECLARE_ENUM(Abc,ABC_ENUM) 

int main() 
{ 
    int v1 = 4; 
    Abc v2 = static_cast<Abc>(v1); 

    #define CHECK_ENUM_CASE(name,assign) case name: std::cout<< #name <<std::endl; break; 
    switch (v2) 
    { 
     ABC_ENUM(CHECK_ENUM_CASE) 
     default : 
      std::cout<<"no match found"<<std::endl; 
    } 
    #undef CHECK_ENUM_CASE 
} 

o incluso (con algunas más instalaciones ya existentes en esa cabecera):

#include "enumFactory.h" 

#define ABC_ENUM(XX) \ 
    XX(A,=4) \ 
    XX(B,=8) \ 
    XX(C,=12) \ 

DECLARE_ENUM(Abc,ABC_ENUM) 
DEFINE_ENUM(Abc,ABC_ENUM) 

int main() 
{ 
    int v1 = 4; 
    Abc v2 = static_cast<Abc>(v1); 
    const char *name = GetString(v2); 
    if (name[0]==0) name = "no match found"; 
    std::cout << name << std::endl; 
} 
7

La única forma que he encontrado para hacerlo "fácil", fue crear (macro) una matriz ordenada de las enumeraciones y verificar con eso.

El truco switch falla con enum s porque un enum puede tener más de un enumerador con un valor determinado.

Es realmente un problema molesto.

3

extensiones administradas para C++ es compatible con la siguiente sintaxis:

enum Abc 
{ 
    A = 4, 
    B = 8, 
    C = 12 
}; 

Enum::IsDefined(Abc::typeid, 8); 

Referencia: MSDN "Managed Extensions for C++ Programming"

+0

No estoy seguro de qué es "managed C++", pero ¿está seguro de que es C++ y no C#? [this] (http://msdn.microsoft.com/en-us/library/system.enum.isdefined%28v=vs.110%29.aspx) se ve como C# –

+3

@ BЈовић: 'managed C++' es una variante de Microsoft de 'C++' que puede usar las bibliotecas del '.NET framework'. Parece que esto es 'C++' ya que el operador '::' no está definido en 'C#' como este. – Stefan

+0

@ BЈовић ¿Intentó el código en un proyecto Managed Extensions C++? Estamos usando un código similar en uno de nuestros proyectos C++. Específicamente utilizamos el método Enum :: IsDefined(). – Brett

4

En C++ 11 hay una manera mejor si usted está preparado para una lista de sus valores de enumeración como parámetros de plantilla . Puedes ver esto como algo bueno, permitiéndote aceptar subconjuntos de los valores enum válidos en diferentes contextos; a menudo útil al analizar códigos de fuentes externas.

Una posible adición útil al siguiente ejemplo sería algunas aserciones estáticas alrededor del tipo subyacente de EnumType relativo a IntType para evitar problemas de truncamiento. Izquierda como ejercicio.

#include <stdio.h> 

template<typename EnumType, EnumType... Values> class EnumCheck; 

template<typename EnumType> class EnumCheck<EnumType> 
{ 
public: 
    template<typename IntType> 
    static bool constexpr is_value(IntType) { return false; } 
}; 

template<typename EnumType, EnumType V, EnumType... Next> 
class EnumCheck<EnumType, V, Next...> : private EnumCheck<EnumType, Next...> 
{ 
    using super = EnumCheck<EnumType, Next...>; 

public: 
    template<typename IntType> 
    static bool constexpr is_value(IntType v) 
    { 
     return v == static_cast<IntType>(V) || super::is_value(v); 
    } 
}; 

enum class Test { 
    A = 1, 
    C = 3, 
    E = 5 
}; 

using TestCheck = EnumCheck<Test, Test::A, Test::C, Test::E>; 

void check_value(int v) 
{ 
    if (TestCheck::is_value(v)) 
     printf("%d is OK\n", v); 
    else 
     printf("%d is not OK\n", v); 
} 

int main() 
{ 
    for (int i = 0; i < 10; ++i) 
     check_value(i); 
} 
+0

Dado que el valor de 'int v' no se conoce en tiempo de compilación, 'is_value' deberá ejecutarse en tiempo de ejecución. ¿No resultaría esto en todo tipo de llamadas a funciones recursivas y sería muy ineficiente en comparación con una simple declaración de cambio o una matriz de todos los valores? Todavía tiene que enumerar todos los valores enum, por lo que no es como si estuviera ganando algo con este enfoque. ¿O me perdí algo aquí? –

+0

@InnocentBystander Son todas funciones 'constexpr', por lo que el compilador tiene muchas posibilidades de optimización. Las funciones tampoco son recursivas; es una cadena de funciones que pasa a tener el mismo nombre. En algunas pruebas rápidas con el ejemplo anterior, gcc 5.4 genera una instrucción de código uno más corta para la versión de la plantilla en comparación con la versión del conmutador. Clang 3.8 es dos instrucciones más largas para la versión de la plantilla. El resultado variará dependiendo de cuántos valores y si los valores son contiguos. La gran victoria, especialmente cuando se realiza la descodificación de protocolo, es escribir los códigos que espera en una línea. – janm

+1

tienes razón, lo siento no "recursivo" per se, pero la cadena de llamadas de función. Es interesante que los compiladores puedan optimizar todo eso. Y gracias por dar seguimiento a una respuesta de 3 años :) –

2

Un poco necro, pero ... hace una prueba de alcance de int en valores de primera/última enumeración (se puede combinar con la idea de JANM para hacer comprobaciones exactas), C++ 11:

Cabecera: Declaración

namespace chkenum 
{ 
    template <class T, T begin, T end> 
    struct RangeCheck 
    { 
    private: 
     typedef typename std::underlying_type<T>::type val_t; 
    public: 
     static 
     typename std::enable_if<std::is_enum<T>::value, bool>::type 
     inrange(val_t value) 
     { 
      return value >= static_cast<val_t>(begin) && value <= static_cast<val_t>(end); 
     } 
    }; 

    template<class T> 
    struct EnumCheck; 
} 

#define DECLARE_ENUM_CHECK(T,B,E) namespace chkenum {template<> struct EnumCheck<T> : public RangeCheck<T, B, E> {};} 

template<class T> 
inline 
typename std::enable_if<std::is_enum<T>::value, bool>::type 
testEnumRange(int val) 
{ 
    return chkenum::EnumCheck<T>::inrange(val); 
} 

Enum:

enum MinMaxType 
{ 
    Max = 0x800, Min, Equal 
}; 
DECLARE_ENUM_CHECK(MinMaxType, MinMaxType::Max, MinMaxType::Equal); 

Uso:

bool r = testEnumRange<MinMaxType>(i); 

Principalmente la diferencia de arriba propuso que la función de prueba dependa solo del tipo de enumeración.

0

Sin embargo, otra manera de hacerlo:

#include <algorithm> 
#include <iterator> 
#include <iostream> 

template<typename> 
struct enum_traits { static constexpr void* values = nullptr; }; 

namespace detail 
{ 

template<typename T> 
constexpr bool is_value_of(int, void*) { return false; } 

template<typename T, typename U> 
constexpr bool is_value_of(int v, U) 
{ 
    using std::begin; using std::end; 

    return std::find_if(begin(enum_traits<T>::values), end(enum_traits<T>::values), 
     [=](auto value){ return value == static_cast<T>(v); } 
    ) != end(enum_traits<T>::values); 
} 

} 

template<typename T> 
constexpr bool is_value_of(int v) 
{ return detail::is_value_of<T>(v, decltype(enum_traits<T>::values) { }); } 

//////////////////// 
enum Abc { A = 4, B = 8, C = 12 }; 

template<> 
struct enum_traits<Abc> { static constexpr auto values = { A, B, C }; }; 
decltype(enum_traits<Abc>::values) enum_traits<Abc>::values; 

enum class Def { D = 1, E = 3, F = 5 }; 

int main() 
{ 
    std::cout << "Abc:"; 
    for(int i = 0; i < 10; ++i) 
     if(is_value_of<Abc>(i)) std::cout << " " << i; 
    std::cout << std::endl; 

    std::cout << "Def:"; 
    for(int i = 0; i < 10; ++i) 
     if(is_value_of<Def>(i)) std::cout << " " << i; 
    std::cout << std::endl; 

    return 0; 
} 

La parte "fea" de este enfoque en mi humilde opinión es tener que definir:

decltype(enum_traits<Abc>::values) enum_traits<Abc>::values 

Si no se opone a ellas, se pueden envolver dentro de una macro:

#define REGISTER_ENUM_VALUES(name, ...) \ 
template<> struct enum_traits<name> { static constexpr auto values = { __VA_ARGS__ }; }; \ 
decltype(enum_traits<name>::values) enum_traits<name>::values; 
Cuestiones relacionadas