7

Let's say I have

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;
}

Is it possible to make the enum class contextually convertible to bool to allow someone to do

Flags f = /* ... */;
if (f & Flags::A) {
    // Do A things
}
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • I'd like to know also. Currently I usually find I'm doing something like `if(f & static_cast(Flags::A))` whenever I need this. – shuttle87 Jun 19 '14 at 02:52

2 Answers2

5

I don't think you can provide a conversion operator to bool, as there is no real instance of the class, but you can overload other operators. The natural one would be operator!:

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

Then your program would do:

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

Which is really not natural but it would not be horribly surprising to others (as of what it means, they would probably be puzzled by the double negation).

Alternative, you can implement the operation as a named function to make it more readable:

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

Then again, if you really want implicit conversions, why are you using an enum class in the first place?

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 1
    `Then again, if you really want implicit conversions, why are you using an enum class in the first place?` Because `enum class` disables harmful implicit conversions to/from `int`. This is the same reason we have explicit conversion operators in C++11 -- we want to allow a specific use (testing a flag in an `if`) and prevent harmful uses (accidentally passing the flags enum somewhere as an `int`, or accidentally passing an `int` as the flags enum). – Billy ONeal Jun 19 '14 at 07:10
  • @BillyONeal If you want to avoid harmful implicit conversions don't add one for `bool`. Although you limit the scope of the conversions you are intentionally exposing the same problem you're trying to avoid by using strongly typed enums. – Captain Obvlious Jun 19 '14 at 07:15
  • @Captain: Not true. `explicit operator bool` does *not* allow implicit conversion to/from `int`, which is the harmful behavior here. That's why this whole explicit conversion operator and contextual conversion mechanism was added to the language to begin with. – Billy ONeal Jun 19 '14 at 07:43
  • I know that but `operator bool()` is not even applicable here unless you stuff everything into a class. – Captain Obvlious Jun 19 '14 at 07:50
  • @Captain: Then the answer to my question "can I make an enum class contextually convertible to bool" is "no, you can't". I'm fine with that answer. – Billy ONeal Jun 19 '14 at 16:15
  • 1
    Actually I'm going with "not with strongly types enums alone but you can fudge something very close". See my answer. – Captain Obvlious Jun 20 '14 at 01:13
1

Although you can't accomplish this with strongly typed enums alone you can encapsulate the enum type and conversions in a class to get behavior similar to what you're looking for. It does take a bit more effort to put it together but not so much it will be cumbersome (unless you're doing dozens of enum base flags. In that case a template based solution might be desirable.

By encapsulating it in a class you gain all the necessary conversion operators necessary to perform the operations detailed in your question. These conversions go both ways and when coupled with operators at namespace scope provides (I hope) the behavior you are trying to achieve.

The code:

#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);
}

One downside I see to this is that it doesn't play well with enum related type traits (i.e. std::underlying_type).

Captain Obvlious
  • 19,754
  • 5
  • 44
  • 74
  • +1. Unfortunately this won't work for my application for now (trying to update legacy C code that has issues with constructors and destructors) but I'll look at it in the future. :) – Billy ONeal Jun 20 '14 at 03:10
  • That is unfortunate. Hopefully this will solve your problem or at least get you closer to the behavior you're wanting. – Captain Obvlious Jun 20 '14 at 03:15
  • @CaptainObvlious if this class is passed by value in a manner similar to enums (or any other fundamental types), I wonder if the compiler will be able to optimize it to pass in eg a register (instead of memcpy, etc.). Basically, will it incur additional overhead as compared to a regular enum? (Sorry for beating a dead horse.) – Super-intelligent Shade Jul 14 '17 at 17:05