2012-03-26 6 views
27

Ich habe einige generische Code, der mit Flags funktioniert mit C++ 11 enum class Typen angegeben. Bei einem Schritt möchte ich wissen, ob eines der Bits in der Flagge gesetzt ist. Derzeit verwende ich den Code:Wie kann ich eine Enum-Klasse in einem booleschen Kontext verwenden?

if (flags != static_cast<E>(0)) // Works, but ugly. 

ich auch Benutzer zwingen könnte, einen bestimmten Namen für ein All-Null-Feld angeben, die besser lesbar ist, sondern erlegt meine Namenskonventionen für jedermann verwenden es:

if (flags != E::none) // Works, if you manually define none = 0. 

Doch diese beiden liest so schön wie die traditionelle:

if (flags) // Doesn't work with class enums. 

Ist es möglich, eine benutzerdefinierte Funktion angeben, eine Klasse enum in einem booleschen Kontext zu bewerten?

+0

Gibt es einen bestimmten Grund, warum Sie nicht Binärflags verwenden können? Sie sind sauber und effizient. Wie auch immer, Enum-Klassenwerte können nicht in Zahlen umgewandelt werden. Sie müssen Flags verwenden! = E :: none, wenn Sie aufzählen. Weißt du, du kannst einfach eine Nicht-Enum-Klasse erstellen und statisch const int whatever = 1, statisch const int whatever_else = 2 ... usw. verwenden. und überladen nur ein paar Betreiber. –

+1

@OrgnlDave: "Binäre Flags" was bedeutet, Boole? Als Bitfelder sind sie oft IB/UB und mit regelmäßiger Ausrichtung würde ich sie nicht effizient nennen, noch sauber, wenn ich mehrere gleichzeitig kopieren muss. Ich sehe den Wert nicht, wenn ich statische Ints vorschlage, ich könnte einfach eine normale Enum verwenden und mindestens ein bisschen Typsicherheit bekommen. –

+0

"Ich könnte einfach ein normales Enum verwenden und mindestens ein bisschen Typ-Sicherheit bekommen." Dann müssen Sie akzeptieren, dass Sie damit nicht mehr über Bits sprechen. – GManNickG

Antwort

14

Ist es möglich, eine benutzerdefinierte Funktion anzugeben, um eine Klassenaufzählung in einem booleschen Kontext auszuwerten?

Ja, aber nicht automatisch. Das manuelle Aufrufen einer Funktion ist noch eleganter als die anderen dargestellten Alternativen.

Wählen Sie einfach einen schönen Funktionsnamen, z. B. any, und implementieren Sie ihn. Die Überladungsauflösung stellt sicher, dass Ihre Funktion mit allen anderen gut spielt.

bool any(E arg) 
    { return arg != E::none; } 

... 

if (any(flags)) { 
    ... 

Sieht gut genug für mich.


Update: wenn Sie dies mehrere Aufzählungstypen anwenden möchten, kann es als Templat werden:

template< typename enum_type > // Declare traits type 
struct enum_traits {}; // Don't need to declare all possible traits 

template<> 
struct enum_traits<E> { // Specify traits for "E" 
    static constexpr bool has_any = true; // Only need to specify true traits 
}; 

template< typename enum_type > // SFINAE makes function contingent on trait 
typename std::enable_if< enum_traits<enum_type>::has_any, 
    bool >::type 
any(enum_type e) 
    { return e != enum_type::none; } 

Ich habe mit dieser Art von Mechanismus für andere Dinge und begegnet nie Nebenwirkungen oder Probleme: v).

Sie könnten das Merkmal überspringen und die SFINAE-Bedingung auf etwas wie enum_type::none == enum_type::none setzen, um lediglich auf das Vorhandensein von none und den Gleichheitsoperator zu prüfen, aber das wäre weniger explizit und sicher.

6

Nein, nicht so. Konvertierungsoperatoren müssen Mitglieder sein, und Enums dürfen keine Mitglieder haben. Ich denke, das Beste, was Sie tun können, ist Vergleich mit none, oder, wenn es keinen none Enumerator gibt, wickeln Sie die static_cast in einer Funktion.

19

Wie @RMatin sagt. Aber man konnte überlasten operator!

bool operator!(E e) { 
    return e == static_cast<E>(0); 
} 

Damit Sie die !!e Idiom verwenden können

if(!!e) { 
    ... 
} 
6

Wenn Sie ein Flag-Feld haben (dh ein Bitfeld) Ich würde stark beraten Sie nicht enum class zu verwenden für Bitfelder.

Stark typisierte enums existieren, nun, stark typisiert. Es macht die Enumeratoren zu etwas mehr als nur benannten konstanten Ganzzahlen wie reguläre enums. Die Idee ist, dass, wenn Sie eine Variable eines enum class Typs haben, der Inhalt immer genau mit einem der Enumeratorwerte übereinstimmen sollte. Aus diesem Grund gibt es keine implizite Konvertierung von oder zu Integer-Typen.

Aber das ist nicht das, was Sie tun. Sie nehmen ein Bitfeld, das eine Zusammenstellung von Aufzählungswerten ist. Diese Zusammensetzung ist selbst keiner dieser Werte; Es ist eine Kombination von ihnen. Daher sind Sie liegen, wenn Sie sagen, dass Sie die enum class Art nehmen; Sie nehmen wirklich nur eine Ganzzahl ohne Vorzeichen, die könnte einer der enum class Enumeratoren sein.

Zum Beispiel:

enum class Foo 
{ 
    First = 0x01, 
    Second = 0x02, 
    Third = 0x04, 
}; 

Foo val = Foo::First | Foo::Second; 

val in diesem Fall enthält keine First, Second oder Third. Sie haben verloren starke Eingabe, weil es keinen der Typen enthält.

enum class Werte können nicht implizit in bool konvertiert werden; Sie können nicht implizit in ganze Zahlen konvertiert werden. und sie können die meisten mathematischen Operationen nicht implizit ausführen. Sie sind undurchsichtige Werte.

Und daher sind sie ungeeignet für die Verwendung als Bitfelder. Der Versuch, enum class auf solch unangemessene Weise zu verwenden, wird nur zu einer Menge Casting führen. Benutze einfach ein normales altes enum und rette dir den Schmerz.

+6

"Sie haben starke Schreibarbeit verloren, weil sie keinen der Typen enthält." Sie meinen, es enthält keinen der vordefinierten _values_. Das bedeutet nicht, dass ich irgendeine besondere Art von Verstoß gegen den Typ gemacht habe. Darüber hinaus sind sie _nicht_ undurchsichtige Werte - ihre Wertdarstellung ist in Bezug auf den zugrundeliegenden Speichertyp definiert - sie sind lediglich _explizite_ Werte. Ich schätze den Vorschlag, dass Enum-Klassen hier nicht das richtige Werkzeug sind, aber zu sagen, was ich will, ist "illegal" und nicht "muss so explizit sein, dass es nicht mehr so ​​wertvoll ist". –

+0

@JoeWreschnig: Zuerst habe ich gesagt, dass der Code, den ich geschrieben habe, illegal war, was er * ist. Zweitens, was Sie wollen * erfordert * viele explizite und sinnlose Umwandlungen, was das Problem ist, das Sie uns zu lösen gebeten haben. Die Lösung besteht darin, keine Dinge mehr zu verwenden, die explizite und sinnlose Umwandlungen erfordern. Drittens ist es eine Verletzung vom Typ * semantic *. Der springende Punkt, Enumerationen stark zu typisieren, ist eine gewisse syntaktische Gewissheit, dass die Werte, die an Sie weitergegeben werden, eine der rechtlichen Werte sind. Ja, die Leute können lügen, aber sie müssen zumindest das tun, was oft als Zeichen dafür angesehen wird, etwas Schattiges zu tun. –

+1

-1: Lustig, ich habe gerade eine andere Frage beantwortet, wie 'enum' ideal für Fahnensets sind. Das Problem mit dem Beispiel von ideone ist, dass die Überladung 'operator |' fehlt. 'Enum'-Typen sind so definiert, dass sie der Wertesemantik des zugrunde liegenden Typs folgen, und wenn dies die bitweise Arithmetik unterstützt, dann sind Typsicherheit und arithmetische Sicherheit beide optimiert. – Potatoswatter

0

Ein kurzes Beispiel für Enum-Flags unten.

#indlude "enum_flags.h" 

ENUM_FLAGS(foo_t) 
enum class foo_t 
    { 
    none   = 0x00 
    ,a    = 0x01 
    ,b    = 0x02 
    }; 

ENUM_FLAGS(foo2_t) 
enum class foo2_t 
    { 
    none   = 0x00 
    ,d    = 0x01 
    ,e    = 0x02 
    }; 

int _tmain(int argc, _TCHAR* argv[]) 
    { 
    if(flags(foo_t::a & foo_t::b)) {}; 
    // if(flags(foo2_t::d & foo_t::b)) {}; // Type safety test - won't compile if uncomment 
    }; 

ENUM_FLAGS (T) ist ein Makro, definiert in enum_flags.h (weniger als 100 Linien, frei ohne Einschränkungen zu verwenden).

6
struct Error { 
    enum { 
     None  = 0, 
     Error1  = 1, 
     Error2  = 2, 
    } Value; 

    /* implicit */ Error(decltype(Value) value) : Value(value) {} 

    explicit operator bool() { 
     return Value != Error::None; 
    } 
}; 

inline bool operator==(Error a, Error b) { 
    return a.Value == b.Value; 
} 

inline bool operator!=(Error a, Error b) { 
    return !(a == b); 
} 

enum hat keine überladenen Operator für jetzt, so wickeln Sie es ein in class oder struct.

0

ich überlastete in der Regel den einstelligen + Operator für fahnenartige enum classes, so dass ich die folgenden Aktionen durchführen:

#define ENUM_FLAGS (FlagType, UnderlyingType)       \ 
    /* ... */               \ 
    UnderlyingType operator+(const FlagType &flags) {     \ 
      return static_cast<UnderlyingType>(flags)      \ 
    }                 \ 
    /* ... */               \ 
    FlagType operator&(const FlagType &lhs, const FlagType &rhs) {  \ 
      return static_cast<FlagType>(+lhs & +rhs)      \ 
    }                 \ 
    /* ... */               \ 
    FlagType &operator|=(FlagType &lhs, const FlagType &rhs) {   \ 
      return lhs = static_cast<FlagType>(+lhs | +rhs)    \ 
    }                 \ 
    /* ... */               \ 
    /***/ 

// .... 

enum class Flags: std::uint16_t { 
    NoFlag = 0x0000, 
    OneFlag = 0x0001, 
    TwoFlag = 0x0002, 
    // ....  
    LastFlag = 0x8000 
}; 

ENUM_FLAGS(Flags, std::uint16_t) 

auto flagVar = Flags::NoFlag; 

// ... 

flagVar |= Flags::OneFlag; 

// ... 

if (+(flagVar & Flags::OneFlag)) { 
    /// ... 
} 
Verwandte Themen