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?
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_cast
ing 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]
- For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type.
For enumerations whose underlying type is not fixed (i.e. : std::uint32_t
is missing) the standard says basically the same thing, but in a more convoluted way: the enum has the same values as the underlying type, but there are more rules about what the underlying type is.
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;