C ++でフラグとして列挙型を使用する方法?


187

enumsをフラグとして扱うことは、[Flags]属性を介してC#でうまく機能しますが、C ++でこれを行うための最良の方法は何ですか?

たとえば、次のように記述します。

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

seahawk.flags = CanFly | EatsFish | Endangered;

ただし、int/ enum変換に関するコンパイラエラーが発生します。単なる鈍いキャストよりもこれを表現する良い方法はありますか?できれば、boostやQtなどのサードパーティライブラリの構成に依存したくないのですが。

編集:回答に示されているように、seahawk.flagsとして宣言することで、コンパイラエラーを回避できますint。ただし、タイプセーフを適用するためのメカニズムが必要なので、誰かが書けませんseahawk.flags = HasMaximizeButton


私がVisual C ++ 2013で知っている限り、この[Flags]属性は問題なく機能します。つまり、[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
rivanov

@rivanov、いいえ、それはC ++では機能しません(2015も)。もしかしてC#ですか?
Ajay

5
@rivanov、[Flags]属性はC ++ CLIの.Net Frameworkでのみ機能し、ネイティブC ++はそのような属性をサポートしません。
Zoltan Tirinda 2016

回答:


250

「正しい」方法は、列挙型のビット演算子を次のように定義することです。

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

残りのビット演算子など。enumの範囲がintの範囲を超える場合は、必要に応じて変更します。


42
^これ。唯一の問題は、オペレーター定義を自動化/テンプレート化する方法です。これにより、新しい列挙型を追加するたびにオペレーターを定義し続ける必要がなくなります。
eodabash 2011

10
また、int値がenumの識別子のいずれにも対応していなくても、任意のintからenum型へのキャストは有効ですか?
Ingo Schalk-Schupp

8
これはまったくナンセンスです。のどのメンバーがAnimalFlags式で表されHasClaws | CanFlyますか?これはないenumsがためのものです。整数と定数を使用します。
オービットのライトネスレース

26
@LightnessRacesinOrbit:不正解です。列挙型のドメインは、その基礎となる型のドメインです。特定のドメインに名前が付けられているだけです。そして、あなたの質問に答えるために:メンバー " (HasClaws | CanFly)"。
Xeo

5
@MarcusJ:値を2の累乗に制限すると、列挙型をビットフラグとして使用できます。したがって、3を取得すると、HasClaws(= 1)とCanFly(= 2)の両方がわかります。代わりに、1から4までの値をそのまま割り当てて3を取得した場合、それは単一のEatsFish、またはとの組み合わせにHasClawsなりCanFlyます。列挙型が排他的な状態のみを示している場合、連続する値は問題ありませんが、フラグの組み合わせでは、値をビット排他にする必要があります。
クリスチャンセヴェリン

122

注意(トピックから少し外れています):一意のフラグを作成する別の方法は、ビットシフトを使用して行うことができます。私自身、これは読みやすくなっています。

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

これは、intまでの値を保持できるため、ほとんどの場合、32フラグであり、シフト量に明確に反映されます。


2
コードを簡単にコピーして貼り付けることができるように、最後のコンマ(3、)を削除し、}の後にコロンを追加してください。ありがとう
カトゥ

4
16進数の記述はありませんか?冒涜!
Pharap 2014

1
@ジェイミー、枢機卿は常に1で始まります。話している相手に応じて、序数のみが0または1で始まる場合があります。
マイケル

2
@マイケル、それは本当です!列挙型では、通常BLAH_NONEに0を予約します。:-)その記憶をいらいらしてくれてありがとう!
ジェイミー

1
@Katu•最終列挙での余分なコンマは標準で許可されています。私はそれが好きではありませんが、Stroustrupが私に言うことはすでに知っています...「あなたはそれが好きではありませんか?自分の言語を自由に作成してください。私が作成しました。」
エルジェイ

55

私のような怠惰な人々のために、ここにコピー&ペーストするためのテンプレート化されたソリューションがあります:

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

23
:+1怠惰は、プログラマの三の大美徳の一つであるthreevirtues.com
Pharap

10
これは非常に優れたソリューションです。どんな型に対してもビット単位の操作を楽に提供するように注意してください。私は似たようなものを使用していますが、適用するタイプを識別する特性を追加し、小さなenable_ifマジックを組み合わせています。
Rai

@Rai:のように、名前空間とusing適切な場所にいつでも配置できますrel_ops
Yakov Galka 2016

1
@ybungalobill、ただし、おそらく列挙型に一致する、usingのスコープ内の任意のタイプに適用される操作で同じ問題が発生しますか?おそらく特性が必要だと思います。
Rai

19
このコードは使用しないでください。これは、どのクラスも誤って操作する可能性を広げます。また、コードはGCCの厳密なコンパイルshitalshah.com/p/…を通過しない古いスタイルのキャストを使用しています。
Shital Shah 2016

44

Windows環境で作業している場合は、DEFINE_ENUM_FLAG_OPERATORSwinnt.hで定義されたマクロがその作業を行います。したがって、この場合、これを行うことができます:

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

seahawk.flags = CanFly | EatsFish | Endangered;

44

seahawk.flags変数はどのタイプですか?

標準C ++では、列挙型はタイプセーフではありません。それらは事実上整数です。

AnimalFlagsは変数の型であってはなりません。変数はintでなければならず、エラーはなくなります。

他の一部の人が提案したように16進値を入力する必要はありません。違いはありません。

列挙値は、デフォルトでint型です。したがって、ビット単位でOR結合し、それらを組み合わせて、結果をintに格納できます。

enum型は、値が列挙値の1つであるintの制限されたサブセットです。したがって、その範囲外に新しい値を作成する場合、列挙型の変数にキャストしないと値を割り当てることができません。

必要に応じて列挙値の型を変更することもできますが、この質問には意味がありません。

編集:ポスターは、型安全性に関心があり、int型内に存在してはならない値を望んでいないと述べました。

ただし、AnimalFlagsの範囲外の値を、AnimalFlags型の変数内に配置するのは安全ではない型です。

int型の内部でも、範囲外の値をチェックする安全な方法があります...

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

上記は、値が1、2、4、または8である別の列挙型から無効なフラグを設定することを妨げるものではありません。

絶対的な型保証が必要な場合は、単にstd :: setを作成し、その中に各フラグを格納できます。スペース効率はよくありませんが、タイプセーフであり、ビットフラグintと同じ機能を提供します。

C ++ 0x注:強く型付けされた列挙型

C ++ 0xでは、最終的にタイプセーフな列挙値を持つことができます。

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

if(CanFly == 2) { }//Compiling error

4
列挙値は整数ではありませんが、非常に簡単に整数に変換されます。の型HasClaws | CanFlyは整数型ですが、の型HasClawsAnimalFlagsであり、整数型ではありません。
Karu

1
ああ、でも、列挙型の正しい範囲を個々のフラグ値だけでなく、それらのビットごとの組み合わせになるように定義するとどうなるでしょうか。次に、幻影の答えは正しいです、そして、正しいフラグ列挙の組み合わせだけがそのタイプとして渡されることができることを維持します。
スコット

3
@スコット:C ++標準が列挙型インスタンスの値の有効範囲をそのように定義していることは注目に値します。「eminが最小の列挙子でemaxが最大の列挙の場合、列挙の値はbminからbmaxの範囲の値で、次のように定義されます。2の補数表現の場合はKを1、1の場合は0とします補体またはサインマグニチュード表現BMAXがより最小値の大きい又は等しいmax(|emin| − K, |emax|)と等しく(1u<<M) - 1、ここで、M負でない整数です。」
Ben Voigt

(私のような)列挙型の値をビット単位で操作できる実用的なものを必要とし、テンプレートや型キャストで見苦しくない人にとっては、これは良い解決策です。変数をtypeとして定義するだけintです。
Eric Sokolowsky、

また、C ++で、定期的なことに注意してくださいenum、技術的にデフォルト設定されていないintものの、その基礎となるタイプ(いずれか事前にC ++ 11(IIRC)、またはポストC ++ 11何の基になる型が指定されていない場合)としてenum class 行います。代わりに、基になる型は、すべての列挙子を表すのに十分な大きさにデフォルトで設定され、int明示的に必要な場合よりも大きいという唯一の実際のハードルールがあります。基本的に、基になる型は(言い換えれば)「何でも機能しますが、おそらく int列挙子が大きすぎない限り」と指定されintます。
ジャスティン時間-モニカを復活させる

26

エイドロンの現在受け入れられている答えは危険すぎると思います。コンパイラのオプティマイザは、列挙型の可能な値について仮定を行い、無効な値でガベージを返す可能性があります。そして、通常、フラグ列挙型で可能なすべての順列を定義する必要はありません。

Brian R. Bondyが以下に述べるように、C ++ 11を使用している場合(誰もがそうするべきですが、それは良いことです)、これを使ってこれをより簡単に実行できます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. 

これにより、列挙型のタイプを指定することで安定したサイズと値の範囲が確保されenum class、を使用constexprして列挙型がintなどに自動的にダウンキャストされなくなり、演算子のコードが確実にインライン化されるため、通常の数値と同じくらい高速になります。

11より前のC ++方言にこだわっている人向け

C ++ 11をサポートしていないコンパイラーに悩まされていた場合は、クラスにint型をラップして、ビットごとの演算子とその列挙型の使用のみを許可し、その値を設定します。

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

これは通常のenum + typedefとほぼ同じように定義できます。

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

typedef SafeEnum<enum TFlags_>  TFlags;

使い方も同様です:

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;

またenum foo : type、2番目のテンプレートパラメータ、つまりを使用して、バイナリで安定した列挙型(C ++ 11のような)の基礎となる型をオーバーライドすることもできますtypedef SafeEnum<enum TFlags_,uint8_t> TFlags;

operator boolオーバーライドをC ++ 11のexplicitキーワードでマークして、それがint変換を引き起こさないようにしました。これにより、フラグセットが書き出されたときに、フラグのセットが0または1に折りたたまれる可能性があります。C ++ 11を使用できない場合は、過負荷のままにして、使用例の最初の条件をに書き換え(myFlags & EFlagTwo) == EFlagTwoます。


注意として、最初に定義された演算子の例ではstd::underlying_type、特定の型をハードコーディングする代わりに使用するか、基になる型を提供し、直接ではなく型エイリアスとして使用することをお勧めします。このようにして、基になるタイプへの変更は、手動で行う必要がなく、自動的に伝播されます。
ジャスティン時間-モニカを復活させる

17

ここに示すように、標準ライブラリクラスのビットセットを使用してこれを行う最も簡単な方法。

タイプセーフな方法でC#機能をエミュレートするには、ビットセットの周りにテンプレートラッパーを記述し、int引数をテンプレートの型パラメーターとして指定された列挙型に置き換える必要があります。何かのようなもの:

    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;

4
:より完全なコードについてはこれを見て codereview.stackexchange.com/questions/96146/...
Shitalシャー

11

私の意見では、これまでのところ、どの答えも理想的ではありません。理想的になるために私は解決策を期待します:

  1. サポート==!==&&=||=および~(すなわち、従来の意味での演算子a & b
  2. タイプセーフであること、つまりリテラルや整数型などの非列挙値の割り当てを許可しない(列挙値のビットごとの組み合わせを除く)、または列挙型変数を整数型に割り当てることを許可する
  3. 次のような許可式 if (a & b)...
  4. 邪悪なマクロ、実装固有の機能、その他のハックを必要としない

これまでのソリューションのほとんどは、ポイント2または3に当てはまります。私の意見では、WebDancerは終了ですが、ポイント3で失敗し、列挙ごとに繰り返す必要があります。

私が提案する解決策は、ポイント3にも対応するWebDancerの一般化バージョンです。

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

これにより、必要な演算子のオーバーロードが作成されますが、SFINAEを使用してそれらを列挙型に制限します。簡潔にするために、すべての演算子を定義したわけではありませんが、異なるのはだけ&です。演算子は現在グローバルです(つまり、すべての列挙型に適用されます)が、名前空間にオーバーロードを配置するか(私が何をするか)、または追加のSFINAE条件を追加することで(おそらく、特定の基になる型を使用するか、特別に作成された型のエイリアス) )。これunderlying_type_tはC ++ 14の機能ですが、十分にサポートされているようで、C ++ 11を簡単にエミュレートできます。template<typename T> using underlying_type_t = underlying_type<T>::type;


提案されたソリューションはうまく機能しますが、フラグとして扱われることを意図していない列挙型にもこのパターンが導入されます。これが、MicrosoftのDEFINE_ENUM_FLAG_OPERATORSなどの(悪)マクロを使用する理由です。
WebDancer 2018年

@WebDancer、もちろんあなたは正しいですが、私はすでに私の答えでそれを述べました。また、この問題に対処する2つの方法を提案しました。名前空間に配置するか、より制限的なSFINAE条件を使用することです。
Trevor

私の要点は、本当に狭い名前空間(例:名前空間AllMyFlagEnums)を作成するか、何らかの方法でコードを正確に列挙する数個だけを選択するSFINAE条件がない限り、コードは私の頭の中で壊れています。これを危険にさらすのではなく、「テキストテンプレート」をコピーして貼り付けます。ここで、列挙名と、場合によっては「悪質な」マクロを置き換えます。もっと良い方法があればいいのに。
WebDancer 2018年

まず、コード内の別の場所で、リテラル、整数、または別の列挙型からの要素を割り当てるなど、停止しようとしていることの1つを実行する必要がある場合にのみ問題が発生します。それ以外の場合、変更された列挙型は通常の列挙型のように動作します。たとえば、要素は必ずしも2の累乗である必要はなく、割り当て、比較、およびビットごとの演算は通常どおり機能します。本当にリテラルを割り当てる必要がある場合や列挙型を混在させる必要がある場合でも、明示的にキャストすることができ、意図がより明確になるという利点もあります。したがって、範囲を縮小する必要がない可能性があります。
Trevor

次に、スコープを縮小する必要がある場合でも、名前空間を狭める必要はありません。ライブラリで作業している場合、おそらく名前空間の列挙型に依存するコードが既にある場合、列挙型コードは同じ名前空間に入るだけです。クラスの列挙型動作が必要な場合(おそらく、列挙型をクラスのメソッド引数またはメンバー変数として使用する場合)、同じ効果を得るために、列挙型コードをクラスに配置します。結論としては、列挙型だけを名前空間で囲む必要はありませんが、可能です。
Trevor

8

C ++標準はこれについて明示的に述べています。「17.5.2.1.3ビットマスク型」のセクションを参照してください。

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

この「テンプレート」を考えると、次のようになります。

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

他のオペレーターについても同様です。また、「constexpr」にも注意してください。コンパイラーがオペレーターのコンパイル時に実行できるようにする場合に必要です。

C ++ / CLIを使用していて、refクラスの列挙型メンバーに割り当てたい場合は、代わりにトラッキング参照を使用する必要があります。

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

注:このサンプルは完全ではありません。演算子の完全なセットについては、セクション「17.5.2.1.3ビットマスクタイプ」を参照してください。


6

私も同じ質問をしているのに気づき、solのような一般的なC ++ 11ベースのソリューションを思いつきました。

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

インターフェースは好みに合わせて改善できます。その後、次のように使用できます。

FlagSet<Flags> flags{Flags::FLAG_A, Flags::FLAG_C};
flags |= Flags::FLAG_D;

2
:より良く、より完全なコードのためにこれを見て codereview.stackexchange.com/questions/96146/...
Shitalシャー

5
私のnumeric_limitsの使用を除いて、コードはほとんど同じです。タイプセーフな列挙型クラスを作成するのは一般的な方法だと思います。すべての列挙型の最後にSENTINELを置くよりも、numeric_limitsを使用する方がよいと私は主張します。
Omair 2016年

1
それは巨大なビットセットです!
オービット


5

コンパイラが強く型付けされた列挙型をまだサポートしていない場合は、c ++ソースから次の記事を参照できます。

要約から:

この記事では、ビット操作を制限して
安全で正当なものだけを許可し、すべての無効なビット操作をコンパイル時エラーに変換するという問題の解決策を紹介します。何よりも、ビット操作の構文は変更されず、ビットを処理するコードを変更する必要はありません。ただし、まだ検出されないままのエラーを修正する場合を除きます。


5

次のマクロを使用します。

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

上記のものに似ていますが、いくつかの改善点があります。

  • タイプセーフです(基になるタイプがであるとは限りませんint
  • (@LunarEclipseの答えとは対照的に)基になるタイプを手動で指定する必要はありません。

type_traitsを含める必要があります。

#include <type_traits>

4

C ++ 11より前のC ++バージョンのテンプレートとキーワードがないため、Uliwitnessの回答について詳しく説明し、C ++ 98のコードを修正し、Safe Boolイディオムを使用します。std::underlying_type<>explicit

また、列挙値が明示的な割り当てなしで連続できるように変更したので、

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

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

次に、生のフラグ値を取得できます

seahawk.flags.value();

これがコードです。

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

3

個々の列挙値を実際に使用していない場合(たとえば、それらをオフにする必要がない場合)のビットマスクのオプションは次のとおりです...バイナリ互換性の維持について心配していない場合、つまりビットがどこにあるか気にしないでください... また、スコーピングやアクセス制御についてあまり気にしない方がいいでしょう。うーん、列挙型にはビットフィールドのためのいくつかの素晴らしいプロパティがあります...誰かがこれを試したことがありますか?

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 :(";
    }
}

私たちは人生が素晴らしいこと、私たちには離散的な価値観があること、そして&と| 私たちの心のコンテンツに、それはまだそのビットが何を意味するかのコンテキストがあります。すべてが一貫していて予測可能です...私にとっては... Win10 x64でUpdate 3のMicrosoft VC ++コンパイラーを使い続け、コンパイラーのフラグに触れない限り:)

すべてが素晴らしいのに... フラグの意味に関してはいくつかのコンテキストがあります。それは、あなたのプログラムがあなたができる単一の個別のタスクより多くの責任を負う恐ろしい現実の世界のビットフィールドとの和集合の中にあるからですまだ偶然に(かなり簡単に)異なる共用体の2つのフラグフィールドを一緒に(たとえば、AnimalPropertiesとObjectPropertiesは両方ともintであるため)スマッシュし、すべてのビットを混合します。これは、追跡するのが恐ろしいバグです...この投稿の多くの人は、ビットマスクの作成は簡単で、保守が難しいため、ビットマスクを頻繁に使用しません。

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 :(
}

したがって、「Flags」への直接アクセスを防ぐためにunion宣言を非公開にし、getter / setterと演算子オーバーロードを追加する必要があります。その後、それらすべてのためのマクロを作成します。基本的には、開始しようとしたときにすぐに戻ります。 Enumでこれを行います。

残念ながら、コードを移植可能にしたい場合は、A)ビットレイアウトを保証するか、B)コンパイル時にビットレイアウトを決定する方法がないと思います(追跡して、少なくとも全体の変更を修正できます)バージョン/プラットフォームなど) ビットフィールドのある構造体のオフセット

実行時に、フィールドを設定してフラグをXORし、どのビットが変更されたかを確認するトリックをプレイできます。100%一貫性があり、プラットフォームに依存せず、完全に決定的なソリューション、つまりENUMがある場合でも、私にはかなり安っぽい音がします。

TL; DR:嫌悪者の言うことを聞かないでください。C ++は英語ではありません。Cから継承された短縮キーワードのリテラル定義が使用法に適合しない場合があるというだけで、キーワードのC および C ++定義にユースケースが完全に含まれている場合、それを使用してはならないというわけではありません。構造体を使用して、構造体以外のものをモデリングしたり、学校や社交カースト以外のものをクラス化したりすることもできます。固定された値にはfloatを使用できます。未使用でも、小説、演劇、映画の人物でもない変数には、charを使用できます。言語仕様の前にキーワードの意味を決定するために辞書に行くプログラマーは誰でも……まあ、私はそこに舌を持ちます。

話し言葉に基づいてコードをモデル化したい場合は、偶然にもビットフィールドに列挙型を多用するObjective-Cで作成するのが最善です。


3

構文糖のみ。追加のメタデータはありません。

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

整数型のフラグ演算子は機能します。


私見これが最良の答えです。クリーンでシンプルで簡単なクライアント構文。「constexpr uint8_t」ではなく「const int」を使用するだけですが、概念は同じです。
ヨーヨー

(申し訳ありませんが、 "constexpr int")
ヨーヨー

3

現在、列挙型フラグの言語サポートはありません。メタクラスがc ++標準の一部である場合、この機能が本質的に追加される可能性があります。

私の解決策は、基礎となる型を使用して、列挙型クラスの型セーフなビットごとの操作のサポートを追加する列挙型のみのインスタンス化されたテンプレート関数を作成することです。

ファイル: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_

利便性と間違いを減らすために、列挙型と整数のビットフラグ演算をラップすることもできます。

ファイル: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_

可能な使用法:

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

3

@Xaqqは、列挙型のフラグを使用するには、本当にすてきなタイプセーフな方法で提供している、ここflag_setクラスを。

コードをGitHub公開しました。使用方法は次のとおりです。

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

2

オブジェクトとオブジェクトのコレクションが混乱しています。具体的には、バイナリフラグとバイナリフラグのセットを混同しています。適切なソリューションは次のようになります。

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

};

2

これは、オーバーロードやキャストの束を必要としない私の解決策です:

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

とにかく(強く型付けされていない)enumとintを識別しているので、問題ないと思います。

(長い)サイドノートと同じように

  • 強く型付けされた列挙型を使用したい
  • フラグをいじる必要はありません
  • パフォーマンスは問題ではありません

私はこれを思いつくでしょう:

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

C ++ 11イニシャライザリストとを使用するenum class


ちなみに、フラグの列挙型はお勧めしません。単純な理由:フラグの組み合わせは、列挙型の要素ではありません。したがって、これはかなり不適切なようです。あるいはusing Flags = unsigned long、フラグ値自体を含む名前空間または構造体の内部/*static*/ const Flags XY = 0x01などを使用します。
yau

1

上記のように(カイ)または次の操作を行います。本当に列挙型は「列挙型」であり、あなたがしたいことはセットを持つことなので、実際にはstl :: setを使用する必要があります

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

1

Objective-CのNS_OPTIONSに似ているかもしれません。

#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.

あなたの答えが最適である理由を説明できますか?この質問に回答した回答は他にもいくつかあります。そのため、区別するための情報をいくつか含めてください。
trevorp
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.