Как я могу использовать класс enum в логическом контексте? - PullRequest
31 голосов
/ 26 марта 2012

У меня есть некоторый общий код, который работает с флагами, указанными с использованием типов C ++ 11 enum class.На одном шаге я хотел бы знать, установлены ли какие-либо биты во флаге.В настоящее время я использую код:

if (flags != static_cast<E>(0)) // Works, but ugly.

Я также мог бы заставить пользователей указывать конкретное имя для поля «все ноль», которое более читабельно, но налагает мои соглашения об именах для всех, кто его использует:

if (flags != E::none) // Works, if you manually define none = 0.

Но ни один из них не выглядит так же хорошо, как традиционный:

if (flags) // Doesn't work with class enums.

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

Ответы [ 7 ]

26 голосов
/ 26 марта 2012

Как говорит @RMatin. Но вы могли бы перегрузить operator!

bool operator!(E e) {
  return e == static_cast<E>(0);
}

Так что вы можете использовать !!e идиома

if(!!e) {
  ...
}
19 голосов
/ 27 марта 2012

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

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

Просто выберите подходящее имя функции, например 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 и оператора равенства, но это будет менее явным и безопасным.

8 голосов
/ 27 марта 2012

Если у вас есть поле флагов (то есть: битовое поле), я бы настоятельно посоветовал вам не использовать 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 и избавьте себя от боли.

7 голосов
/ 30 апреля 2014
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 голосов
/ 26 марта 2012

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

1 голос
/ 31 июля 2017

Я обычно перегружаю унарный оператор + для флагов 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)) {
    /// ...
}
0 голосов
/ 21 июля 2013

Ниже приведен краткий пример перечислимых флагов.

#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 строк, бесплатный для использования без ограничений).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...