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

Лечение enums как флаги хорошо работает в C# через [Flags] атрибут, но каков наилучший способ сделать это в 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,

30 ответов

Решение

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

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.

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

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

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

Обратите внимание, если вы работаете в среде 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;

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

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

К какому типу относится переменная seahawk.flags?

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

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

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

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

Тип enum является ограниченным подмножеством int, значение которого является одним из перечисляемых значений. Следовательно, когда вы создаете какое-то новое значение вне этого диапазона, вы не можете присвоить его без приведения к переменной вашего типа enum.

Вы также можете изменить типы значений enum, если хотите, но для этого вопроса нет смысла.

РЕДАКТИРОВАТЬ: автор сказал, что они были заинтересованы в безопасности типов и они не хотят, чтобы значение, которое не должно существовать внутри типа 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....

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

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

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

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

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

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

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

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

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

Чтобы эмулировать функцию 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;

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

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

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

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

Операторы флагов на целочисленном типе просто работают.

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

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

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

Я обнаружил, что задаю тот же вопрос, и нашел общее решение на 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;

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

Из аннотации:

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

Другое макро-решение, но в отличие от существующих ответов в нем не используется reinterpret_cast (или C-cast) для перевода между Enum& а также Int&, что запрещено в стандартном C++ (см. этот пост).

#define MAKE_FLAGS_ENUM(TEnum, TUnder)                                                                                             \
TEnum  operator~  ( TEnum  a          ) { return static_cast<TEnum> (~static_cast<TUnder> (a)                           ); }  \
TEnum  operator|  ( TEnum  a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) |  static_cast<TUnder>(b) ); }  \
TEnum  operator&  ( TEnum  a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) &  static_cast<TUnder>(b) ); }  \
TEnum  operator^  ( TEnum  a, TEnum b ) { return static_cast<TEnum> ( static_cast<TUnder> (a) ^  static_cast<TUnder>(b) ); }  \
TEnum& operator|= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b) ); return a; }  \
TEnum& operator&= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b) ); return a; }  \
TEnum& operator^= ( TEnum& a, TEnum b ) { a = static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b) ); return a; }

Потерять reinterpret_cast означает, что мы не можем полагаться на x |= y синтаксис больше, но расширив их до их x = x | y формы нам это больше не нужно.

Примечание: вы можете использовать std::underlying_type чтобы получить TUnder, Я не включил это для краткости.

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

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 для & и | к нашему сердцу содержание, которое все еще имеет контекст того, что означают его биты. Все непротиворечиво и предсказуемо... для меня... пока я продолжаю использовать компилятор Microsoft VC++ с обновлением 3 на Win10 x64 и не трогаю флаги моего компилятора:)

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

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

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

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

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

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

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

Я хотел бы подробнее остановиться на ответе Uliwitness, исправляя его код для C++98 и используя идиому Safe Bool, из-за отсутствия std::underlying_type<> шаблон и explicit ключевое слово в версиях C++ ниже C++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;
};

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

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

Копируемый "злой" макрос на основе некоторых других ответов в этой теме:

      #include <type_traits>

/*
 * Macro to allow enum values to be combined and evaluated as flags.
 *  * Based on:
 *  - DEFINE_ENUM_FLAG_OPERATORS from <winnt.h>
 *  - https://stackoverflow.com/a/63031334/1624459
 */
#define MAKE_ENUM_FLAGS(TEnum)                                                      \
    inline TEnum operator~(TEnum a) {                                               \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(~static_cast<TUnder>(a));                         \
    }                                                                               \
    inline TEnum operator|(TEnum a, TEnum b) {                                      \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b)); \
    }                                                                               \
    inline TEnum operator&(TEnum a, TEnum b) {                                      \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b)); \
    }                                                                               \
    inline TEnum operator^(TEnum a, TEnum b) {                                      \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        return static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b)); \
    }                                                                               \
    inline TEnum& operator|=(TEnum& a, TEnum b) {                                   \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        a = static_cast<TEnum>(static_cast<TUnder>(a) | static_cast<TUnder>(b));    \
        return a;                                                                   \
    }                                                                               \
    inline TEnum& operator&=(TEnum& a, TEnum b) {                                   \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        a = static_cast<TEnum>(static_cast<TUnder>(a) & static_cast<TUnder>(b));    \
        return a;                                                                   \
    }                                                                               \
    inline TEnum& operator^=(TEnum& a, TEnum b) {                                   \
        using TUnder = typename std::underlying_type_t<TEnum>;                      \
        a = static_cast<TEnum>(static_cast<TUnder>(a) ^ static_cast<TUnder>(b));    \
        return a;                                                                   \
    }

использование

      enum class Passability : std::uint8_t {
    Clear      = 0,
    GroundUnit = 1 << 1,
    FlyingUnit = 1 << 2,
    Building   = 1 << 3,
    Tree       = 1 << 4,
    Mountain   = 1 << 5,
    Blocked    = 1 << 6,
    Water      = 1 << 7,
    Coastline  = 1 << 8
};

MAKE_ENUM_FLAGS(Passability)

Преимущества

  • Применяется только к выбранным перечислениям при явном использовании.
  • Не использовать незаконные reinterpret_cast.
  • Нет необходимости указывать базовый тип.

Примечания

  • Заменять std::underlying_type_t<TEnum> с участием std::underlying_type<TEnum>::type при использовании C++ <14.

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

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

};

C++20 типобезопасных операторов перечисления

TL;DR

      template<typename T>
requires std::is_enum_v<T> and
         requires (std::underlying_type_t<T> x) {
             { x | x } -> std::same_as<std::underlying_type_t<T>>;
             T(x);
         }
T operator|(T left, T right)
{
    using U = std::underlying_type_t<T>;
    return T( U(left) | U(right) );
}

template<typename T>
requires std::is_enum_v<T> and
         requires (std::underlying_type_t<T> x) {
             { x | x } -> std::same_as<std::underlying_type_t<T>>;
             T(x);
         }
T operator&(T left, T right)
{
    using U = std::underlying_type_t<T>;
    return T( U(left) & U(right) );
}

template<typename T>
requires std::is_enum_v<T> and requires (T x) { { x | x } -> std::same_as<T>; }
T & operator|=(T &left, T right)
{
    return left = left | right;
}

template<typename T>
requires std::is_enum_v<T> and requires (T x) { { x & x } -> std::same_as<T>; }
T & operator&=(T &left, T right)
{
    return left = left & right;
}

Обоснование

С чертой типаstd::is_enumмы можем протестировать какой-то типTдля того, является ли он типом перечисления. Сюда входят перечисления как с незаданной областью, так и с областью действия (т.е.enumиenum class). С чертой типаstd::underlying_typeмы можем получить базовый тип перечисления. С концепциями и ограничениями C++20 довольно легко затем предоставить перегрузки для побитовых операций.

С ограниченной областью действия и без нее

Если операции должны быть перегружены только для перечислений с областью действия или без нее,std::is_scoped_enumможно использовать для соответствующего расширения ограничений шаблона.

С++23

С С++ 23 мы получаемstd::to_underlyingчтобы упростить преобразование значения перечисления в его базовый тип.

Семантика перемещения и совершенная переадресация

Если вы попадете в странную ситуацию, когда ваш базовый тип имеет различную семантику для копирования и перемещения или он не обеспечивает копирование c'tor, тогда вы должны выполнить идеальную пересылку операндов с помощьюstd::forward.

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

Вот ленивое решение C++11, которое не меняет поведение перечислений по умолчанию. Это также работает для enum structа также enum class, и является constexpr.

      #include <type_traits>

template<class T = void> struct enum_traits {};

template<> struct enum_traits<void> {
    struct _allow_bitops {
        static constexpr bool allow_bitops = true;
    };
    using allow_bitops = _allow_bitops;

    template<class T, class R = T>
    using t = typename std::enable_if<std::is_enum<T>::value and
        enum_traits<T>::allow_bitops, R>::type;

    template<class T>
    using u = typename std::underlying_type<T>::type;
};

template<class T>
constexpr enum_traits<>::t<T> operator~(T a) {
    return static_cast<T>(~static_cast<enum_traits<>::u<T>>(a));
}
template<class T>
constexpr enum_traits<>::t<T> operator|(T a, T b) {
    return static_cast<T>(
        static_cast<enum_traits<>::u<T>>(a) |
        static_cast<enum_traits<>::u<T>>(b));
}
template<class T>
constexpr enum_traits<>::t<T> operator&(T a, T b) {
    return static_cast<T>(
        static_cast<enum_traits<>::u<T>>(a) &
        static_cast<enum_traits<>::u<T>>(b));
}
template<class T>
constexpr enum_traits<>::t<T> operator^(T a, T b) {
    return static_cast<T>(
        static_cast<enum_traits<>::u<T>>(a) ^
        static_cast<enum_traits<>::u<T>>(b));
}
template<class T>
constexpr enum_traits<>::t<T, T&> operator|=(T& a, T b) {
    a = a | b;
    return a;
}
template<class T>
constexpr enum_traits<>::t<T, T&> operator&=(T& a, T b) {
    a = a & b;
    return a;
}
template<class T>
constexpr enum_traits<>::t<T, T&> operator^=(T& a, T b) {
    a = a ^ b;
    return a;
}

Чтобы включить побитовые операторы для перечисления:

      enum class my_enum {
    Flag1 = 1 << 0,
    Flag2 = 1 << 1,
    Flag3 = 1 << 2,
    // ...
};

// The magic happens here
template<> struct enum_traits<my_enum> :
    enum_traits<>::allow_bitops {};

constexpr my_enum foo = my_enum::Flag1 | my_enum::Flag2 | my_enum::Flag3;

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

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

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

Так же, как (более длинное) примечание, если вы

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

Я бы придумал это:

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

я предпочитаю использоватьпоскольку это помогает автоматизировать преобразование строк в перечисления и наоборот. Это библиотека только для заголовков, написанная на стандарте С++17.

magic_enumуже имеет шаблонные функции для побитовых операторов перечисления. См. документацию .

Использование:

      #include <magic_enum.hpp>

enum Flag { ... };

Flag flag{};
Flag value{};

using namespace magic_enum::bitwise_operators;
flag |= value;

Вы можете использовать структуру следующим образом:

      struct UiFlags2 {
    static const int
    FULLSCREEN = 0x00000004,               //api 16
    HIDE_NAVIGATION = 0x00000002,          //api 14
    LAYOUT_HIDE_NAVIGATION = 0x00000200,   //api 16
    LAYOUT_FULLSCREEN = 0x00000400,        //api 16
    LAYOUT_STABLE = 0x00000100,            //api 16
    IMMERSIVE_STICKY = 0x00001000;         //api 19
};

и используйте как это:

      int flags = UiFlags2::FULLSCREEN | UiFlags2::HIDE_NAVIGATION;

Так что тебе не нужно intлитье, и его можно использовать напрямую.
Также это разделение области, например enum class

Может быть, как 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.
      (int&)seahawk.flags = CanFly | EatsFish | Endangered;
  1. отлит изenum AnimalFlags&к
  2. результатCanFly | EatsFish | Endangeredнабирается.
  3. результатintнаписано вseahawk.flagsкакint&

Отказ от ответственности: C++ заставляет вас не делать этого и писать перегрузки для |оператор. Вы должны знать, что делаете. Будьте C++ своим инструментом, а не своим ревнивым богом.

Не совсем ответ, но я хотел бы подчеркнуть, что для тех, у кого есть проблемы с реализацией 64-битных битовых полей, вам следует добавить «ull» к 1 в операции сдвига битов.

      enum BitField : uint64_t
{
    FIELD_1 = 1ull << 0,
    FIELD_2 = 1ull << 1,
    FIELD_3 = 1ull << 2,
    FIELD_4 = 1ull << 3,
    FIELD_5 = 1ull << 4,

    FIELD_U32_MAX = 1ull << 30,

    FIELD_U64_MAX = 1ull << 63,
};

это позволяет избежать ошибки, связанной с ограничением 64-битных перечислений 32-битами.

Я думаю, мы можем упростить решение, отказавшись от использования наборов битов и сделав это, создав шаблонный класс с переменной-членом типа, лежащего в основе перечислителя, а затем переопределив операторы (&=,|=).

Вот мой код:

      template <typename _Enum> class EnumFlags final
{
    static_assert(std::is_enum_v<_Enum>,"EnumFlags can only be specialized for enum types!");

    using UnderlyingType = std::underlying_type_t<_Enum>;

    static_assert(std::is_unsigned_v<UnderlyingType>,"EnumFlags requires underlying enum type to be unsigned");

private:
    UnderlyingType m_flags = 0;

public:
    EnumFlags() = default;
    EnumFlags(EnumFlags &&) = default;
    EnumFlags(const EnumFlags &) = default;

public:
    [[nodiscard("")]] constexpr void operator&=(_Enum _enum) noexcept
    {
        m_flags &= (UnderlyingType)_enum;
    }

    [[nodiscard("")]] constexpr void operator|=(_Enum _enum) noexcept
    {
        m_flags |= (UnderlyingType)_enum;
    }

    [[nodiscard("")]] constexpr void operator^=(_Enum _enum) noexcept
    {
        m_flags ^= (UnderlyingType)_enum;
    }

    [[nodiscard("")]] constexpr bool operator&(_Enum _enum) noexcept
    {
        return m_flags & (UnderlyingType)_enum;
    }

public:
    [[nodiscard("")]] UnderlyingType GetFlags() const noexcept
    {
        return (UnderlyingType)m_flags;
    }
};

Я надеюсь, что это решение ограничивает гибкость и простоту использования предыдущих ответов, хорошая разработка!

Другие вопросы по тегам