Existe-t-il un moyen générique de transtyper int
en enum
dans C++
?
Si int
se situe dans la plage d'une enum
, il devrait renvoyer une valeur enum
, sinon jetez une exception
. Existe-t-il un moyen de l'écrire génériquement? Plus d'un enum type
devrait être pris en charge.
Arrière-plan: J'ai un type externe enum _ et un contrôle no sur le code source. J'aimerais stocker cette valeur dans une base de données et la récupérer.
La chose évidente est d’annoter votre enum:
// generic code
#include <algorithm>
template <typename T>
struct enum_traits {};
template<typename T, size_t N>
T *endof(T (&ra)[N]) {
return ra + N;
}
template<typename T, typename ValType>
T check(ValType v) {
typedef enum_traits<T> traits;
const T *first = traits::enumerators;
const T *last = endof(traits::enumerators);
if (traits::sorted) { // probably premature optimization
if (std::binary_search(first, last, v)) return T(v);
} else if (std::find(first, last, v) != last) {
return T(v);
}
throw "exception";
}
// "enhanced" definition of enum
enum e {
x = 1,
y = 4,
z = 10,
};
template<>
struct enum_traits<e> {
static const e enumerators[];
static const bool sorted = true;
};
// must appear in only one TU,
// so if the above is in a header then it will need the array size
const e enum_traits<e>::enumerators[] = {x, y, z};
// usage
int main() {
e good = check<e>(1);
e bad = check<e>(2);
}
Le tableau doit être tenu à jour avec e
, ce qui est une nuisance si vous n'êtes pas l'auteur de e
. Comme le dit Sjoerd, il peut probablement être automatisé avec n'importe quel système de construction décent.
En tout cas, vous êtes contre 7.2/6:
Pour une énumération où emin est le le plus petit énumérateur et emax est le le plus grand, les valeurs de l'énumération sont les valeurs du type sous-jacent dans la gamme bmin à bmax, où bmin et bmax sont, respectivement, le les plus petites et les plus grandes valeurs de le plus petit champ de bits pouvant stocker emin et emax. Il est possible de définir un énumération qui a des valeurs non défini par l’un de ses énumérateurs.
Donc, si vous n'êtes pas l'auteur de e
, vous pouvez ou non avoir la garantie que les valeurs valides de e
apparaissent réellement dans sa définition.
Laid.
enum MyEnum { one = 1, two = 2 };
MyEnum to_enum(int n)
{
switch( n )
{
case 1 : return one;
case 2 : return two;
}
throw something();
}
Maintenant pour la vraie question. Pourquoi avez-vous besoin de cela? Le code est moche, pas facile à écrire (*?), Pas facile à maintenir, et pas facile à intégrer à votre code. Le code vous dit que c'est faux. Pourquoi le combattre?
MODIFIER:
Alternativement, étant donné que les énumérations sont des types intégraux en C++:
enum my_enum_val = static_cast<MyEnum>(my_int_val);
mais ceci est encore plus laid que ci-dessus, beaucoup plus sujet aux erreurs, et il ne jettera pas comme vous le souhaitez.
Si, comme vous le décrivez, les valeurs se trouvent dans une base de données, pourquoi ne pas écrire un générateur de code qui lit cette table et crée un fichier .h et .cpp avec à la fois une fonction enum et une fonction to_enum(int)
?
Avantages:
to_string(my_enum)
.Que penses-tu de celui-ci?
#include <iostream>
#include <stdexcept>
#include <set>
#include <string>
using namespace std;
template<typename T>
class Enum
{
public:
static void insert(int value)
{
_set.insert(value);
}
static T buildFrom(int value)
{
if (_set.find(value) != _set.end()) {
T retval;
retval.assign(value);
return retval;
}
throw std::runtime_error("unexpected value");
}
operator int() const { return _value; }
private:
void assign(int value)
{
_value = value;
}
int _value;
static std::set<int> _set;
};
template<typename T> std::set<int> Enum<T>::_set;
class Apples: public Enum<Apples> {};
class Oranges: public Enum<Oranges> {};
class Proxy
{
public:
Proxy(int value): _value(value) {}
template<typename T>
operator T()
{
T theEnum;
return theEnum.buildFrom(_value);
}
int _value;
};
Proxy convert(int value)
{
return Proxy(value);
}
int main()
{
Apples::insert(4);
Apples::insert(8);
Apples a = convert(4); // works
std::cout << a << std::endl; // prints 4
try {
Apples b = convert(9); // throws
}
catch (std::exception const& e) {
std::cout << e.what() << std::endl; // prints "unexpected value"
}
try {
Oranges b = convert(4); // also throws
}
catch (std::exception const& e) {
std::cout << e.what() << std::endl; // prints "unexpected value"
}
}
Vous pouvez ensuite utiliser le code que j'ai posté ici pour activer les valeurs.
Non, il n'y a pas d'introspection en C++, pas plus qu'il n'y a de fonctionnalité intégrée de "vérification de domaine".
Si vous êtes prêt à répertorier vos valeurs enum en tant que paramètres de modèle, vous pouvez le faire en C++ 11 avec des modèles varadic. 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.
Peut-être pas aussi générique que vous le souhaitez, mais le code de vérification lui-même est généralisé, il vous suffit de spécifier l'ensemble des valeurs. Cette approche gère les espaces, les valeurs arbitraires, etc.
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<typename std::underlying_type<EnumType>::type>(V) || super::is_value(v);
}
EnumType convert(IntType v)
{
if (!is_value(v)) throw std::runtime_error("Enum value out of range");
return static_cast<EnumType>(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);
}
Vous ne devriez pas vouloir que quelque chose comme ce que vous décrivez existe, je crains qu'il y ait des problèmes dans la conception de votre code.
De plus, vous supposez que les énumérations vont dans une plage, mais ce n'est pas toujours le cas:
enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };
Ce n'est pas dans une plage: même si c'était possible, êtes-vous censé vérifier chaque entier de 0 à 2 ^ n pour voir s'il correspond à la valeur de certaines enum?
L'alternative C++ 0x à la version "laide" permet plusieurs enums. Utilise les listes d’initialisation plutôt que les commutateurs, un peu plus propre IMO. Malheureusement, cela ne contourne pas la nécessité de coder en dur les valeurs enum.
#include <cassert> // assert
namespace // unnamed namespace
{
enum class e1 { value_1 = 1, value_2 = 2 };
enum class e2 { value_3 = 3, value_4 = 4 };
template <typename T>
int valid_enum( const int val, const T& vec )
{
for ( const auto item : vec )
if ( static_cast<int>( item ) == val ) return val;
throw std::exception( "invalid enum value!" ); // throw something useful here
} // valid_enum
} // ns
int main()
{
// generate list of valid values
const auto e1_valid_values = { e1::value_1, e1::value_2 };
const auto e2_valid_values = { e2::value_3, e2::value_4 };
auto result1 = static_cast<e1>( valid_enum( 1, e1_valid_values ) );
assert( result1 == e1::value_1 );
auto result2 = static_cast<e2>( valid_enum( 3, e2_valid_values ) );
assert( result2 == e2::value_3 );
// test throw on invalid value
try
{
auto result3 = static_cast<e1>( valid_enum( 9999999, e1_valid_values ) );
assert( false );
}
catch ( ... )
{
assert( true );
}
}