How to overload |= operator on scoped enum?
Solution 1
inline NumericType& operator |=(NumericType& a, NumericType b)
{
return a= a |b;
}
This works? Compile and run: (Ideone)
#include <iostream>
using namespace std;
enum class NumericType
{
None = 0,
PadWithZero = 0x01,
NegativeSign = 0x02,
PositiveSign = 0x04,
SpacePrefix = 0x08
};
inline NumericType operator |(NumericType a, NumericType b)
{
return static_cast<NumericType>(static_cast<int>(a) | static_cast<int>(b));
}
inline NumericType operator &(NumericType a, NumericType b)
{
return static_cast<NumericType>(static_cast<int>(a) & static_cast<int>(b));
}
inline NumericType& operator |=(NumericType& a, NumericType b)
{
return a= a |b;
}
int main() {
// your code goes here
NumericType a=NumericType::PadWithZero;
a|=NumericType::NegativeSign;
cout << static_cast<int>(a) ;
return 0;
}
print 3.
Solution 2
NumericType operator |= (NumericType &a, NumericType b) {
unsigned ai = static_cast<unsigned>(a);
unsigned bi = static_cast<unsigned>(b);
ai |= bi;
return a = static_cast<NumericType>(ai);
}
However, you may still consider defining a class for your collection of enum
bits:
class NumericTypeFlags {
unsigned flags_;
public:
NumericTypeFlags () : flags_(0) {}
NumericTypeFlags (NumericType t) : flags_(static_cast<unsigned>(t)) {}
//...define your "bitwise" test/set operations
};
Then, change your |
and &
operators to return NumericTypeFlags
instead.
Solution 3
I got sick of all the boilerplate with enum arithmetic, and moved to idioms more like this:
struct NumericType {
typedef uint32_t type;
enum : type {
None = 0,
PadWithZero = 0x01,
NegativeSign = 0x02,
PositiveSign = 0x04,
SpacePrefix = 0x08
};
};
This way I can still pass NumericType::type
arguments for clarity, but I sacrifice type safety.
I considered making a generic template class to use in place of uint32_t
which would provide one copy of the arithmetic overloads, but apparently I'm not allowed to derive an enum from a class, so whatever (thanks C++!).
Solution 4
After reading this question, I have overloaded the bitwise operators valid for all enums.
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>>>
inline T operator~(const T& value)
{
return static_cast<T>(~static_cast<int>(value));
}
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>>>
inline T operator|(const T& left, const T& right)
{
return static_cast<T>(static_cast<int>(left) | static_cast<int>(right));
}
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>>>
inline T& operator|=(T& left, const T& right)
{
return left = left | right;
}
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>>>
inline T operator&(const T& left, const T& right)
{
return static_cast<T>(static_cast<int>(left) & static_cast<int>(right));
}
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>>>
inline T& operator&=(T& left, const T& right)
{
return left = left & right;
}
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>>>
inline T operator^(const T& left, const T& right)
{
return static_cast<T>(static_cast<int>(left) ^ static_cast<int>(right));
}
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>>>
inline T& operator^=(T& left, const T& right)
{
return left = left ^ right;
}
Solution 5
Why strongly typed? Because my books say it is good practice.
Then your books are not talking about your use case. Unscoped enumerations are fine for flag types.
enum NumericType : int
{
None = 0,
PadWithZero = 0x01,
NegativeSign = 0x02,
PositiveSign = 0x04,
SpacePrefix = 0x08
};
Daniel A.A. Pelsmaeker
When I was five years old, I wanted to learn how to read. Afraid that I might get bored in class if they'd allow that, my father decided to teach me how to play chess and my mother showed me how to get our Commodore 64 to repeat my name over and over. 10 PRINT "DANIEL" 20 GOTO 10 I was much more interested in the Commodore than in chess and from Commodore Basic through QBasic, Visual Basic I worked my way up to C#. This was all self-taught until I decided to follow an education in Computer Science after getting my Building Sciences degree. I have a bachelor's degree in Building Sciences (BBE) and master's degree in Computer Science (MSc), and I am currently working on getting my PhD.
Updated on February 14, 2022Comments
-
Daniel A.A. Pelsmaeker about 2 years
How can I overload the
|=
operator on a strongly typed (scoped)enum
(in C++11, GCC)?I want to test, set and clear bits on strongly typed enums. Why strongly typed? Because my books say it is good practice. But this means I have to
static_cast<int>
everywhere. To prevent this, I overload the|
and&
operators, but I can't figure out how to overload the|=
operator on an enum. For a class you'd simply put the operator definition in the class, but for enums that doesn't seem to work syntactically.This is what I have so far:
enum class NumericType { None = 0, PadWithZero = 0x01, NegativeSign = 0x02, PositiveSign = 0x04, SpacePrefix = 0x08 }; inline NumericType operator |(NumericType a, NumericType b) { return static_cast<NumericType>(static_cast<int>(a) | static_cast<int>(b)); } inline NumericType operator &(NumericType a, NumericType b) { return static_cast<NumericType>(static_cast<int>(a) & static_cast<int>(b)); }
The reason I do this: this is the way it works in strongly-typed C#: an enum there is just a struct with a field of its underlying type, and a bunch of constants defined on it. But it can have any integer value that fits in the enum's hidden field.
And it seems that C++ enums work in the exact same way. In both languages casts are required to go from enum to int or vice versa. However, in C# the bitwise operators are overloaded by default, and in C++ they aren't.
-
Daniel A.A. Pelsmaeker about 11 yearsThe reason I do this: this is the way it works in strongly-typed C#: an enum there is just a struct with a field of its underlying type, and a bunch of constants defined on it. But it can have any integer value that fits in the enum's hidden field. And it seems that C++ enums work in the exact same way. In both languages casts are required to go from enum to int or vice versa. However, in C# the bitwise operators are overloaded by default, and in C++ they aren't. By the way...
typedef enum { } Flag
is not the C++11 syntax for enums:enum class Flag { }
. -
paddy about 11 yearsJust because enums appear to work in that way, it doesn't make it right to abuse the underlying type. Strong typing exists specifically to stop you from doing this.
-
Daniel A.A. Pelsmaeker about 11 yearsYou obviously haven't programmed in C#. :P There they don't call it abuse, Microsoft recommends it. I like the idea of sticking with the enum type, even for bit flags, instead of generalizing everything to int. C++ does that too much already.
-
Pete Becker about 11 years@paddy - this kind of thing is quite common. The values that an enumerated type can represent are not restricted to the named enumerators; they can be any value that fits in the enum's bits (loosely speaking). Having to provide names for all the possible combinations here would be at best tedious.
-
ereOn about 11 years@PeteBecker: Being common doesn't necessarily make it right. Sure people have been doing that for years or perhaps even decades. But to me, combining two elements of a same set in such a way should result in an object type that is designed to store combinations, not in the originating type itself.
-
Pete Becker about 11 years@ereOn - sure, if you think of an enumerated type as a set, you get constraints that aren't part of the properties of an enumerated type. You can, of course, restrict your use of enumerated types to match that restrictive model. That doesn't mean that people who use their full capabilities are abusing them. The standard was carefully written to allow exactly this sort of use.
-
paddy about 11 yearsThis was probably always destined to solicit debate. The way I see it, the semantics of an enumeration is to provide distinct values which are the only possible values for that type. I still hold that, while it's totally legit to declare the individual bits as an enumeration, it's semantically incorrect to use the same type to store a combination of those bits. When you break semantics, you go against strong typing. So you have to make a choice between the two. You cannot combine broken semantics with strong typing.
-
Daniel A.A. Pelsmaeker about 11 yearsYour class has a hidden inner field, just like an enum. But it doesn't have its constants, so as soon as you encounter a method that expects a
NumericTypeFlags
you get no help from your development environment whatsoever. -
jxh about 11 years@Virtlink: It provides better type safety than a plain
unsigned
. It has better semantics than creating aNumericType
with a value not in theenum
. The implementation of the interface can make sure that onlyNumericType
compatible arguments are used for testing and setting. In short, the help goes to the user of the class, at the cost of some work from the implementor to make it helpful to the user. -
MSalters about 9 years@paddy: While you're entitled to your opinion, the C++ committee explicitly and intentionally extended the range of enums to cover all binary combinations yet left out a automatic
operator|
. The logic here is that you should be able to provide anoperator|
if and only if it makes sense, but you shouldn't need to spell out one million names for 20 combinations of flags. -
underscore_d over 5 yearsYou should (a) return a reference to the modified object and (b) always cast to the
std::underlying_type
of theenum
, rather than assuming some fixed type will always suffice for all values that might be added or formed via bitwise. -
jxh over 5 years@underscore_d: I agree with you. The answer I provided was not directed at C++11, and properly converting the value to the appropriate unsigned underlying type for the bitwise operation would complicate the essence of the answer. A container would be a better way to capture flags, anyway. The OP should consider using
bitset
. -
Louis Strous over 5 yearsIf the enum is defined inside a class, and if you want to declare the operators inside the class as well, then declare them as "friend", otherwise the compiler complains that the operators have too many arguments -- It then thinks they apply to the class and not the enum. Or declare the operators outside of the class, but then you must qualify the enum name with the class name. If enum E is defined inside class C, then operator| for the enum should be declared as "inline C::E operator|(C::E a, C::E b)" outside the class, or "friend E operator|(E a, E b)" inside the class.
-
Justin Time - Reinstate Monica over 4 yearsFor reference,
[dcl.enum/8]
specifies that an enum with a fixed underlying type can hold all values allowed by its underlying type, even if they're not explicit enumerators. If the underlying type isn't fixed, it instead can hold all values allowed by the smallest bit-field that can store all defined enumerators. The usage of the term "bit-field" suggests that this is explicitly intended to guarantee that using an enum as a bitfield type is well-defined and perfectly compliant. -
Bryce Wagner about 3 yearsThere's 2 types of enums: ones where you can combine and ones where you can't. In C# they are differentiated by the [Flags] attribute (although it doesn't enforce that to use | and &, maybe it should). In C++ they're differentiated by whether you have explicitly defined the & and | operators.
-
LRDPRDX almost 2 yearsWhy not to use the
std::underlying_type<T>::type
instead ofint
? Generalize everything !