Home > Software design >  Is using enum class for flags undefined behavior?
Is using enum class for flags undefined behavior?

Time:01-02

I've been using overloaded operators as demonstrated in the second answer from here: How to use C 11 enum class for flags ... example:

#define ENUMFLAGOPS(EnumName)\
[[nodiscard]] __forceinline EnumName operator|(EnumName lhs, EnumName rhs)\
{\
    return static_cast<EnumName>(\
        static_cast<std::underlying_type<EnumName>::type>(lhs) |\
        static_cast<std::underlying_type<EnumName>::type>(rhs)\
        );\
}...(other operator overloads)

enum class MyFlags : UINT //duplicated in JS
{
    None = 0,
    FlagA = 1,
    FlagB = 2,
    FlagC = 4,
};
ENUMFLAGOPS(MyFlags)

...

MyFlags Flags = MyFlags::FlagA | MyFlags::FlagB;

And I've grown concerned that this may be producing undefined behavior. I've seen it mentioned that merely having an enum class variable that is not equal to one of the defined enum values is undefined behavior. The underlying UINT value of Flags in this case is 3. Is this undefined behavior? And if so, what would be the right way to do this in c 20?

CodePudding user response:

It's a misconception that an enum type has only the values it declares. Enums have all the values of the underlying type. It's just that in an enum some of these values have names. It's perfectly fine to obtain a value that has no name by static_casting or in the case of classical enums by operations (|) or simple assignment.

Your code is perfectly fine (outside of maybe raising some eyebrows for the macro use).

9.7.1 Enumeration declarations [dcl.enum]

  1. For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type.

This is outside the scope of your question but you can define your operators without any macros and I highly recommend it:

template <class E>
concept EnumFlag = std::is_enum_v<E> && requires() { {E::FlagTag}; };

template <EnumFlag E>
[[nodiscard]] constexpr E operator|(E lhs, E rhs)
{
    return static_cast<E>(std::to_underlying(lhs) | std::to_underlying(rhs));
}

enum class MyFlags : std::uint32_t
{
    None = 0x00,
    FlagA = 0x01,
    FlagB = 0x02,
    FlagC = 0x04,

    FlagTag = 0x00,
};

Yes, you can have multiple "names" (enumerators) with the same value. Because we don't don't use the FlagTag value it doesn't matter what value it has.

To mark enums for which you want the operators defined you can use a tag like in the above example or you can use a type trait:

template <class E>
struct is_enum_flag : std::false_type {};

template <>
struct is_enum_flag<MyFlags> : std::true_type {};

template <class E>
concept EnumFlag = is_enum_flag<E>::value;
  • Related