Подтвердить что ты не робот

Как я могу использовать класс 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.

Можно ли указать пользовательскую функцию для оценки перечисления класса в булевом контексте?

4b9b3361

Ответ 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)) {
    /// ...
}