Wie verwende ich Enums als Flags in C ++?

187

Das Behandeln von enums als Flags funktioniert in C # über das [Flags]Attribut gut, aber wie geht das in C ++ am besten?

Zum Beispiel möchte ich schreiben:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

seahawk.flags = CanFly | EatsFish | Endangered;

Ich erhalte jedoch Compilerfehler bezüglich int/ enumKonvertierungen. Gibt es eine schönere Möglichkeit, dies auszudrücken, als nur stumpfes Casting? Vorzugsweise möchte ich mich nicht auf Konstrukte aus Bibliotheken von Drittanbietern wie Boost oder Qt verlassen.

EDIT: Wie in den Antworten angegeben, kann ich den Compilerfehler vermeiden, indem ich seahawk.flagsals deklariere int. Ich hätte jedoch gerne einen Mechanismus, um die Typensicherheit zu erzwingen, damit jemand nicht schreiben kann seahawk.flags = HasMaximizeButton.

Tony das Pony
quelle
Soweit ich in Visual C ++ 2013 weiß [Flags], funktioniert das Attribut [Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
einwandfrei,
@rivanov, Nein, es funktioniert nicht mit C ++ (2015 auch). Meinten Sie C #?
Ajay
5
@rivanov, Das Attribut [Flags] funktioniert nur mit dem .Net Framework in C ++ CLI. Das native C ++ unterstützt solche Attribute nicht.
Zoltan Tirinda

Antworten:

249

Der "richtige" Weg besteht darin, Bitoperatoren für die Aufzählung wie folgt zu definieren:

enum AnimalFlags
{
    HasClaws   = 1,
    CanFly     = 2,
    EatsFish   = 4,
    Endangered = 8
};

inline AnimalFlags operator|(AnimalFlags a, AnimalFlags b)
{
    return static_cast<AnimalFlags>(static_cast<int>(a) | static_cast<int>(b));
}

Usw. Rest der Bitoperatoren. Ändern Sie nach Bedarf, wenn der Enum-Bereich den Int-Bereich überschreitet.

Eidolon
quelle
42
^ das. Die Frage ist nur, wie Sie die Operatordefinitionen automatisieren / templatisieren können, damit Sie sie nicht jedes Mal neu definieren müssen, wenn Sie eine neue Aufzählung hinzufügen.
Eodabash
10
Ist die Umwandlung von einem beliebigen int zurück in den Aufzählungstyp auch dann gültig, wenn der int-Wert keinem der Bezeichner der Aufzählung entspricht?
Ingo Schalk-Schupp
8
Das ist völliger Unsinn. Welches Mitglied von AnimalFlagswird durch den Ausdruck dargestellt HasClaws | CanFly? Dafür sind wir nicht daenum . Verwenden Sie Ganzzahlen und Konstanten.
Leichtigkeitsrennen im Orbit
25
@ LightnessRacesinOrbit: Das stimmt nicht. Die Domäne eines Aufzählungstyps ist die Domäne des zugrunde liegenden Typs - nur bestimmten wurde ein Name zugewiesen. Und um Ihre Frage zu beantworten: Das Mitglied " (HasClaws | CanFly)".
Xeo
5
@MarcusJ: Wenn Sie Ihre Werte auf Zweierpotenzen beschränken, können Sie Ihre Aufzählungen als Bit-Flags verwenden. Wenn Sie also eine 3 erhalten, wissen Sie sowohl HasClaws(= 1) als auch CanFly(= 2). Wenn Sie stattdessen nur die Werte 1 bis 4 direkt zuweisen und eine 3 erhalten, kann es sich um eine einzelne EatsFishoder eine Kombination aus HasClawsund handeln CanFly. Wenn Ihre Aufzählung nur exklusive Zustände angibt, sind aufeinanderfolgende Werte in Ordnung, aber für eine Kombination von Flags müssen die Werte bitexklusiv sein.
Christian Severin
122

Hinweis (auch etwas abseits des Themas): Eine andere Möglichkeit, eindeutige Flags zu erstellen, kann durch eine Bitverschiebung erfolgen. Ich selbst finde das leichter zu lesen.

enum Flags
{
    A = 1 << 0, // binary 0001
    B = 1 << 1, // binary 0010
    C = 1 << 2, // binary 0100
    D = 1 << 3, // binary 1000
};

Es kann Werte bis zu einem int halten, dh meistens 32 Flags, was sich deutlich im Verschiebungsbetrag widerspiegelt.

WoutervD
quelle
2
Könnten Sie bitte das letzte Komma (3,) löschen und nach} einen Doppelpunkt hinzufügen, damit der Code einfach kopiert und eingefügt werden kann? Vielen Dank
Katu
4
Keine Erwähnung von hexadezimal? Blasphemie!
Pharap
1
@ Jamie, Kardinäle beginnen immer mit 1, nur Ordnungszahlen können mit 0 oder 1 beginnen, je nachdem, mit wem Sie sprechen.
Michael
2
@ Michael, das stimmt! In einer Aufzählung reservieren Sie normalerweise 0 für BLAH_NONE. :-) Danke, dass du diese Erinnerung erschüttert hast!
Jamie
1
@Katu • Das überflüssige Komma bei der endgültigen Aufzählung ist nach dem Standard zulässig. Ich mag es nicht, aber ich weiß bereits, was Stroustrup mir sagen würde ... "Du magst es nicht? Du kannst deine eigene Sprache erstellen. Ich habe es getan."
Eljay
55

Für faule Leute wie mich gibt es hier eine Lösung zum Kopieren und Einfügen:

template<class T> inline T operator~ (T a) { return (T)~(int)a; }
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
template<class T> inline T& operator|= (T& a, T b) { return (T&)((int&)a |= (int)b); }
template<class T> inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); }
template<class T> inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); }
user720594
quelle
23
+1 Faulheit ist eine der drei großen Tugenden eines Programmierers: threevirtues.com
Pharap
10
Dies ist eine sehr schöne Lösung. Achten Sie jedoch darauf, dass sie für jeden Typ fröhlich bitweise Operationen ermöglicht. Ich verwende etwas Ähnliches, aber mit der Hinzufügung von Merkmalen, die die Typen identifizieren, auf die es angewendet werden soll, kombiniert mit ein wenig enable_if-Magie.
Rai
@Rai: Sie können es jederzeit in einen Namespace einfügen und usinges gegebenenfalls genau so rel_ops.
Yakov Galka
1
@ybungalobill, aber Sie haben immer noch das gleiche Problem mit den Operationen, die für jeden Typ im Bereich der Verwendung gelten, der vermutlich mit der Aufzählung übereinstimmen würde? Ich denke, Eigenschaften sind höchstwahrscheinlich notwendig.
Rai
19
Verwenden Sie diesen Code nicht. Es öffnet die Tür für JEDE Klasse, die versehentlich bedient wird. Außerdem verwendet der Code eine Besetzung im alten Stil, die nicht durch die strikte GCC-Kompilierung shitalshah.com/p/… geleitet wird .
Shital Shah
44

Hinweis: Wenn Sie in einer Windows-Umgebung arbeiten, ist DEFINE_ENUM_FLAG_OPERATORSin winnt.h ein Makro definiert, das die Aufgabe für Sie erledigt. In diesem Fall können Sie Folgendes tun:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};
DEFINE_ENUM_FLAG_OPERATORS(AnimalFlags)

seahawk.flags = CanFly | EatsFish | Endangered;
Simon Mourier
quelle
43

Welcher Typ ist die Variable seahawk.flags?

In Standard-C ++ sind Aufzählungen nicht typsicher. Sie sind effektiv ganze Zahlen.

AnimalFlags sollten NICHT der Typ Ihrer Variablen sein. Ihre Variable sollte int sein und der Fehler wird verschwinden.

Das Setzen von Hexadezimalwerten, wie von einigen anderen Personen vorgeschlagen, ist nicht erforderlich. Es macht keinen Unterschied.

Die Aufzählungswerte sind standardmäßig vom Typ int. Sie können sie also sicher bitweise ODER kombinieren und zusammenfügen und das Ergebnis in einem Int speichern.

Der Aufzählungstyp ist eine eingeschränkte Teilmenge von int, deren Wert einer der aufgezählten Werte ist. Wenn Sie also einen neuen Wert außerhalb dieses Bereichs erstellen, können Sie ihn nicht zuweisen, ohne eine Variable Ihres Aufzählungstyps umzuwandeln.

Sie können auch die Aufzählungswerttypen ändern, wenn Sie möchten, aber diese Frage hat keinen Sinn.

BEARBEITEN: Auf dem Poster stand, dass sie sich mit der befassen und keinen Wert möchten, der innerhalb des int-Typs nicht vorhanden sein sollte.

Es wäre jedoch vom Typ unsicher, einen Wert außerhalb des Bereichs von AnimalFlags in eine Variable vom Typ AnimalFlags einzufügen.

Es gibt eine sichere Möglichkeit, innerhalb des int-Typs nach Werten außerhalb des Bereichs zu suchen ...

int iFlags = HasClaws | CanFly;
//InvalidAnimalFlagMaxValue-1 gives you a value of all the bits 
// smaller than itself set to 1
//This check makes sure that no other bits are set.
assert(iFlags & ~(InvalidAnimalFlagMaxValue-1) == 0);

enum AnimalFlags {
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8,

    // put new enum values above here
    InvalidAnimalFlagMaxValue = 16
};

Das Obige hindert Sie nicht daran, ein ungültiges Flag aus einer anderen Aufzählung mit dem Wert 1,2,4 oder 8 zu setzen.

Wenn Sie absolute Typensicherheit wünschen, können Sie einfach ein std :: set erstellen und jedes Flag dort speichern. Es ist nicht platzsparend, aber typsicher und bietet Ihnen die gleichen Fähigkeiten wie ein Bitflag-Int.

C ++ 0x Hinweis: Stark typisierte Aufzählungen

In C ++ 0x können Sie endlich typsichere Aufzählungswerte haben ....

enum class AnimalFlags {
    CanFly = 2,
    HasClaws = 4
};

if(CanFly == 2) { }//Compiling error
Brian R. Bondy
quelle
4
Die Aufzählungswerte sind keine ganzen Zahlen, aber sie können sehr leicht in ganze Zahlen umgewandelt werden. Der Typ von HasClaws | CanFlyist ein ganzzahliger Typ, aber der Typ von HasClawsist AnimalFlagskein ganzzahliger Typ.
Karu
Ah, aber was ist, wenn wir den korrekten Bereich der Aufzählung so definieren, dass er nicht nur die einzelnen Flag-Werte, sondern auch ihre bitweisen Kombinationen sind? Dann ist die Antwort von eidolon richtig und behauptet, dass nur Kombinationen der richtigen Flaggenaufzählung als dieser Typ übergeben werden können.
Scott
3
@Scott: Es ist erwähnenswert, dass der C ++ - Standard den gültigen Wertebereich einer Enum-Instanz auf diese Weise definiert. "Für eine Aufzählung, bei der emin der kleinste Aufzähler und emax der größte ist, sind die Werte der Aufzählung die Werte im Bereich von bmin bis bmax, die wie folgt definiert sind: Sei K 1 für eine Zweierkomplementdarstellung und 0 für eine Einse." Komplement- oder Vorzeichengrößendarstellung. bmax ist der kleinste Wert größer oder gleich max(|emin| − K, |emax|)und gleich (1u<<M) - 1, wobei Mes sich um eine nicht negative ganze Zahl handelt. "
Ben Voigt
Für diejenigen, die (wie ich) nur etwas Praktisches wollen, das es ermöglicht, Aufzählungswerte bitweise zu manipulieren und mit Vorlagen und Typumwandlung nicht zu hässlich aussieht, ist dies eine gute Lösung. Definieren Sie einfach die zu typisierenden Variablen int.
Eric Sokolowsky
Beachten Sie, dass in C ++, regelmäßig enumtechnisch nicht standardmäßig auf intals die ihr zugrunde liegenden Typ (entweder Pre-C ++ 11 (IIRC) oder Post-C ++ 11 , wenn keine zugrunde liegende Typ angegeben ist), obwohl enum class tut . Stattdessen verwendet der zugrunde liegende Typ standardmäßig etwas, das groß genug ist, um alle Enumeratoren darzustellen, mit der einzigen wirklich harten Regel, dass er nur größer ist, als intwenn es explizit sein muss . Grundsätzlich wird der zugrunde liegende Typ als (umschrieben) "was auch immer funktioniert, aber es ist wahrscheinlich, es int sei denn, die Enumeratoren sind zu groß für int" angegeben.
Justin Time - Wiedereinsetzung Monica
26

Ich finde die derzeit akzeptierte Antwort von Eidolon zu gefährlich. Das Optimierungsprogramm des Compilers kann Annahmen über mögliche Werte in der Aufzählung treffen und Sie erhalten möglicherweise Müll mit ungültigen Werten zurück. Und normalerweise möchte niemand alle möglichen Permutationen in Flags-Enums definieren.

Wie Brian R. Bondy weiter unten ausführt, können Sie dies jetzt einfacher tun, wenn Sie C ++ 11 verwenden (was jeder sollte, es ist so gut) enum class:

enum class ObjectType : uint32_t
{
    ANIMAL = (1 << 0),
    VEGETABLE = (1 << 1),
    MINERAL = (1 << 2)
};


constexpr enum ObjectType operator |( const enum ObjectType selfValue, const enum ObjectType inValue )
{
    return (enum ObjectType)(uint32_t(selfValue) | uint32_t(inValue));
}

// ... add more operators here. 

Dies stellt einen stabilen Größen- und Wertebereich sicher, indem ein Typ für die Aufzählung angegeben wird, verhindert das automatische Downcasting von Aufzählungen auf Ints usw. durch Verwendung enum classund stellt constexprsicher, dass der Code für die Operatoren inline und damit genauso schnell wie reguläre Zahlen wird.

Für Leute, die mit C ++ - Dialekten vor 11 Jahren zu tun haben

Wenn ich mit einem Compiler feststecken würde, der C ++ 11 nicht unterstützt, würde ich einen int-Typ in eine Klasse einschließen, die dann nur die Verwendung bitweiser Operatoren und der Typen aus dieser Aufzählung erlaubt, um ihre Werte festzulegen:

template<class ENUM,class UNDERLYING=typename std::underlying_type<ENUM>::type>
class SafeEnum
{
public:
    SafeEnum() : mFlags(0) {}
    SafeEnum( ENUM singleFlag ) : mFlags(singleFlag) {}
    SafeEnum( const SafeEnum& original ) : mFlags(original.mFlags) {}

    SafeEnum&   operator |=( ENUM addValue )    { mFlags |= addValue; return *this; }
    SafeEnum    operator |( ENUM addValue )     { SafeEnum  result(*this); result |= addValue; return result; }
    SafeEnum&   operator &=( ENUM maskValue )   { mFlags &= maskValue; return *this; }
    SafeEnum    operator &( ENUM maskValue )    { SafeEnum  result(*this); result &= maskValue; return result; }
    SafeEnum    operator ~()    { SafeEnum  result(*this); result.mFlags = ~result.mFlags; return result; }
    explicit operator bool()                    { return mFlags != 0; }

protected:
    UNDERLYING  mFlags;
};

Sie können dies so ziemlich wie eine reguläre Aufzählung + typedef definieren:

enum TFlags_
{
    EFlagsNone  = 0,
    EFlagOne    = (1 << 0),
    EFlagTwo    = (1 << 1),
    EFlagThree  = (1 << 2),
    EFlagFour   = (1 << 3)
};

typedef SafeEnum<enum TFlags_>  TFlags;

Und die Verwendung ist ähnlich:

TFlags      myFlags;

myFlags |= EFlagTwo;
myFlags |= EFlagThree;

if( myFlags & EFlagTwo )
    std::cout << "flag 2 is set" << std::endl;
if( (myFlags & EFlagFour) == EFlagsNone )
    std::cout << "flag 4 is not set" << std::endl;

Sie können den zugrunde liegenden Typ auch für binär stabile Aufzählungen (wie C ++ 11 enum foo : type) mithilfe des zweiten Vorlagenparameters überschreiben , d typedef SafeEnum<enum TFlags_,uint8_t> TFlags;. H.

Ich habe die operator boolÜberschreibung mit dem explicitSchlüsselwort C ++ 11 markiert , um zu verhindern, dass sie zu int-Konvertierungen führt, da diese dazu führen können, dass Flagsets beim Ausschreiben auf 0 oder 1 reduziert werden. Wenn Sie C ++ 11 nicht verwenden können, lassen Sie diese Überladung weg und schreiben Sie die erste Bedingung in der Beispielverwendung als neu (myFlags & EFlagTwo) == EFlagTwo.

uliwitness
quelle
Als Hinweis würde ich empfehlen, dass der zu Beginn definierte Beispieloperator std::underlying_typeeinen bestimmten Typ verwendet, anstatt ihn fest zu codieren, oder dass der zugrunde liegende Typ bereitgestellt und als Typalias anstelle von direkt verwendet wird. Auf diese Weise werden Änderungen am zugrunde liegenden Typ automatisch weitergegeben, anstatt manuell vorgenommen werden zu müssen.
Justin Time - Stellen Sie Monica
17

Der einfachste Weg , dies zu tun , wie gezeigt hier , die Standard - Bibliothek - Klasse bitset .

Um die C # -Funktion typsicher zu emulieren, müssen Sie einen Vorlagen-Wrapper um das Bitset schreiben und die int-Argumente durch eine Aufzählung ersetzen, die der Vorlage als Typparameter zugewiesen wird. Etwas wie:

    template <class T, int N>
class FlagSet
{

    bitset<N> bits;

    FlagSet(T enumVal)
    {
        bits.set(enumVal);
    }

    // etc.
};

enum MyFlags
{
    FLAG_ONE,
    FLAG_TWO
};

FlagSet<MyFlags, 2> myFlag;
soru
quelle
4
Schauen Sie sich diese für vollständigeren Code: codereview.stackexchange.com/questions/96146/...
Shital Shah
11

Meiner Meinung nach ist keine der bisherigen Antworten ideal. Um ideal zu sein, würde ich die Lösung erwarten:

  1. Unterstützen Sie die ==, !=, =, &, &=, |, |=und~ Operatoren im herkömmlichen Sinne (dh a & b)
  2. Seien Sie typsicher, dh lassen Sie nicht zu, dass nicht aufgezählte Werte wie Literale oder Ganzzahltypen zugewiesen werden (mit Ausnahme von bitweisen Kombinationen von Aufzählungswerten) oder dass eine Aufzählungsvariable einem Ganzzahltyp zugewiesen werden kann
  3. Erlaube Ausdrücke wie if (a & b)...
  4. Benötigen Sie keine bösen Makros, implementierungsspezifischen Funktionen oder andere Hacks

Die meisten der bisherigen Lösungen fallen auf Punkt 2 oder 3 um. WebDancer ist meiner Meinung nach der Abschluss, schlägt jedoch bei Punkt 3 fehl und muss für jede Aufzählung wiederholt werden.

Meine vorgeschlagene Lösung ist eine verallgemeinerte Version von WebDancer, die auch Punkt 3 behandelt:

#include <cstdint>
#include <type_traits>

template<typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
class auto_bool
{
    T val_;
public:
    constexpr auto_bool(T val) : val_(val) {}
    constexpr operator T() const { return val_; }
    constexpr explicit operator bool() const
    {
        return static_cast<std::underlying_type_t<T>>(val_) != 0;
    }
};

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr auto_bool<T> operator&(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) &
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr T operator|(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) |
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

enum class AnimalFlags : uint8_t 
{
    HasClaws = 1,
    CanFly = 2,
    EatsFish = 4,
    Endangered = 8
};

enum class PlantFlags : uint8_t
{
    HasLeaves = 1,
    HasFlowers = 2,
    HasFruit = 4,
    HasThorns = 8
};

int main()
{
    AnimalFlags seahawk = AnimalFlags::CanFly;        // Compiles, as expected
    AnimalFlags lion = AnimalFlags::HasClaws;         // Compiles, as expected
    PlantFlags rose = PlantFlags::HasFlowers;         // Compiles, as expected
//  rose = 1;                                         // Won't compile, as expected
    if (seahawk != lion) {}                           // Compiles, as expected
//  if (seahawk == rose) {}                           // Won't compile, as expected
//  seahawk = PlantFlags::HasThorns;                  // Won't compile, as expected
    seahawk = seahawk | AnimalFlags::EatsFish;        // Compiles, as expected
    lion = AnimalFlags::HasClaws |                    // Compiles, as expected
           AnimalFlags::Endangered;
//  int eagle = AnimalFlags::CanFly |                 // Won't compile, as expected
//              AnimalFlags::HasClaws;
//  int has_claws = seahawk & AnimalFlags::CanFly;    // Won't compile, as expected
    if (seahawk & AnimalFlags::CanFly) {}             // Compiles, as expected
    seahawk = seahawk & AnimalFlags::CanFly;          // Compiles, as expected

    return 0;
}

Dies führt zu Überladungen der erforderlichen Operatoren, verwendet jedoch SFINAE, um sie auf aufgezählte Typen zu beschränken. Beachten Sie, dass ich der Kürze halber nicht alle Operatoren definiert habe, aber der einzige, der anders ist, ist der &. Die Operatoren sind derzeit global (dh gelten für alle aufgezählten Typen). Dies kann jedoch entweder durch Platzieren der Überladungen in einem Namespace (wie ich es tue) oder durch Hinzufügen zusätzlicher SFINAE-Bedingungen (möglicherweise unter Verwendung bestimmter zugrunde liegender Typen oder speziell erstellter Typaliasnamen) verringert werden ). Das underlying_type_tist eine C ++ 14-Funktion, aber es scheint gut unterstützt zu werden und ist für C ++ 11 mit einem einfachen einfach zu emulierentemplate<typename T> using underlying_type_t = underlying_type<T>::type;

Trevor
quelle
Ihre vorgeschlagene Lösung funktioniert zwar hervorragend, führt dieses Muster jedoch auch für Aufzählungen ein, die nicht als Flags behandelt werden sollen. Dies ist wahrscheinlich der Grund für die Verwendung von (bösen) Makros wie DEFINE_ENUM_FLAG_OPERATORS von Microsoft.
WebDancer
@WebDancer, du bist natürlich richtig, aber das habe ich dann schon in meiner Antwort gesagt. Ich habe auch zwei Möglichkeiten vorgeschlagen, um das Problem zu beheben - das Einfügen in einen Namespace oder die Verwendung einer restriktiveren SFINAE-Bedingung.
Trevor
Mein Punkt ist, wenn Sie keinen wirklich engen Namespace erstellen (z. B. den Namespace AllMyFlagEnums) oder eine SFINAE-Bedingung haben, die auf irgendeine Weise nur einige exakte Aufzählungen auswählt, ist der Code in meinem Kopf fehlerhaft. Anstatt dies zu riskieren, kopiere ich eine "Textvorlage" und füge sie ein, wobei ich nur den Namen der Aufzählung und manchmal die "bösen" Makros ersetze. Ich wünschte, es gäbe einen besseren Weg.
WebDancer
Erstens verursacht es nur dann ein Problem, wenn Sie an einer anderen Stelle in Ihrem Code eines der Dinge tun müssen, die gestoppt werden sollen, z. B. ein Literal, eine Ganzzahl oder ein Element aus einer anderen Aufzählung zuweisen. Andernfalls verhält sich die modifizierte Aufzählung wie eine reguläre Aufzählung, z. B. müssen die Elemente nicht unbedingt Zweierpotenzen sein, und Zuweisungs-, Vergleichs- und bitweise Operationen funktionieren wie gewohnt. Wenn Sie wirklich Literale zuweisen oder Enums mischen müssen, können Sie diese dennoch explizit umwandeln, mit dem zusätzlichen Vorteil, dass Ihre Absicht klarer wird. Es besteht also die Möglichkeit, dass der Umfang nicht reduziert werden muss.
Trevor
Zweitens muss der Namespace möglicherweise nicht eng sein, auch wenn Sie den Umfang reduzieren müssen - obwohl dies davon abhängt, was Sie tun. Wenn Sie an einer Bibliothek arbeiten, haben Sie möglicherweise bereits Ihren Code, der von den Aufzählungen in einem Namespace abhängt. Der Aufzählungscode befindet sich dann nur im selben Namespace. Wenn Sie das Aufzählungsverhalten für eine Klasse benötigen (möglicherweise möchten Sie die Aufzählungen als Methodenargumente oder Elementvariablen der Klasse verwenden), fügen Sie den Aufzählungscode für denselben Effekt in die Klasse ein. Unterm Strich müssen Sie keinen Namespace nur um die Aufzählungen wickeln - obwohl Sie könnten.
Trevor
8

Der C ++ - Standard spricht ausdrücklich davon, siehe Abschnitt "17.5.2.1.3 Bitmaskentypen":

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf

Mit dieser "Vorlage" erhalten Sie:

enum AnimalFlags : unsigned int
{
    HasClaws = 1,
    CanFly = 2,
    EatsFish = 4,
    Endangered = 8
};

constexpr AnimalFlags operator|(AnimalFlags X, AnimalFlags Y) {
    return static_cast<AnimalFlags>(
        static_cast<unsigned int>(X) | static_cast<unsigned int>(Y));
}

AnimalFlags& operator|=(AnimalFlags& X, AnimalFlags Y) {
    X = X | Y; return X;
}

Und ähnlich für die anderen Betreiber. Beachten Sie auch den "constexpr", der benötigt wird, wenn der Compiler die Kompilierungszeit des Operators ausführen kann.

Wenn Sie C ++ / CLI verwenden und Enum-Mitgliedern von Ref-Klassen zuweisen möchten, müssen Sie stattdessen Tracking-Referenzen verwenden:

AnimalFlags% operator|=(AnimalFlags% X, AnimalFlags Y) {
    X = X | Y; return X;
}

HINWEIS: Dieses Beispiel ist nicht vollständig. Eine vollständige Reihe von Operatoren finden Sie in Abschnitt "17.5.2.1.3 Bitmaskentypen".

WebDancer
quelle
6

Ich stellte die gleiche Frage und fand eine generische C ++ 11-basierte Lösung, ähnlich der von soru:

template <typename TENUM>
class FlagSet {

private:
    using TUNDER = typename std::underlying_type<TENUM>::type;
    std::bitset<std::numeric_limits<TUNDER>::max()> m_flags;

public:
    FlagSet() = default;

    template <typename... ARGS>
    FlagSet(TENUM f, ARGS... args) : FlagSet(args...)
    {   
        set(f);
    }   
    FlagSet& set(TENUM f)
    {   
        m_flags.set(static_cast<TUNDER>(f));
        return *this;
    }   
    bool test(TENUM f)
    {   
        return m_flags.test(static_cast<TUNDER>(f));
    }   
    FlagSet& operator|=(TENUM f)
    {   
        return set(f);
    }   
};

Die Oberfläche kann nach Geschmack verbessert werden. Dann kann es so verwendet werden:

FlagSet<Flags> flags{Flags::FLAG_A, Flags::FLAG_C};
flags |= Flags::FLAG_D;
Omair
quelle
2
Sehen Sie sich diesen Code an, um einen besseren und vollständigeren Code zu erhalten: codereview.stackexchange.com/questions/96146/…
Shital Shah
5
Abgesehen von meiner Verwendung von numeric_limits ist der Code fast der gleiche. Ich denke, es ist ein üblicher Weg, eine typsichere Aufzählungsklasse zu haben. Ich würde argumentieren, dass die Verwendung von numeric_limits besser ist, als ein SENTINEL am Ende jeder Aufzählung zu setzen.
Omair
1
Das ist ein riesiger Bitset!
Leichtigkeitsrennen im Orbit
(möglicherweise ...)
Leichtigkeitsrennen im Orbit
5

Wenn Ihr Compiler noch keine stark typisierten Aufzählungen unterstützt, können Sie den folgenden Artikel aus der C ++ - Quelle lesen :

Aus der Zusammenfassung:

Dieser Artikel bietet eine Lösung für das Problem, Bitoperationen so einzuschränken,
dass nur sichere und legitime zulässig sind, und alle ungültigen Bitmanipulationen in Fehler zur Kompilierungszeit umzuwandeln. Das Beste ist, dass die Syntax von Bitoperationen unverändert bleibt und der mit Bits arbeitende Code nicht geändert werden muss, außer möglicherweise, um Fehler zu beheben, die bisher unentdeckt geblieben sind.

Francesco
quelle
5

Ich benutze das folgende Makro:

#define ENUM_FLAG_OPERATORS(T)                                                                                                                                            \
    inline T operator~ (T a) { return static_cast<T>( ~static_cast<std::underlying_type<T>::type>(a) ); }                                                                       \
    inline T operator| (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) | static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator& (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) & static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator^ (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) ^ static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T& operator|= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) |= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator&= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) &= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator^= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b) ); }

Es ähnelt den oben genannten, weist jedoch mehrere Verbesserungen auf:

  • Es ist typsicher (es wird nicht angenommen, dass der zugrunde liegende Typ ein ist int )
  • Der zugrunde liegende Typ muss nicht manuell angegeben werden (im Gegensatz zur Antwort von @LunarEclipse).

Es muss type_traits enthalten:

#include <type_traits>
oierlauzi
quelle
4

Ich möchte die Antwort von Uliwitness näher erläutern , seinen Code für C ++ 98 korrigieren und die Safe Bool-Sprache verwenden , da die std::underlying_type<>Vorlage und die fehlenexplicit Schlüsselwort in C ++ - Versionen unter C ++ 11 .

Ich habe es auch so modifiziert, dass die Enum-Werte ohne explizite Zuweisung sequentiell sein können

enum AnimalFlags_
{
    HasClaws,
    CanFly,
    EatsFish,
    Endangered
};
typedef FlagsEnum<AnimalFlags_> AnimalFlags;

seahawk.flags = AnimalFlags() | CanFly | EatsFish | Endangered;

Sie können dann den Raw-Flags-Wert mit abrufen

seahawk.flags.value();

Hier ist der Code.

template <typename EnumType, typename Underlying = int>
class FlagsEnum
{
    typedef Underlying FlagsEnum::* RestrictedBool;

public:
    FlagsEnum() : m_flags(Underlying()) {}

    FlagsEnum(EnumType singleFlag):
        m_flags(1 << singleFlag)
    {}

    FlagsEnum(const FlagsEnum& original):
        m_flags(original.m_flags)
    {}

    FlagsEnum& operator |=(const FlagsEnum& f) {
        m_flags |= f.m_flags;
        return *this;
    }

    FlagsEnum& operator &=(const FlagsEnum& f) {
        m_flags &= f.m_flags;
        return *this;
    }

    friend FlagsEnum operator |(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) |= f2;
    }

    friend FlagsEnum operator &(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) &= f2;
    }

    FlagsEnum operator ~() const {
        FlagsEnum result(*this);
        result.m_flags = ~result.m_flags;
        return result;
    }

    operator RestrictedBool() const {
        return m_flags ? &FlagsEnum::m_flags : 0;
    }

    Underlying value() const {
        return m_flags;
    }

protected:
    Underlying  m_flags;
};
Fabio A.
quelle
3

Hier ist eine Option für Bitmasken, wenn Sie die einzelnen Enum-Werte nicht verwenden können (z. B. müssen Sie sie nicht ausschalten) ... und wenn Sie sich keine Sorgen um die Aufrechterhaltung der Binärkompatibilität machen, dh: Sie Es ist mir egal, wo deine Teile leben ... was du wahrscheinlich bist. Außerdem sollten Sie sich nicht zu sehr mit dem Umfang und der Zugriffskontrolle befassen. Hmmm, Enums haben einige nette Eigenschaften für Bitfelder ... frage mich, ob das jemals jemand versucht hat :)

struct AnimalProperties
{
    bool HasClaws : 1;
    bool CanFly : 1;
    bool EatsFish : 1;
    bool Endangered : 1;
};

union AnimalDescription
{
    AnimalProperties Properties;
    int Flags;
};

void TestUnionFlags()
{
    AnimalDescription propertiesA;
    propertiesA.Properties.CanFly = true;

    AnimalDescription propertiesB = propertiesA;
    propertiesB.Properties.EatsFish = true;

    if( propertiesA.Flags == propertiesB.Flags )
    {
        cout << "Life is terrible :(";
    }
    else
    {
        cout << "Life is great!";
    }

    AnimalDescription propertiesC = propertiesA;
    if( propertiesA.Flags == propertiesC.Flags )
    {
        cout << "Life is great!";
    }
    else
    {
        cout << "Life is terrible :(";
    }
}

Wir können sehen, dass das Leben großartig ist, wir haben unsere diskreten Werte und wir haben eine nette Beziehung zu & und | nach Herzenslust, der immer noch den Kontext dessen hat, was seine Teile bedeuten. Alles ist konsistent und vorhersehbar ... für mich ... solange ich weiterhin den VC ++ - Compiler von Microsoft mit Update 3 unter Win10 x64 verwende und meine Compiler-Flags nicht berühre :)

Obwohl alles großartig ist ... haben wir jetzt einen gewissen Kontext bezüglich der Bedeutung von Flags, da es sich um eine Vereinigung mit dem Bitfeld in der schrecklichen realen Welt handelt, in der Ihr Programm möglicherweise für mehr als eine einzelne diskrete Aufgabe verantwortlich ist, die Sie könnten immer noch versehentlich (ziemlich leicht) zwei Flaggenfelder verschiedener Gewerkschaften zusammenschlagen (z. B. AnimalProperties und ObjectProperties, da sie beide Ints sind) und all deine Teile durcheinander bringen, was ein schrecklicher Fehler ist, den man aufspüren kann ... und woher ich weiß Viele Leute in diesem Beitrag arbeiten nicht sehr oft mit Bitmasken, da das Erstellen einfach und die Wartung schwierig ist.

class AnimalDefinition {
public:
    static AnimalDefinition *GetAnimalDefinition( AnimalFlags flags );   //A little too obvious for my taste... NEXT!
    static AnimalDefinition *GetAnimalDefinition( AnimalProperties properties );   //Oh I see how to use this! BORING, NEXT!
    static AnimalDefinition *GetAnimalDefinition( int flags ); //hmm, wish I could see how to construct a valid "flags" int without CrossFingers+Ctrl+Shift+F("Animal*"). Maybe just hard-code 16 or something?

    AnimalFlags animalFlags;  //Well this is *way* too hard to break unintentionally, screw this!
    int flags; //PERFECT! Nothing will ever go wrong here... 
    //wait, what values are used for this particular flags field? Is this AnimalFlags or ObjectFlags? Or is it RuntimePlatformFlags? Does it matter? Where's the documentation? 
    //Well luckily anyone in the code base and get confused and destroy the whole program! At least I don't need to static_cast anymore, phew!

    private:
    AnimalDescription m_description; //Oh I know what this is. All of the mystery and excitement of life has been stolen away :(
}

Dann machen Sie Ihre Gewerkschaftserklärung privat, um den direkten Zugriff auf "Flags" zu verhindern, und müssen Getter / Setter und Operatorüberladungen hinzufügen, dann ein Makro für all das erstellen, und Sie sind im Grunde wieder da, wo Sie angefangen haben, als Sie es versucht haben Mach das mit einer Aufzählung.

Wenn Sie möchten, dass Ihr Code portabel ist, gibt es leider keine Möglichkeit, entweder A) das Bitlayout zu garantieren oder B) das Bitlayout zur Kompilierungszeit zu bestimmen (damit Sie es verfolgen und zumindest Änderungen korrigieren können) Versionen / Plattformen etc) Offset in einer Struktur mit Bitfeldern

Zur Laufzeit können Sie Streiche spielen, indem Sie die Felder setzen und die Flags XOR-verknüpfen, um zu sehen, welche Bits sich geändert haben. Das klingt für mich ziemlich beschissen, obwohl Verse eine 100% konsistente, plattformunabhängige und vollständig deterministische Lösung haben, dh: eine ENUM.

TL; DR: Hör nicht auf die Hasser. C ++ ist kein Englisch. Nur weil die wörtliche Definition eines von C geerbten abgekürzten Schlüsselworts möglicherweise nicht zu Ihrer Verwendung passt, heißt das nicht, dass Sie es nicht verwenden sollten, wenn C und C ++ - Definition des Schlüsselworts Ihren Anwendungsfall absolut einschließt. Sie können auch Strukturen verwenden, um andere Dinge als Strukturen zu modellieren, und Klassen für andere Dinge als Schule und soziale Kaste. Sie können float für Werte verwenden, die geerdet sind. Sie können char für Variablen verwenden, die weder unverbrannt noch eine Person in einem Roman, Theaterstück oder Film sind. Jeder Programmierer, der zum Wörterbuch geht, um die Bedeutung eines Schlüsselworts vor der Sprachspezifikation zu bestimmen, ist ein ... Nun, ich werde meine Zunge dort halten.

Wenn Sie möchten, dass Ihr Code der gesprochenen Sprache nachempfunden ist, schreiben Sie am besten in Objective-C, das übrigens auch häufig Enums für Bitfelder verwendet.

Dreckig
quelle
3

Nur syntaktischer Zucker. Keine zusätzlichen Metadaten.

namespace UserRole // grupy
{ 
    constexpr uint8_t dea = 1;
    constexpr uint8_t red = 2;
    constexpr uint8_t stu = 4;
    constexpr uint8_t kie = 8;
    constexpr uint8_t adm = 16;
    constexpr uint8_t mas = 32;
}

Flag-Operatoren für integrale Typen funktionieren einfach.

vSzemkel
quelle
IMHO ist dies die beste Antwort. Saubere und einfache Client-Syntax. Ich würde nur "const int" anstelle von "constexpr uint8_t" verwenden, aber das Konzept ist das gleiche.
Yooy
(Entschuldigung, "constexpr int")
yoyo
3

Derzeit gibt es keine Sprachunterstützung für Enum-Flags. Meta-Klassen könnten diese Funktion von Natur aus hinzufügen, falls sie jemals Teil des C ++ - Standards sein sollte.

Meine Lösung wäre, nur enum-instanziierte Vorlagenfunktionen zu erstellen, die typsichere bitweise Operationen für die enum-Klasse unter Verwendung des zugrunde liegenden Typs unterstützen:

Datei: EnumClassBitwise.h

#pragma once
#ifndef _ENUM_CLASS_BITWISE_H_
#define _ENUM_CLASS_BITWISE_H_

#include <type_traits>

//unary ~operator    
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator~ (Enum& val)
{
    val = static_cast<Enum>(~static_cast<std::underlying_type_t<Enum>>(val));
    return val;
}

// & operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator& (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
}

// &= operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator&= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

//| operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator| (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
}
//|= operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator|= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

#endif // _ENUM_CLASS_BITWISE_H_

Zur Vereinfachung und zur Reduzierung von Fehlern möchten Sie Ihre Bit-Flags-Operationen möglicherweise auch für Aufzählungen und Ganzzahlen umschließen:

Datei: BitFlags.h

#pragma once
#ifndef _BIT_FLAGS_H_
#define _BIT_FLAGS_H_

#include "EnumClassBitwise.h"

 template<typename T>
 class BitFlags
 {
 public:

     constexpr inline BitFlags() = default;
     constexpr inline BitFlags(T value) { mValue = value; }
     constexpr inline BitFlags operator| (T rhs) const { return mValue | rhs; }
     constexpr inline BitFlags operator& (T rhs) const { return mValue & rhs; }
     constexpr inline BitFlags operator~ () const { return ~mValue; }
     constexpr inline operator T() const { return mValue; }
     constexpr inline BitFlags& operator|=(T rhs) { mValue |= rhs; return *this; }
     constexpr inline BitFlags& operator&=(T rhs) { mValue &= rhs; return *this; }
     constexpr inline bool test(T rhs) const { return (mValue & rhs) == rhs; }
     constexpr inline void set(T rhs) { mValue |= rhs; }
     constexpr inline void clear(T rhs) { mValue &= ~rhs; }

 private:
     T mValue;
 };
#endif //#define _BIT_FLAGS_H_

Mögliche Verwendung:

#include <cstdint>
#include <BitFlags.h>
void main()
{
    enum class Options : uint32_t
    { 
          NoOption = 0 << 0
        , Option1  = 1 << 0
        , Option2  = 1 << 1
        , Option3  = 1 << 2
        , Option4  = 1 << 3
    };

    const uint32_t Option1 = 1 << 0;
    const uint32_t Option2 = 1 << 1;
    const uint32_t Option3 = 1 << 2;
    const uint32_t Option4 = 1 << 3;

   //Enum BitFlags
    BitFlags<Options> optionsEnum(Options::NoOption);
    optionsEnum.set(Options::Option1 | Options::Option3);

   //Standard integer BitFlags
    BitFlags<uint32_t> optionsUint32(0);
    optionsUint32.set(Option1 | Option3); 

    return 0;
}
Nicker
quelle
3

@Xaqq bietet eine wirklich schöne typsichere Möglichkeit, Enum-Flags hier von a zu verwendenflag_set Klasse zu verwenden.

Ich habe den Code in GitHub veröffentlicht. Die Verwendung ist wie folgt:

#include "flag_set.hpp"

enum class AnimalFlags : uint8_t {
    HAS_CLAWS,
    CAN_FLY,
    EATS_FISH,
    ENDANGERED,
    _
};

int main()
{
    flag_set<AnimalFlags> seahawkFlags(AnimalFlags::HAS_CLAWS
                                       | AnimalFlags::EATS_FISH
                                       | AnimalFlags::ENDANGERED);

    if (seahawkFlags & AnimalFlags::ENDANGERED)
        cout << "Seahawk is endangered";
}
mrts
quelle
2

Sie verwirren Objekte und Sammlungen von Objekten. Insbesondere verwechseln Sie Binärflags mit Sätzen von Binärflags. Eine richtige Lösung würde so aussehen:

// These are individual flags
enum AnimalFlag // Flag, not Flags
{
    HasClaws = 0,
    CanFly,
    EatsFish,
    Endangered
};

class AnimalFlagSet
{
    int m_Flags;

  public:

    AnimalFlagSet() : m_Flags(0) { }

    void Set( AnimalFlag flag ) { m_Flags |= (1 << flag); }

    void Clear( AnimalFlag flag ) { m_Flags &= ~ (1 << flag); }

    bool Get( AnimalFlag flag ) const { return (m_Flags >> flag) & 1; }

};
Michael
quelle
2

Hier ist meine Lösung, ohne dass eine Menge Überladung oder Casting erforderlich ist:

namespace EFoobar
{
    enum
    {
        FB_A    = 0x1,
        FB_B    = 0x2,
        FB_C    = 0x4,
    };
    typedef long Flags;
}

void Foobar(EFoobar::Flags flags)
{
    if (flags & EFoobar::FB_A)
        // do sth
        ;
    if (flags & EFoobar::FB_B)
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar(EFoobar::FB_A | EFoobar::FB_B);
    EFoobar::Flags otherflags = 0;
    otherflags|= EFoobar::FB_B;
    otherflags&= ~EFoobar::FB_B;
    Foobar(otherflags);
}

Ich denke, es ist in Ordnung, weil wir sowieso (nicht stark typisierte) Aufzählungen und Ints identifizieren.

Nur als (längere) Randnotiz, wenn Sie

  • möchte stark typisierte Aufzählungen verwenden und
  • Sie müssen nicht viel mit Ihren Flaggen herumspielen
  • Leistung ist kein Problem

Ich würde mir das einfallen lassen:

#include <set>

enum class EFoobarFlags
{
    FB_A = 1,
    FB_B,
    FB_C,
};

void Foobar(const std::set<EFoobarFlags>& flags)
{
    if (flags.find(EFoobarFlags::FB_A) != flags.end())
        // do sth
        ;
    if (flags.find(EFoobarFlags::FB_B) != flags.end())
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar({EFoobarFlags::FB_A, EFoobarFlags::FB_B});
    std::set<EFoobarFlags> otherflags{};
    otherflags.insert(EFoobarFlags::FB_B);
    otherflags.erase(EFoobarFlags::FB_B);
    Foobar(otherflags);
}

Verwenden von C ++ 11-Initialisiererlisten und enum class.

yau
quelle
Übrigens würde ich Enums für Flags lieber gar nicht empfehlen. Einfacher Grund: Kombinationen von Flags sind wieder keine Elemente der Aufzählung. Das scheint also ziemlich ungeeignet zu sein. Alternativ würde ich ein using Flags = unsigned longinnerhalb eines Namespace oder einer Struktur verwenden, das die Flag-Werte selbst als /*static*/ const Flags XY = 0x01usw. enthält.
Yau
1

Wie oben (Kai) oder wie folgt. Wirklich Aufzählungen sind "Aufzählungen". Was Sie tun möchten, ist eine Menge zu haben, daher sollten Sie wirklich stl :: set verwenden

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

int main(void)
{
    AnimalFlags seahawk;
    //seahawk= CanFly | EatsFish | Endangered;
    seahawk= static_cast<AnimalFlags>(CanFly | EatsFish | Endangered);
}
Spacen Jasset
quelle
1

Vielleicht wie NS_OPTIONS von Objective-C.

#define ENUM(T1, T2) \
enum class T1 : T2; \
inline T1 operator~ (T1 a) { return (T1)~(int)a; } \
inline T1 operator| (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) | static_cast<T2>(b))); } \
inline T1 operator& (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) & static_cast<T2>(b))); } \
inline T1 operator^ (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) ^ static_cast<T2>(b))); } \
inline T1& operator|= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) |= static_cast<T2>(b))); } \
inline T1& operator&= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) &= static_cast<T2>(b))); } \
inline T1& operator^= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) ^= static_cast<T2>(b))); } \
enum class T1 : T2

ENUM(Options, short) {
    FIRST  = 1 << 0,
    SECOND = 1 << 1,
    THIRD  = 1 << 2,
    FOURTH = 1 << 3
};

auto options = Options::FIRST | Options::SECOND;
options |= Options::THIRD;
if ((options & Options::SECOND) == Options::SECOND)
    cout << "Contains second option." << endl;
if ((options & Options::THIRD) == Options::THIRD)
    cout << "Contains third option." << endl;
return 0;

// Output:
// Contains second option. 
// Contains third option.
Mondfinsternis
quelle
Können Sie erklären, warum Ihre Antwort am besten passt? Es gibt mehrere andere Antworten, die diese Frage beantwortet haben. Bitte geben Sie einige Informationen an, um Ihre zu unterscheiden.
Trevorp