2016-06-10 10 views
5

C++11 Bereiche sind groß, sollten Sie sie verwenden, wann immer dies möglich ist. Manchmal müssen Sie jedoch eine Ganzzahl in einen Bereichswert umwandeln (sagen Sie, wenn Sie es von einer Benutzereingabe erhalten).So können Sie ganzzahlige Typen sicher in Bereiche umwandeln

Gibt es eine sichere Möglichkeit, dies zu tun und festzustellen, wenn der Wert nicht gültig ist (d. H. Außerhalb der zulässigen Werte der Enumeration)?

Ich glaube, nur mit static_cast führt zu undefiniertem Verhalten, wenn die ganze Zahl nicht gültig ist. Gibt es eine allgemeine Vorgehensweise, die es nicht erfordert, eine Konvertierungsfunktion für jeden Bereichsaufzählungstyp von Hand zu schreiben (und die jedes Mal aktualisiert werden muss, wenn Sie der Aufzählung einen neuen Wert hinzufügen)?

+2

Das Verhalten ist nicht undefiniert. Abschnitt 7.2, Absatz 10 sagt "Ein Ausdruck der arithmetischen oder Aufzählungstyp kann explizit in einen Aufzählungstyp konvertiert werden. Der Wert ist unverändert, wenn es im Bereich der Aufzählungswerte des Aufzählungstyps ist; ** andernfalls ist der resultierende Aufzählungswert nicht angegeben **. " Der Wert ist also nicht spezifiziert, aber das Verhalten ist nicht undefiniert (keine nasalen Dämonen). – Cornstalks

+0

@Cornstalks, danke für den Hinweis. Das ist definitiv besser als nicht definiert. Dennoch steht die Frage: Gibt es eine nette Möglichkeit festzustellen, ob der Wert ungültig ist? – toth

+0

mir ist leider nicht bekannt. – Cornstalks

Antwort

3

Ein üblicher Weg, das zu tun ist in Ihrem Enum eine Endmarkierung

enum class Colors : char 
{ 
    Red, 
    Green, 
    Blue, 
    Last_Element 
} 

Mit diesem Ansatz umfasst, bei der Konvertierung, können Sie überprüfen, ob der Wert, den Sie verwenden weniger als der Wert von Last_Element ist .

Betrachten Sie die folgende Funktion:

template<typename T> 
typename std::enable_if<std::is_enum<T>::value, bool>::type 
    IsValidEnumIntegral<T>(int integral) 
{ 
    return (int)(T::Last_Element) > integral; 
} 

und Sie es wie so verwenden könnte:

if(IsValidEnumIntegral<Colors>(2)) 
    //do whatever 

Dies würde arbeiten für jeden Enum Sie mit einem Element gemacht genannt Last_Element. Sie könnten weiter gehen, um eine ähnliche Funktion zu erstellen, die dann automatisch für Sie konvertiert wird.

Hinweis: Dies wird nicht getestet. Ich bin dazu momentan nicht in der Lage, aber ich denke, das könnte funktionieren.

BEARBEITEN: Dies funktioniert nur, wenn die fragliche Enum eine Reihe von Ganzzahlen ohne Lücken für seine Elemente verwendet. Die angegebene Funktion nimmt auch an, dass die Aufzählung keine negativen ganzen Zahlen enthält, obwohl ein First_Element leicht hinzugefügt werden kann.

+0

Dies würde nur funktionieren, wenn es keine Löcher in der 'enum' gibt. Wenn es Lücken gibt, kann dies immer noch fehlschlagen. – NathanOliver

+0

@NathanOliver Es gibt keine perfekte Lösung, Sie müssen die Einschränkungen akzeptieren. – Barmar

+0

Wenn Sie dies wirklich tun müssen, wäre der richtige Weg, eine Klasse zu definieren, deren Konstruktor genau die gewünschten Überprüfungen durchführt, anstatt eine Enumeration zu verwenden. – Barmar

3

[dcl.enum]/8:

Für eine Aufzählung, deren zugrunde liegenden Typ festgelegt ist, die Werte der Aufzählung sind die Werte des zugrundeliegenden Typs.

Dazu gehören alle scoped Aufzählungen, weil der zugrunde liegende Typ eines scoped Enum standardmäßig int:

Der zugrunde liegende Typ kann explizit angegeben werden unter Verwendung eines ENUM-Basis. Bei einem Aufzählungsauflistungstyp ist der zugrunde liegende Typ int, wenn er nicht explizit angegeben ist. In beiden Fällen ist der zu Grunde liegende Typ , der ist, fest.

Somit kann durch Überprüfung, ob der Eingangswert innerhalb des Bereichs des zugrunde liegenden Typ der Enumeration ist (die Sie mit std::numeric_limits und std::underlying_type überprüfen können), können Sie sicher sein, dass die static_cast immer haben, wird das Verhalten gut definierte .

Das ist jedoch nicht genug, wenn der Rest Ihres Programms nicht bereit ist, jeden Wert innerhalb des Bereichs des zugrunde liegenden Typs des Enums zu behandeln. In diesem Fall müssen Sie die Validierung selbst durchführen, möglicherweise mit einer Antwort von @ Altainia.

+1

Ich hatte nie die Spezifikation für Enums sorgfältig gelesen, und das ist nicht das, was ich erwartet habe. Ich habe heute etwas gelernt; Danke. – Nemo

+0

Das ist ein wirklich guter Punkt, hatte missverstanden, was "Werte der Aufzählung" im Standard bedeutet. Danke für das Aufzeigen! – toth

1

Ein anderer Weg, den ich in einigen Codebasen gemacht habe, ist im Grunde genommen ein Typmerkmal, um Ihre Bereichsgrenzen mit zusätzlichen Informationen zu dekorieren.

Die Idee ist, dass Sie einige Merkmale wie

namespace mpl { 
    template <typename T> 
    struct GetEnumData; 
} 

Dann haben würden, wenn Sie einen „intelligenten“ Enum erklären, werden Sie auch diese Eigenschaft auf eine Struktur zu verknüpfen spezialisieren, die Metadaten über die scoped Enum enthält wie die rechtlichen Werte sind. Oder eine Liste von Strings, die den Namen der enum-Werte entsprechen, sodass Sie die enum-to-string-Konvertierung und zurück durchführen können.

enum class my_enum { a, b, c }; 
namespace mpl { 
    template<> 
    struct GetEnumData<my_enum> { 
    static constexpr std::size_t my_number = 3; 
    static const char * const my_strings []() { 
     return {"a", "b", "c"}; 
    } 
    static const int my_values []() { 
     return {0, 1, 2}; 
    } 
    } 
} // end namespace mpl 

N.B. Das Erzeugen des obigen Typenmerkmals ist ein wenig mühsam, daher verwenden Sie am Ende immer ein Makro für Ihre "Smart Enum" -Deklarationen. Wenn Sie Makros wirklich nicht mögen, ist dieser Ansatz nicht für Sie. Zumindest bis C++ in zukünftigen Versionen noch mehr Introspektions-Features hinzufügt (Daumen drücken).

Sobald Sie diese Art Trait obwohl, können Sie nützliche Dinge wie "enum_cast" tun, die eine Zeichenfolge zum Beispiel zu einer Aufzählung analysieren.

template <typename T> 
T enum_cast(const std::string & input) { 
    using data = GetEnumData<T>; 

    for (std::size_t idx = 0; idx < data::my_number; ++idx) { 
    if (data::my_strings()[idx] == input) { 
     return static_cast<T>(data::my_values()[idx]); 
    } 
    } 
    throw bad_enum_value(input); 
} 

Und man konnte etwas ähnliches für int ‚s oder andere integrale Arten tun.

Verwandte Themen