80 votes

Moyen générique de cast int enum en C++

Est-il un générique façon de jeter int de enum en C++?

Si int tombe dans la gamme de l' enum il doit retourner un enum de la valeur, autrement jeter un exception. Est-il possible de l'écrire de façon générique? Plus d'un enum type doit être pris en charge.

Contexte: j'ai un externe enum type et aucun contrôle sur le code source. Je voudrais stocker cette valeur dans une base de données et de les récupérer.

37voto

Steve Jessop Points 166970

La chose la plus évidente consiste à 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);
}

Vous avez besoin de la matrice pour être tenu à jour avec e, ce qui est une nuisance si vous n'êtes pas l'auteur de l' e. Comme Sjoerd dit, il peut probablement être automatisé à l'aide de tout bon système de construction.

Dans tous les cas, vous êtes contre 7.2/6:

Pour une énumération où emin est l' le plus petit agent recenseur et emax est l' 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, la la plus petite et la plus grande des valeurs de la plus petit champ qui permet de stocker emin et emax. Il est possible de définir une l'énumération qui a des valeurs non défini par l'une quelconque de ses agents recenseurs.

Donc, si vous n'êtes pas l'auteur de l' e, vous peut ou peut ne pas avoir la garantie que les valeurs valides de e fait apparaître dans sa définition.

21voto

John Dibling Points 56814

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 la vraie question. Pourquoi avez-vous besoin? Le code est moche, pas facile à écrire (*?) et pas facile à maintenir, et pas facile à intégrer à votre code. Le code qu'il vous dit que c'est mal. Pourquoi se battre?

EDIT:

Sinon, étant donné que les énumérations sont des types intégraux en C++:

enum my_enum_val = static_cast<MyEnum>(my_int_val);

mais ce qui est encore plus laide que ci-dessus, beaucoup plus sujettes à des erreurs, et il ne lèvera pas que vous le désirez.

3voto

Sjoerd Points 4548

Si, comme vous le décrivez, les valeurs sont dans une base de données, pourquoi ne pas écrire un générateur de code qui lit ce tableau et crée un .h et .rpc fichier avec les enum et un to_enum(int) fonction?

Avantages:

  • Facile d'ajouter un to_string(my_enum) fonction.
  • Peu d'entretien nécessaire
  • Base de données et le code sont en phase

2voto

Simone Points 6673

Que pensez-vous 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 passer sur les valeurs.

1voto

Simone Points 6673

Vous devriez voulez pas quelque chose comme ce que vous décrivez existe, j'en ai peur il ya des problèmes dans votre conception du code.

Aussi, vous supposez que les énumérations viennent dans une gamme, 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, vous êtes censé vérifier chaque nombre entier de 0 à 2^n pour voir si elles correspondent à une valeur de l'enum?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X