【问题标题】:How can I use an enum class in a boolean context?如何在布尔上下文中使用枚举类?
【发布时间】:2012-03-26 15:26:44
【问题描述】:

我有一些通用代码可以使用 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.

是否可以指定一个自定义函数来评估布尔上下文中的类枚举?

【问题讨论】:

  • 您不能使用二进制标志是否有特定原因?它们干净高效。无论如何,枚举类值不能转换为数字。如果要枚举,则必须使用标志!= E::none。你知道,你可以只创建一个非枚举类并使用 static const int whatever = 1、static const int whatever_else = 2...等。并且只重载几个运算符。
  • @OrgnlDave:“二进制标志”是什么意思,布尔值?作为位域,它们通常是 IB/UB 并且定期对齐,如果我需要一次复制多个位域,我不会称它们为有效的,也不会干净。我没有看到建议静态整数的价值,我可以使用常规枚举并至少获得一点类型安全。
  • “我可以只使用一个常规枚举并获得至少一点类型安全性。”然后你必须接受这一点,同时你也不能再谈论比特了。
  • @GManNickG:不,我没有;枚举类的基本点之一是它们是命名特定(一组)值表示的类型安全方式。说位和类型安全是绝对没问题的。
  • @GManNickG:枚举类移除隐式转换为 int 但不隐藏或更改值表示。您仍然可以按字面意思指定值,您仍然可以使用 static_cast 而不是 reinterpret_cast,并且您仍然可以使用 std::underlying_type。

标签: c++ c++11


【解决方案1】:

就像@RMatin 所说。但是你可以超载operator!

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

这样你就可以使用!!e idiom

if(!!e) {
  ...
}

【讨论】:

  • 不错!我喜欢这个胜过“任何”。结合 SFINAE 的答案,这对我来说是最好的解决方案。
【解决方案2】:

是否可以指定自定义函数来评估布尔上下文中的类枚举?

是的,但不是自动的。手动调用函数仍然比提供的其他替代方法更优雅。

只需选择一个好听的函数名称,例如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 和相等运算符的存在,但这会不太明确和安全。

【讨论】:

    【解决方案3】:
    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 目前没有重载运算符,因此请将其包装在 classstruct 中。

    【讨论】:

      【解决方案4】:

      如果您有一个标志字段(即:位字段),我会强烈建议您不要将enum class 用于位字段。

      存在强类型枚举,嗯,强类型。它使枚举器不仅仅是像常规枚举那样命名的常量整数。这个想法是,如果您有一个enum class 类型的变量,那么它的内容应该总是 与枚举器值之一完全匹配。这就是为什么没有从整数类型到整数类型的隐式转换。

      但这不是你正在做的。您正在使用一个位域,它是枚举器值的组合。这种组合本身并不是这些价值中的任何一种;这是它们的组合。因此,当你说你使用enum class 类型时,你是在撒谎;你真的只是在取一个可能enum class枚举数之一的无符号整数。

      例如:

      enum class Foo
      {
        First   = 0x01,
        Second  = 0x02,
        Third   = 0x04,
      };
      
      Foo val = Foo::First | Foo::Second;
      

      val 在这种情况下不包含FirstSecondThird。你已经失去了强类型,因为它不包含任何类型。

      enum class 值不能隐式转换为布尔值;它们不能隐式转换为整数;并且它们不能隐式地对它们执行大多数数学运算。它们是不透明的值

      因此它们不适合用作位域。尝试以这种不恰当的方式使用enum class 只会导致大量选角。只需使用普通的旧 enum 即可避免痛苦。

      【讨论】:

      • "你丢失了强类型,因为它不包含任何类型。"您的意思是,它不包含任何预定义的。这并不意味着我犯了任何特定类型的 type 违规。此外,它们是不透明的值——它们的值表示是根据底层存储类型定义的——它们只是显式值。我很欣赏枚举类在这里不是正确工具的建议,但是说我想要的是“非法的”而不是“需要明确到它如此冗长以至于不再有价值的地步”是不准确的。
      • @JoeWreschnig:首先,我说我写的代码是非法的,它。其次,你想要的需要大量显式和无意义的强制转换,这是你要求我们解决的问题。解决方案是停止使用需要显式和无意义强制转换的东西。第三,它是一个语义类型违规。对枚举进行强类型化的全部意义在于有一些语法保证传递给您的值是合法值之一。是的,人们可以撒谎,但他们至少必须施放才能做到这一点,这通常被认为是在做一些阴暗的事情。
      • -1:有趣,我刚刚回答了另一个问题,即enums 是如何成为标志集的理想选择。 ideone 示例的问题是缺少operator| 重载。 enum 类型被定义为遵循底层类型的值语义,如果支持按位算术,则类型安全和算术安全都得到优化。
      • @Potatoswatter:enums 是,是的。 enum classes 不是enum class 值不遵循值语义。它们是强类型;它们仅用于存储枚举值之一。所以你不能对它们做任何数学运算。请注意,如果您将 Ideaone 代码更改为 enum 而不是 enum class,则该代码有效。在没有显式转换的情况下将数字存储在 enum class 变量中也是非法的。因此,即使 operator| 有效,您仍然需要进行强制转换来存储结果值。
      • @Nicol:“在没有显式转换的情况下将数字存储在枚举类变量中也是非法的。因此,即使 operator| 有效,您仍然需要转换来存储结果值。” ——是的,这正是你会做的。如果默认语义还不够,您可以添加自定义。 ideone.com/yLBZF
      【解决方案5】:

      不,不是那样的。转换运算符必须是成员,枚举不能有成员。我认为您能做的最好的事情就是与none 进行比较,或者,如果没有none 枚举器,则将static_cast 包装在一个函数中。

      【讨论】:

        【解决方案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)) {
              /// ...
          }
          

          【讨论】:

            猜你喜欢
            • 2011-05-19
            • 1970-01-01
            • 2010-09-23
            • 2017-09-07
            • 2013-06-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多