Je lis une valeur enum
à partir d'un fichier binaire et souhaite vérifier si la valeur fait vraiment partie des valeurs enum
. Comment puis-je le faire?
#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;
}
}
Dois-je utiliser l'opérateur switch
ou existe-t-il un meilleur moyen?
MODIFIER
J'ai défini des valeurs enum et malheureusement je ne peux pas les modifier. Pour aggraver les choses, elles ne sont pas continues (leurs valeurs vont de 0, 75,76,80,85,90,95,100, etc.)
La valeur enum
est valide en C++ si elle se situe dans la plage [A, B], définie par la règle standard ci-dessous. Donc, dans le cas de enum X { A = 1, B = 3 }
, la valeur de 2
est considérée comme une valeur enum valide.
Considérez 7.2/6 de la norme:
Pour une énumération où emin est le plus petit énumérateur et emax le plus grand, les valeurs de l'énumération sont les valeurs du type sous-jacent dans l'intervalle bmin à bmax, où bmin et bmax sont respectivement les valeurs les plus petites et les plus grandes. du plus petit champ de bits pouvant stocker emin et emax. Il est possible de définir une énumération dont les valeurs ne sont définies par aucun de ses énumérateurs .
Il n'y a pas de rétrospection en C++. Une solution consiste à répertorier en plus les valeurs enum dans un tableau et à écrire un wrapper capable d'effectuer la conversion et éventuellement de lever une exception en cas d'échec.
Voir Question similaire sur la manière de convertir int en enum pour plus de détails.
Peut-être utiliser enum comme ceci:
enum MyEnum
{
A,
B,
C
};
et pour vérifier
if (v2 >= A && v2 <= C)
Si vous ne spécifiez pas de valeurs pour les constantes enum, les valeurs commencent à zéro et augmentent d'une unité à chaque déplacement dans la liste. Par exemple, donné enum MyEnumType { ALPHA, BETA, GAMMA };
ALPHA vaut 0, BETA, 1 et GAMMA, 2.
La seule façon pour moi de rendre cela 'facile' était de créer (macro) un tableau trié des énumérations et de vérifier avec cela.
L'astuce switch
échoue avec enum
s car une enum
peut avoir plusieurs énumérateurs avec une valeur donnée.
C'est un problème ennuyant, vraiment.
En C++ 11, il existe un meilleur moyen si vous êtes prêt à répertorier vos valeurs enum en tant que paramètres de modèle. Vous pouvez considérer cela comme une bonne chose, vous permettant d'accepter des sous-ensembles des valeurs d'enum valides dans différents contextes. souvent utile lors de l'analyse de codes provenant de sources externes.
Certaines assertions statiques autour du type EnumType sous-jacent par rapport à IntType pourraient constituer un complément utile à l'exemple ci-dessous afin d'éviter les problèmes de troncature. Gauche comme un exercice.
#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);
}
Les extensions gérées pour C++ prennent en charge la syntaxe suivante:
enum Abc
{
A = 4,
B = 8,
C = 12
};
Enum::IsDefined(Abc::typeid, 8);
Référence: MSDN " Extensions managées pour la programmation C++ "
Parlant d'une langue, il n'y a pas de meilleur moyen, les valeurs enum existent uniquement à la compilation et il n'y a aucun moyen de les énumérer par programme. Avec une infrastructure bien pensée, vous pourrez toujours éviter d’énumérer plusieurs fois toutes les valeurs. Voir Moyen facile d'utiliser des variables de types enum en tant que chaîne dans C?
Votre échantillon peut ensuite être réécrit en utilisant le "enumFactory.h" fourni ici comme:
#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
}
ou même (en utilisant quelques installations supplémentaires déjà présentes dans cet en-tête):
#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;
}
Kinda necro, mais ... effectue une vérification RANGE de int dans les valeurs first/last enum (peut être combinée à l'idée de janm d'effectuer des vérifications exactes), C++ 11:
Entête:
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);
}
Déclaration d'énumération:
enum MinMaxType
{
Max = 0x800, Min, Equal
};
DECLARE_ENUM_CHECK(MinMaxType, MinMaxType::Max, MinMaxType::Equal);
Usage:
bool r = testEnumRange<MinMaxType>(i);
Principalement, la différence de ce qui précède a proposé que la fonction de test ne dépende que du type d’énumération lui-même.
Encore une autre façon de le faire:
#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 partie "laide" de cette approche, IMHO doit définir:
decltype(enum_traits<Abc>::values) enum_traits<Abc>::values
Si vous n'êtes pas opposé aux macros, vous pouvez l'envelopper dans une 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;