Как использовать перечисления как флаги в С++?

Рассмотрение enum, поскольку флаги прекрасно работают на С# с помощью атрибута [Flags], но что лучший способ сделать это на С++?

Например, я бы хотел написать:

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

seahawk.flags = CanFly | EatsFish | Endangered;

Однако я получаю ошибки компилятора в отношении преобразований int/enum. Есть ли лучший способ выразить это, чем просто тупое литье? Предпочтительно, я не хочу полагаться на конструкции из сторонних библиотек, таких как boost или Qt.

EDIT: Как указано в ответах, я могу избежать ошибки компилятора, объявив seahawk.flags как int. Тем не менее, я хотел бы иметь некоторый механизм для обеспечения безопасности типов, поэтому кто-то не может писать seahawk.flags = HasMaximizeButton.

Ответ 1

"Правильный" способ - определить битовые операторы для перечисления:

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.

Ответ 2

Примечание (также немного отключено от темы): Еще один способ создания уникальных флагов можно выполнить с помощью сдвига бит. Я, сам, считаю, что это легче читать.

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

Он может удерживать значения до int так, чтобы большую часть времени отображал 32 флага, которые четко отражаются на сумме сдвига.

Ответ 3

Для ленивых людей, таких как я, вот шаблонное решение для копирования и вставки:

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

Ответ 4

Какой тип переменной seahawk.flags?

В стандартном С++ перечисления не являются безопасными для типов. Они эффективно целые числа.

AnimalFlags НЕ должен быть типом вашей переменной, ваша переменная должна быть int, и ошибка исчезнет.

Ввод шестнадцатеричных значений, подобных некоторым другим людям, не нужен, это не имеет никакого значения.

Значения перечисления ARE типа int по умолчанию. Таким образом, вы можете поразрядным образом объединить их и собрать их вместе и сохранить результат в int.

Тип перечисления - это ограниченное подмножество 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 и сохранить в нем каждый флаг. Это не просто пространство, но оно безопасно по типу и дает вам ту же способность, что и bitflag int.

Заметка С++ 0x: сильно типизированные перечисления

В С++ 0x вы можете, наконец, иметь безопасные значения перечисления типа...

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

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

Ответ 5

Обратите внимание, что если вы работаете в среде Windows, есть макрос DEFINE_ENUM_FLAG_OPERATORS, определенный в winnt.h, который выполняет эту работу за вас. Поэтому в этом случае вы можете сделать это:

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

seahawk.flags = CanFly | EatsFish | Endangered;

Ответ 6

Я считаю, что принятый ответ eidolon слишком опасен. Оптимизатор компилятора может сделать предположения о возможных значениях в перечислении, и вы можете вернуть мусор с недопустимыми значениями. И обычно никто не хочет определять все возможные перестановки в перечислениях флагов.

Как говорит Брайан Р. Бонди, если вы используете С++ 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. 

Это обеспечивает стабильный размер и диапазон значений путем указания типа для перечисления, запрещает автоматическое downcasting перечислений в ints и т.д. с помощью enum class и использует constexpr, чтобы гарантировать, что код для операторов встраивается и, следовательно, так же быстро, как и обычные номера.

Для людей, придерживающихся диалектов до 11 С++

Если бы я застрял с компилятором, который не поддерживает С++ 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;
};

Вы можете определить это довольно похоже на регулярное перечисление + 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;

И вы также можете переопределить базовый тип для двоично-стабильных перечислений (например, С++ 11 enum foo : type), используя второй параметр шаблона, т.е. typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

Я отметил переопределение operator bool с помощью ключевого слова С++ 11 explicit, чтобы оно не приводило к преобразованию int, так как это могло привести к тому, что множество флагов в конце концов свернуто в 0 или 1 при их записи. Если вы не можете использовать С++ 11, оставьте эту перегрузку и перепишите первое условие в примере использования как (myFlags & EFlagTwo) == EFlagTwo.

Ответ 7

Самый простой способ сделать это, как показано здесь, используя стандартный класс библиотеки bitset.

Чтобы эмулировать функцию С# безопасным способом, вам нужно написать обертку шаблона вокруг битового набора, заменив аргументы 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;

Ответ 8

На мой взгляд, ни один из ответов пока не идеален. Чтобы быть идеальным, я ожидал бы решения:

  1. Поддержка операторов ==, !=, =, &, &=, |, |= и ~ в обычных смысл (т.е. a & b)
  2. Будьте безопасны по типу, то есть не разрешайте присваивать неперечисляемые значения, такие как литералы или целочисленные типы (кроме побитовых комбинаций перечисляемых значений), или разрешайте присваивать переменную перечисления целочисленному типу
  3. Выражения разрешений, такие как if (a & b)...
  4. Не требует злых макросов, специфических для реализации функций или других хаков

Пока что большинство решений относятся к пунктам 2 или 3. На мой взгляд, WebDancer закрывает свои позиции, но в пункте 3 не выполняется, и его необходимо повторять для каждого перечисления.

Мое предлагаемое решение - это обобщенная версия WebDancer, которая также затрагивает пункт 3:

#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 - это функция С++ 14, но, похоже, она хорошо поддерживается и ее легко эмулировать для С++ 11 с помощью простого template<typename T> using underlying_type_t = underlying_type<T>::type;

Ответ 9

Я обнаружил, что задаю тот же вопрос и придумал общее решение на основе С++ 11, похожее на soru's:

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;

Ответ 10

Стандарт 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 Типы битовой маски".

Ответ 11

Если ваш компилятор еще не поддерживает строго типизированные перечисления, вы можете посмотреть следующую статью из источника С++:

Из реферата:

В этой статье представлено решение проблемы ограничения операций бит с помощью     разрешить только безопасные и законные действия и превратить все недопустимые манипуляции бит в     ошибки времени компиляции. Лучше всего, что синтаксис бит-операций остается неизменным,     и код, работающий с битами, не нуждается в модификации, за исключением, возможно,     исправлять ошибки, которые пока не были обнаружены.

Ответ 12

Вы путаете объекты и коллекции объектов. В частности, вы смешиваете двоичные флаги с наборами двоичных флагов. Правильное решение будет выглядеть так:

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

};

Ответ 13

Здесь опция для битмаксов, если у вас фактически нет использования для отдельных значений перечисления (например, вам не нужно их отключать)... и если вас не беспокоит поддержка двоичной совместимости т.е.: вам все равно, где живут ваши битвы... которые вы, вероятно, так и есть. Кроме того, вам лучше не беспокоиться о контроле и контроле доступа. Hmmm, enums имеют некоторые хорошие свойства для бит-полей... интересно, если кто-нибудь когда-либо пробовал это:)

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

Мы видим, что жизнь велика, у нас есть наши дискретные значения, и у нас есть хороший int to и and | к содержанию наших сердец, которое все еще имеет контекст того, что означают его биты. Все согласовано и предсказуемо... для меня... пока я продолжаю использовать компилятор Microsoft VС++ с Update 3 на Win10 x64 и не прикасаюсь к моим флагам компилятора:)

Несмотря на то, что все замечательно... у нас есть некоторый контекст относительно значения флагов сейчас, поскольку он в союзе с битовым полем в ужасном реальном мире, где ваша программа может нести ответственность за более чем одну дискретную задача, которую вы могли бы случайно (довольно легко) разбить два поля флагов разных союзов (скажем, AnimalProperties и ObjectProperties, поскольку они оба являются ints), смешивая все ваши биты, что является ужасной ошибкой для отслеживания... и как я знаю, что многие люди на этом посту не очень часто работают с битмашками, поскольку их легко создавать и поддерживать их сложно.

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

Итак, вы делаете свое объявление union частным, чтобы предотвратить прямой доступ к "Флагам" и должны добавлять геттеры/сеттеры и перегрузки операторов, а затем создавать макрос для всего этого, и вы в основном прямо туда, где вы начали, когда вы пытались сделать это с помощью Enum.

К сожалению, если вы хотите, чтобы ваш код был переносимым, я не думаю, что есть какой-либо способ: либо A) гарантировать компоновку бит, либо B) определить компоновку бит во время компиляции (чтобы вы могли отслеживать и, по крайней мере, исправлять для изменений в версиях/платформах и т.д.) Смещение в структуре с битовыми полями

Во время выполнения вы можете играть в трюки с настройкой полей и XORing флагов, чтобы увидеть, какие биты изменились, звучит довольно дерьмово для меня, хотя стихи, имеющие 100% -ное, независимое от платформы и полностью детерминированное решение, то есть: ENUM.

TL; ДР: Не слушайте ненавистников. С++ не является английским. Просто потому, что буквальное определение сокращенного ключевого слова, унаследованного от C, может не соответствовать вашему использованию, не означает, что вы не должны его использовать, когда определение ключевого слова C и С++ включает в себя ваш прецедент. Вы также можете использовать структуры для моделирования вещей, отличных от структур, и классов для вещей, отличных от школьной и социальной касты. Вы можете использовать float для значений, которые заземлены. Вы можете использовать char для переменных, которые не являются ни сжигаемыми, ни человеком в романе, пьесе или фильме. Любой программист, который идет в словарь, чтобы определить значение ключевого слова до спецификации языка,... хорошо, я буду держать язык за зубами.

Если вам нужен код, смоделированный после разговорного языка, вам лучше всего писать в Objective-C, который, кстати, также сильно использует перечисления для битполей.

Ответ 14

Я хотел бы подробнее остановиться на Ответу Uliwitness, установив его код для С++ 98 и используя Safe Bool idiom, из-за отсутствия шаблона std::underlying_type<> и ключевого слова explicit в версиях на С++ ниже С++ 11.

Я также модифицировал его так, чтобы значения перечисления могли быть последовательными без какого-либо явного назначения, поэтому вы можете иметь

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

Ответ 15

Как указано выше (Kai) или выполните следующее. Действительно перечисления - "Перечисления", то, что вы хотите сделать, - это набор, поэтому вы действительно должны использовать 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);
}

Ответ 16

Вот мое решение без необходимости перегрузки или кастинга:

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

Я думаю, что это нормально, потому что мы все равно идентифицируем (не сильно типизированные) перечисления и ints.

Так же, как (более длинная) заметка, если вы

  • хотите использовать строго типизированные перечисления и
  • Вам не нужны тяжелые битки с флагами
  • производительность не является проблемой.

Я бы придумал следующее:

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

с использованием списков инициализаторов С++ 11 и enum class.

Ответ 17

В настоящее время нет языковой поддержки для флагов перечисления, мета-классы могут по своей природе добавить эту функцию, если она когда-либо станет частью стандарта 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;
}

Ответ 18

Только синтаксический сахар. Никаких дополнительных метаданных.

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

Флаг операторов на целочисленном типе просто работает.

Ответ 19

Может быть, как NS_OPTIONS 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.

Ответ 20

Я использую следующий макрос:

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

Ответ 21

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