是否可以使作用域枚举("enum class")在上下文中可转换为布尔值?

Is it possible to make a scoped enumeration ("enum class") contextually convertible to bool?

本文关键字:上下文 可转换 布尔值 class 作用域 可以使 枚举 是否 enum      更新时间:2023-10-16

假设我有

enum class Flags : std::uint16_t
{
    None = 0,
    A    = 0x0001,
    B    = 0x0002,
    C    = 0x0004
}
inline Flags operator|(Flags lhs, Flags rhs)
{
    return static_cast<Flags>(static_cast<std::uint16_t>(lhs) | static_cast<std::uint16_t>(rhs));
}
inline Flags operator&(Flags lhs, Flags rhs)
{
    return static_cast<Flags>(static_cast<std::uint16_t>(lhs) & static_cast<std::uint16_t>(rhs));
}
inline Flags operator|=(Flags& lhs, Flags rhs)
{
    return lhs = lhs | rhs;
}
inline Flags operator&=(Flags& lhs, Flags rhs)
{
    return lhs = lhs & rhs;
}

是否有可能使enum类在上下文中可转换为bool以允许某人执行

Flags f = /* ... */;
if (f & Flags::A) {
    // Do A things
}

我不认为您可以为bool提供转换操作符,因为没有该类的实际实例,但是您可以重载其他操作符。自然的是operator!:

bool operator!(Flags f) {
   return f == Flags::None;
}

那么你的程序会做:

if (!!(f & Flags::A)) {

这确实是不自然的,但对于其他人来说,这并不会让人感到可怕的惊讶(至于它的意思,他们可能会对双重否定感到困惑)。

或者,您可以将该操作实现为命名函数,以使其更具可读性:

bool test(Flag f, Flag mask) {
   return !!(f & mask);
}
if (test(f,Flags::A)) { …

那么,如果你真的想要隐式转换,为什么要首先使用enum类呢?

虽然不能单独使用强类型枚举完成此操作,但可以将枚举类型和转换封装在类中,以获得与您所寻找的行为相似的行为。将它们组合在一起确实需要更多的努力,但不会太麻烦(除非您要处理数十个枚举基位标志)。在这种情况下,基于模板的解决方案可能是可取的。

通过将

封装在一个类中,您可以获得执行问题中详细操作所需的所有转换操作符。这些转换是双向的,当在命名空间范围内与操作符结合时,提供(我希望)您想要实现的行为。

代码:

#include <cstdint>
class Flags
{
    enum class Enum : std::uint16_t
    {
        EMPTY = 0, FLAG1 = 1, FLAG2 = 2, FLAG3 = 4, FLAG4 = 8
    };
public:
    //  Default constructor. At least you'll have default initialization.
    Flags() : value_(EMPTY) {}
    //  Basic copy-ctor
    Flags(const Flags& value) : value_(value.value_) {}
    //  Conversion-ctor allowing implicit conversions. This allows the
    //  non-member operators to work.
    Flags(Enum value) : value_(value) {}
    //  We want to be able to expose and use the strongly typed enum.
    operator Enum() const
    {
        return value_;
    }
    //  In order to simplify the manipulation of the enum values we
    //  provide an explicit conversion to the underlying type.
    explicit operator std::uint16_t() const
    {
        return static_cast<std::uint16_t>(value_);
    }
    //  Here's your magical bool conversion.
    explicit operator bool() const
    {
        return value_ != EMPTY;
    }
    //  Let's make some friends so Enum can continue to be a hermit.
    friend inline Flags operator|(Flags::Enum lhs, Flags::Enum rhs);
    friend inline Flags operator&(Flags lhs, Flags rhs);
    //  As a convenience we declare the enumeration values here. This allows
    //  scoping similar to the typed enums.
    static const Enum EMPTY = Enum::EMPTY;
    static const Enum FLAG1 = Enum::FLAG1;
    static const Enum FLAG2 = Enum::FLAG2;
    static const Enum FLAG3 = Enum::FLAG3;
    static const Enum FLAG4 = Enum::FLAG4;
private:
    Enum  value_;
};

inline Flags operator|(Flags::Enum lhs, Flags::Enum rhs)
{
    return static_cast<Flags::Enum>(
        static_cast<std::uint16_t>(lhs)
        | static_cast<std::uint16_t>(rhs));
}
inline Flags operator&(Flags lhs, Flags rhs)
{
    return static_cast<Flags::Enum>(
        static_cast<std::uint16_t>(lhs)
        & static_cast<std::uint16_t>(rhs));
}
inline Flags operator|=(Flags& lhs, Flags rhs)
{
    return lhs = lhs | rhs;
}
inline Flags operator&=(Flags& lhs, Flags rhs)
{
    return lhs = lhs & rhs;
}
void Func(Flags)
{
    // do something really cool here
}
int main()
{
    Flags    f;
    // equality
    if (f) {}
    if (!f) {}
    // operations and more equality
    f |= Flags::FLAG1;
    if (f & Flags::FLAG1) {}
    f &= Flags::FLAG1;
    // Call a function after doing some ops on the plain enum values
    Func(Flags::FLAG1 | Flags::FLAG2);
}

我看到的一个缺点是,它不能很好地与枚举相关的类型特征(即std::underlying_type)。