Итак, мы находимся в C++ 17, и до сих пор нет удовлетворительного ответа на действительно отличный интерфейс битовых флагов в C++.
У нас есть enum
который отбрасывает значения своих членов во вмещающую область, но неявно преобразует их в их базовый тип, поэтому может использоваться как если бы они были битовыми флагами, но отказывались переназначаться обратно в enum без приведения.
У нас есть enum class
который решает проблему области имен, так что их значения должны быть явно названы MyEnum::MyFlag
или даже MyClass::MyEnum::MyFlag
, но они неявно преобразуются в свой базовый тип, поэтому не могут использоваться как битовые флаги без бесконечного литья взад и вперед.
И, наконец, у нас есть старые битовые поля из C
такие как:
struct FileFlags {
unsigned ReadOnly : 1;
unsigned Hidden : 1;
...
};
Недостатком которого является отсутствие хорошего способа инициализации себя как целого - нужно прибегнуть к использованию memset или приведению адреса или тому подобного, чтобы перезаписать все значение или инициализировать его все сразу или иным образом манипулировать несколькими битами одновременно. Он также страдает от невозможности назвать значение данного флага, в отличие от его адреса - поэтому нет имени, представляющего 0x02, тогда как при использовании перечислений такое имя существует, поэтому с помощью перечислений легко назвать комбинацию из флаги, такие как FileFlags::ReadOnly | FileFlags::Hidden
FileFlags::ReadOnly | FileFlags::Hidden
- просто нет хорошего способа сказать так много для битовых полей.
Кроме того, у нас все еще есть простой constexpr
или #define
для именования битовых значений, а затем мы просто не используем перечисления вообще. Это работает, но полностью отделяет битовые значения от базового типа битового флага. Возможно, это в конечном итоге не самый плохой подход, особенно если значения битовых флагов являются constexpr
внутри структуры, чтобы дать им собственную область имен?
struct FileFlags {
constexpr static uint16_t ReadOnly = 0x01u;
constexpr static uint16_t Hidden = 0x02u;
...
}
Таким образом, в настоящее время у нас есть много методик, ни один из которых не может дать действительно убедительный способ сказать,
Вот тип, который имеет следующие допустимые битовые флаги, имеет свою собственную область имен, и эти биты и тип должны свободно использоваться со стандартными побитовыми операторами, такими как | & ^ ~, и они должны быть сопоставимы с целочисленными значениями, такими как 0, а результат любых побитовых операторов должен оставаться именованным типом, а не переходить в интеграл
Все это говорит о том, что существует множество попыток создать вышеуказанную сущность в C++ -
- Команда Windows OS разработала простой макрос, который генерирует код C++, чтобы определить необходимые недостающие операторы для данного типа перечисления
DEFINE_ENUM_FLAG_OPERATORS(EnumType)
который затем определяет operator | & ^ ~ и связанные операции назначения, такие как | = и т.д. - "grisumbras" есть проект общественного GIT для включения bitflag семантики с контекстными перечислениями здесь, который использует
enable_if
мету программирования, чтобы данное перечисление преобразовать в тип bitflag, который поддерживает недостающие операторы и снова тихо. - Не зная вышеизложенного, я написал относительно простую оболочку bit_flags, которая определяет для себя все побитовые операторы, так что можно использовать
bit_flags<EnumType> flags
а затемflags
имеет битовую семантику. Чего это не может сделать, так это позволить перечисляемой базе фактически правильно обрабатывать побитовые операторы напрямую, поэтому вы не можете сказатьEnumType::ReadOnly | EnumType::Hidden
EnumType::ReadOnly | EnumType::Hidden
даже при использованииbit_flags<EnumType>
потому что само базовое перечисление все еще не поддерживает необходимые операторы. Я должен был в конечном итоге сделать то же самое, что и # 1 и # 2 выше, и включитьoperator | (EnumType, EnumType)
operator | (EnumType, EnumType)
для различных побитовых операторов, требуя от пользователей объявить специализацию дляtemplate <> struct is_bitflag_enum<EnumType>: std::true_type {};
для их перечисления, такого какtemplate <> struct is_bitflag_enum<EnumType>: std::true_type {};
В конечном счете, проблема с № 1, № 2 и № 3 заключается в том, что невозможно (насколько я знаю) определить отсутствующие операторы в самом перечислении (как в № 1) или определить необходимый тип активатора ( например, template <> struct is_bitflag_enum<EnumType>: std::true_type {};
как в # 2 и частично # 3) в области видимости класса. Это должно происходить вне класса или структуры, так как C++ просто не имеет механизма, о котором я знаю, который позволил бы мне делать такие объявления внутри класса.
Итак, теперь у меня есть желание иметь набор флагов, которые должны быть ограничены определенным классом, но я не могу использовать эти флаги в заголовке класса (например, инициализация по умолчанию, встроенные функции и т.д.), Потому что я не могу включить ни один из механизм, позволяющий обрабатывать перечисление как битовые флаги до закрывающей скобки для определения класса. Или я могу определить все такие перечисления-флагы вне класса, к которому они принадлежат, чтобы затем я мог вызвать "преобразовать это перечисление в побитовый тип" перед определением пользовательского класса, чтобы в полной мере использовать эту функциональность в клиентский класс - но теперь битовые флаги находятся во внешней области видимости, а не связаны с самим классом.
Это не конец света - ничто из вышеперечисленного не является. Но все это вызывает бесконечные головные боли при написании моего кода - и мешает мне писать его самым естественным образом - то есть с заданным перечислением flag, который принадлежит определенному классу в пределах (ограниченного) этого клиентского класса, но с побитовым флагом -семантика (мой подход # 3 почти позволяет это - пока все обернуто битовыми флагами - явно включать необходимую побитовую совместимость).
Все это все еще оставляет у меня досадное чувство, что это может быть намного лучше, чем есть!
Конечно, должен быть - и, возможно, есть, но я еще не понял, - подход к перечислениям, чтобы разрешить для них побитовые операторы, в то же время позволяя им объявляться и использоваться в рамках включающего класса...
У кого-нибудь есть вайп или подход, который я не рассматривал выше, который позволил бы мне "лучший из всех возможных миров" в этом?