wake-up-neo.net

Generischer Weg, um in C++ int in Enum zu setzen

Gibt es eine generische Möglichkeit, int in enum in C++ umzuwandeln?

Wenn int in den Bereich eines enum fällt, sollte es einen enum-Wert zurückgeben, andernfalls werfen Sie ein exception aus. Gibt es eine Möglichkeit, es generisch zu schreiben? Es sollte mehr als ein enum type unterstützt werden.

Hintergrund: Ich habe einen externen enum type und no control über den Quellcode. Ich möchte diesen Wert in einer Datenbank speichern und abrufen.

79
Leonid

Das Offensichtliche ist, Ihr Enum zu kommentieren:

// 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);
}

Das Array muss mit e auf dem neuesten Stand gehalten werden. Dies ist ein Ärgernis, wenn Sie nicht der Autor von e sind. Wie Sjoerd sagt, kann es wahrscheinlich mit jedem anständigen Build-System automatisiert werden.

In jedem Fall haben Sie es mit 7.2/6 zu tun:

Für eine Aufzählung, wobei emin der .__ ist. kleinster enumerator und emax ist der am größten sind die Werte der Aufzählung sind die Werte des zugrunde liegenden Typs im Bereich von bmin bis bmax, wobei bmin und bmax sind jeweils die kleinste und größte Werte von kleinstes Bitfeld, in dem emin gespeichert werden kann und emax. Es ist möglich, ein .__ zu definieren. Aufzählung, deren Werte nicht .__ sind. von einem seiner Enumeratoren definiert.

Wenn Sie also nicht der Autor von e sind, haben Sie möglicherweise keine Garantie, dass gültige Werte von e tatsächlich in der Definition erscheinen.

37
Steve Jessop

Hässlich.

enum MyEnum { one = 1, two = 2 };

MyEnum to_enum(int n)
{
  switch( n )
  {
    case 1 :  return one;
    case 2 : return two;
  }
  throw something();
}

Nun zur richtigen Frage. Warum brauchst du das? Der Code ist hässlich, nicht leicht zu schreiben (*?) Und nicht einfach zu warten und nicht leicht in Ihren Code zu integrieren. Der Code, der Ihnen sagt, dass es falsch ist. Warum bekämpfen?

BEARBEITEN:

Alternativ, vorausgesetzt, dass Aufzählungen ganzzahlige Typen in C++ sind:

enum my_enum_val = static_cast<MyEnum>(my_int_val);

aber das ist noch hässlicher als oben, viel fehleranfälliger, und es wird nicht werfen, wie Sie wollen.

20
John Dibling

Wenn sich die Werte, wie Sie beschrieben haben, in einer Datenbank befinden, warum sollten Sie nicht einen Codegenerator schreiben, der diese Tabelle liest und eine .h- und .cpp-Datei mit der Enum-Funktion und der Funktion to_enum(int) erstellt.

Vorteile:

  • Einfach eine to_string(my_enum)-Funktion hinzufügen.
  • Wenig Wartung erforderlich
  • Datenbank und Code sind synchron
3
Sjoerd

Was denkst du über dieses?

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

Sie können dann den von mir veröffentlichten Code here verwenden, um die Werte zu aktivieren.

2
Simone

Nein, es gibt keine Introspektion in C++ und auch keine eingebaute "Domain Check" -Funktion.

2
luke

Wenn Sie bereit sind, Ihre Aufzählungswerte als Vorlagenparameter aufzulisten, können Sie dies in C++ 11 mit varadic-Vorlagen tun. Sie können dies als eine gute Sache betrachten, die es Ihnen ermöglicht, Teilmengen der gültigen Aufzählungswerte in verschiedenen Kontexten zu akzeptieren. häufig nützlich, wenn Codes aus externen Quellen analysiert werden.

Vielleicht nicht ganz so allgemein, wie Sie möchten, aber der Prüfcode selbst ist verallgemeinert. Sie müssen nur die Menge der Werte angeben. Dieser Ansatz behandelt Lücken, willkürliche Werte usw.

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);
}
1
janm

Sie sollten nicht möchten, dass das, was Sie beschreiben, existiert, ich befürchte, es gibt Probleme in Ihrem Code-Design.

Sie gehen auch davon aus, dass die Aufzählungen in einem Bereich liegen, aber das ist nicht immer der Fall:

enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };

Dies liegt nicht in einem Bereich: Selbst wenn es möglich wäre, sollten Sie jede ganze Zahl von 0 bis 2 ^ n überprüfen, um zu sehen, ob sie mit dem Wert einiger Enummen übereinstimmen?

1
Simone

C++ 0x Alternative zur "hässlichen" Version, ermöglicht mehrere Enumerationen. Verwendet Initializer-Listen statt Schalter, ein bisschen sauberer IMO. Leider funktioniert das nicht, wenn Sie die Aufzählungswerte hart codieren müssen.

#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 );
    }
}
0
Tom