Generische Möglichkeit, int in C ++ in enum umzuwandeln

81

Gibt es eine generische Art und Weise zu werfen , intum enumin C++?

Wenn es intin den Bereich von a fällt enum, sollte es einen enumWert zurückgeben, andernfalls werfen Sie einen exception. Gibt es eine Möglichkeit, es generisch zu schreiben ? Es enum typesollte mehr als eine unterstützt werden.

Hintergrund: Ich habe einen externen Aufzählungstyp und keine Kontrolle über den Quellcode. Ich möchte diesen Wert in einer Datenbank speichern und abrufen.

Leonid
quelle
enum e{x = 10000};fällt in diesem Fall 9999in die Reichweite der enum?
Armen Tsirunyan
Nein, 9999fällt nicht.
Leonid
9
Gute Frage. Wie für jedes "Warum?" Was erscheinen wird, lassen Sie mich einfach "Deserialisierung" sagen - scheint mir ein Grund genug zu sein. Ich würde mich auch freuen, eine C ++ 0x-kompatible Antwort für zu hören enum class.
Kos
9
"Range" ist hier das falsche Wort, vielleicht "Domain"?
Constantin
boost :: numeric_cast <> löst eine positive oder negative Überlaufausnahme aus, wenn der Wert außerhalb der Grenzen liegt. Aber nicht sicher, ob es auch für Aufzählungstypen gilt. Das kannst du versuchen.
Yasouser

Antworten:

37

Das Offensichtliche ist, Ihre Aufzählung 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);
}

Sie müssen das Array auf dem neuesten Stand halten e, was ein Ärgernis ist, wenn Sie nicht der Autor von sind e. 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:

Bei einer Aufzählung, bei der emin der kleinste Aufzähler und emax der größte ist, sind die Werte der Aufzählung die Werte des zugrunde liegenden Typs im Bereich von bmin bis bmax, wobei bmin und bmax die kleinsten und größten Werte des kleinsten sind Bitfeld, das Emin und Emax speichern kann. Es ist möglich, eine Aufzählung zu definieren, deren Werte von keinem ihrer Aufzähler definiert wurden.

Wenn Sie also nicht der Autor von sind e, haben Sie möglicherweise eine Garantie dafür, dass gültige Werte von etatsächlich in der Definition von angezeigt werden .

Steve Jessop
quelle
22

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 eigentlichen Frage. Warum brauchst du das? Der Code ist hässlich, nicht einfach zu schreiben (*?) Und nicht einfach zu warten und nicht einfach in Ihren Code zu integrieren. Der Code sagt Ihnen, dass es falsch ist. Warum dagegen ankämpfen?

BEARBEITEN:

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

enum my_enum_val = static_cast<MyEnum>(my_int_val);

Aber das ist noch hässlicher als oben, viel anfälliger für Fehler, und es wird nicht so werfen, wie Sie es wünschen.

John Dibling
quelle
Dies unterstützt nur einen Typ, MyEnum.
Simone
2
@Leonid: Meines Wissens kann dies nicht generisch erfolgen. Auf einer bestimmten Ebene muss jede Lösung, die Sie throwfür ungültige Typen finden (oder etwas Besonderes tun), einen Schalter haben, wie ich ihn gepostet habe.
John Dibling
2
Warum ist das -1'ed? Es ist die richtige Antwort. Nur weil es nicht die Antwort ist, auf die einige gehofft haben, heißt das nicht, dass es falsch ist.
John Dibling
11
A static_cast<MyEnum>wird auch funktionieren und sollte demreinterpret_cast<MyEnum>
Sjoerd
1
Dieser Ansatz funktioniert gut und noch besser, wenn Sie ein Tool zum Generieren der Funktion verwenden.
Nick
3

Wenn sich die Werte, wie Sie beschreiben, in einer Datenbank befinden, warum nicht einen Codegenerator schreiben, der diese Tabelle liest und eine .h- und .cpp-Datei mit der Aufzählung und einer to_enum(int)Funktion erstellt?

Vorteile:

  • Einfach eine to_string(my_enum)Funktion hinzuzufügen .
  • Wenig Wartung erforderlich
  • Datenbank und Code sind synchron
Sjoerd
quelle
Es gibt keine Kontrolle über den Quellcode vom Typ Enum . Ich bin damit einverstanden, dass eine Einrichtung generiert werden kann, die die Konvertierung implementiert. Der Einrichtung sind jedoch keine Änderungen / Erweiterungen bekannt, die am externen Aufzählungstyp vorgenommen wurden (es sei denn, sie werden jedes Mal zur Kompilierungszeit ausgeführt).
Leonid
@Leonid Lesen Sie dann diesen Enum-Header und generieren Sie die darauf to_enum(int)basierende Funktion.
Sjoerd
@Leonid Jedes seriöse Projektmanagementsystem kann sogar makedas Datum zweier Dateien vergleichen, um festzustellen, ob der Generator erneut ausgeführt werden muss.
Sjoerd
Ich denke, ich werde vorerst eine einfachere Lösung für den Generator wählen. Aber danke für die Idee.
Leonid
Wir verwenden dieses Schema an unserem Arbeitsplatz. Ein Tool generiert den .hpp-Code aus einer Vorlage. Die Vorlage ist minimal.
Nick
3

Nein, in C ++ gibt es weder eine Selbstbeobachtung noch eine integrierte Funktion zur "Domänenprüfung".

Luke
quelle
2

Was denkst du über diesen?

#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önnten dann Code verwende ich gepostet hier auf Werte zu wechseln.

Simone
quelle
Sie müssen noch Apples::insert(4)irgendwo etwas hinzufügen , daher hat dies keinen Vorteil gegenüber einem Switch.
Sjoerd
1
Er sagte, dass Werte aus einer Datenbank stammen, deshalb habe ich eine "Einfügemethode" hinzugefügt.
Simone
1

Sie sollten nicht wollen, dass so etwas wie das, was Sie beschreiben, existiert. Ich befürchte, es gibt Probleme in Ihrem Code-Design.

Sie gehen auch davon aus, dass 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: Sollten Sie, selbst wenn dies möglich war, jede Ganzzahl von 0 bis 2 ^ n überprüfen, um festzustellen, ob sie mit dem Wert einer Aufzählung übereinstimmt?

Simone
quelle
Wie würden Sie sonst Enum-Werte aus der Datenbank abrufen? Die Ganzzahlen sind zur Kompilierungszeit bekannt. Warum ist eine generische Konvertierung basierend auf Vorlagen nicht möglich?
Leonid
2
@Leonid: Weil man auf einer bestimmten Ebene einen Schalter haben muss, wie ich sagte.
John Dibling
2
@Leonid Templates sind keine Wunderwaffe, um jedes erdenkliche Problem zu lösen.
Sjoerd
John hat recht. Sie benötigen einen Typ, der komplexer als eine Aufzählung ist, um das zu tun, was Sie wollen. Ich denke, dass dies mit einer Klassenhierarchie machbar ist.
Simone
Ich habe eine Lösung veröffentlicht, die Klassenhierarchie verwendet.
Simone
1

Wenn Sie bereit sind, Ihre Aufzählungswerte als Vorlagenparameter aufzulisten, können Sie dies in C ++ 11 mit varadischen 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. oft nützlich beim Parsen von Codes aus externen Quellen.

Vielleicht nicht ganz so allgemein wie Sie möchten, aber der Prüfcode selbst ist verallgemeinert. Sie müssen nur den Wertesatz angeben. Dieser Ansatz behandelt Lücken, beliebige 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);
}
janm
quelle
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert. - Von der Überprüfung
Tas
1
@Tas Es ist ein Link zu einer anderen SO-Antwort - es gibt nicht dieselben Probleme wie bei einem externen Link. Trotzdem aktualisiert.
Janm
0

C ++ 0x Alternative zur "hässlichen" Version, ermöglicht mehrere Aufzählungen. Verwendet Initialisierungslisten anstelle von Schaltern, ein bisschen sauberer IMO. Leider funktioniert dies nicht um die Notwendigkeit herum, die Enum-Werte fest zu codieren.

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