Как я могу использовать класс enum в булевом контексте?
У меня есть общий код, который работает с флагами, указанными с использованием типов С++ 11 enum class
. На одном шаге я хотел бы знать, установлен ли какой-либо из бит в флагове. В настоящее время я использую код:
if (flags != static_cast<E>(0)) // Works, but ugly.
Я также мог заставить пользователей указать конкретное имя для поля all-zero, которое более читаемо, но налагает мои соглашения об именах на всех, кто его использует:
if (flags != E::none) // Works, if you manually define none = 0.
Но ни одна из них не читается так хорошо, как традиционная:
if (flags) // Doesn't work with class enums.
Можно ли указать пользовательскую функцию для оценки перечисления класса в булевом контексте?
Ответы
Ответ 1
Можно ли указать пользовательскую функцию для оценки перечисления класса в булевом контексте?
Да, но не автоматически. Вручную вызов функции еще шире, чем другие представленные альтернативы.
Просто выберите красивое имя функции, например any
, и выполните его. Разрешение перегрузки гарантирует, что ваша функция будет хорошо работать со всеми остальными.
bool any( E arg )
{ return arg != E::none; }
...
if ( any( flags ) ) {
...
Выглядит достаточно хорошо для меня.
Обновление:, если вы хотите, чтобы это применимо к нескольким типам перечислений, его можно настроить:
template< typename enum_type > // Declare traits type
struct enum_traits {}; // Don't need to declare all possible traits
template<>
struct enum_traits< E > { // Specify traits for "E"
static constexpr bool has_any = true; // Only need to specify true traits
};
template< typename enum_type > // SFINAE makes function contingent on trait
typename std::enable_if< enum_traits< enum_type >::has_any,
bool >::type
any( enum_type e )
{ return e != enum_type::none; }
Я использую этот механизм для других вещей и никогда не сталкивался с каким-либо побочным эффектом или проблемами: v).
Вы можете пропустить признак и установить условие SFINAE на что-то вроде enum_type::none == enum_type::none
, чтобы просто проверить наличие none
и оператора равенства, но это будет менее явным и безопасным.
Ответ 2
Как говорит @RMatin. Но вы можете перегрузить operator!
bool operator!(E e) {
return e == static_cast<E>(0);
}
Чтобы вы могли использовать !!e
idiom
if(!!e) {
...
}
Ответ 3
Нет, не так. Операторы преобразования должны быть членами, а перечисления не могут иметь членов. Я думаю, что лучшее, что вы можете сделать, это сравнение с none
, или, если нет перечислителя none
, оберните static_cast
в функцию.
Ответ 4
Если у вас есть поле flags (то есть: бит), я настоятельно рекомендую вам не использовать enum class
для битполей.
Сильно типизированные перечисления существуют, ну, строго типизированы. Это делает перечислители чем-то большим, чем просто называемые постоянными целыми, как обычные перечисления. Идея состоит в том, что если у вас есть переменная типа enum class
, то ее содержимое всегда должно точно соответствовать одному из значений перечислителя. Поэтому нет никакого неявного преобразования из или в целые типы.
Но это не то, что вы делаете. Вы принимаете битполе, что является составом значений перечислителя. Эта композиция сама по себе не является одним из этих значений; это их комбинация. Поэтому вы лжете, когда говорите, что используете тип enum class
; вы действительно просто принимаете целое число без знака, которое может быть одним из перечислений enum class
.
Например:
enum class Foo
{
First = 0x01,
Second = 0x02,
Third = 0x04,
};
Foo val = Foo::First | Foo::Second;
val
в этом случае не содержит First
, Second
или Third
. Вы потеряли сильную типизацию, потому что она не содержит каких-либо типов.
enum class
значения не могут быть неявно преобразованы в bool; они не могут быть неявно преобразованы в целые числа; и они не могут неявно выполнять большинство математических операций, выполняемых над ними. Это непрозрачные значения.
И, следовательно, они не подходят для использования в качестве битовых полей. Попытка использовать enum class
таким неприемлемым способом приведет только к большому количеству кастингов. Просто используйте обычный старый enum
и спасите себя от боли.
Ответ 5
struct Error {
enum {
None = 0,
Error1 = 1,
Error2 = 2,
} Value;
/* implicit */ Error(decltype(Value) value) : Value(value) {}
explicit operator bool() {
return Value != Error::None;
}
};
inline bool operator==(Error a, Error b) {
return a.Value == b.Value;
}
inline bool operator!=(Error a, Error b) {
return !(a == b);
}
enum
теперь не имеет перегруженного оператора, поэтому оберните его в class
или struct
.
Ответ 6
Краткий пример ниже перечисляемых флагов.
#indlude "enum_flags.h"
ENUM_FLAGS(foo_t)
enum class foo_t
{
none = 0x00
,a = 0x01
,b = 0x02
};
ENUM_FLAGS(foo2_t)
enum class foo2_t
{
none = 0x00
,d = 0x01
,e = 0x02
};
int _tmain(int argc, _TCHAR* argv[])
{
if(flags(foo_t::a & foo_t::b)) {};
// if(flags(foo2_t::d & foo_t::b)) {}; // Type safety test - won't compile if uncomment
};
ENUM_FLAGS (T) - макрос, определенный в enum_flags.h (менее 100 строк, бесплатно для использования без ограничений).
Ответ 7
Я обычно перегружаю унарный оператор +
для флага типа enum classes
, поэтому я могу сделать следующее:
#define ENUM_FLAGS (FlagType, UnderlyingType) \
/* ... */ \
UnderlyingType operator+(const FlagType &flags) { \
return static_cast<UnderlyingType>(flags) \
} \
/* ... */ \
FlagType operator&(const FlagType &lhs, const FlagType &rhs) { \
return static_cast<FlagType>(+lhs & +rhs) \
} \
/* ... */ \
FlagType &operator|=(FlagType &lhs, const FlagType &rhs) { \
return lhs = static_cast<FlagType>(+lhs | +rhs) \
} \
/* ... */ \
/***/
// ....
enum class Flags: std::uint16_t {
NoFlag = 0x0000,
OneFlag = 0x0001,
TwoFlag = 0x0002,
// ....
LastFlag = 0x8000
};
ENUM_FLAGS(Flags, std::uint16_t)
auto flagVar = Flags::NoFlag;
// ...
flagVar |= Flags::OneFlag;
// ...
if (+(flagVar & Flags::OneFlag)) {
/// ...
}