How to overload |= operator on scoped enum?
Asked Answered
O

7

39

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.

Often answered 8/4, 2013 at 21:34 Comment(7)
I'm not sure this makes sense. The individual bits are enumerated, but PadWithZero | NegativeSign = 0x03 which is not a valid enumerated constant.Bandwidth
@Bandwidth Yeah it's just an example based on a C++11 version of a sequence of NUMERICTYPE_ defines found in Linux 0.1 used to implement printf. Does the result have to be a member of the original enumeration? I come from a C# background and expected scoped enums to behave like those in C#.Often
I'm saying your enumerated type is not closed under |, so it doesn't make sense to coerce it into an (illegal value of) that type. Enumerate the flag constant values, but let a combination of flags be an int.Bandwidth
@Bandwidth - the value is not illegal. Any value that fits in the bits is okay. That kind of bit mask is done all the time, and there's nothing wrong with it.Midshipmite
I'd say a combination of NumericType is a set, which is a different type than NumericType. Why don't you just create a NumericTypeSet class or something ? It would probably make much more sense semanticaly speaking.Alack
@PeteBecker fair enough - I've done the same thing myself before, but it feels odd with strongly-typed enums.Bandwidth
@Bandwidth how is 0x03 illegal? It isn't one of the listed types, but I'm not aware of a restriction of enum class that makes 0x03 undefined behavior when stored in that enum class...Aquacade
C
50
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.

Caseinogen answered 8/4, 2013 at 21:39 Comment(3)
If 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.Grani
@Caseinogen What about NumericType a=NumericType::NegativeSign; a|= NumericType::SpacePrefix;? It should not work indeed. 10 is not a valid value.Galen
Yes.. this answer deserve some more context/explanation. I don't see why a negative sing can not be prefixed with a space if that is what was set by design.Caseinogen
E
3

This seems to work for me:

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.

Expertism answered 8/4, 2013 at 22:6 Comment(4)
Your 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.Often
@Virtlink: It provides better type safety than a plain unsigned. It has better semantics than creating a NumericType with a value not in the enum. The implementation of the interface can make sure that only NumericType 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.Expertism
You should (a) return a reference to the modified object and (b) always cast to the std::underlying_type of the enum, rather than assuming some fixed type will always suffice for all values that might be added or formed via bitwise.Frankfrankalmoign
@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.Expertism
V
3

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;
}
Vraisemblance answered 14/2, 2022 at 3:58 Comment(6)
Why not to use the std::underlying_type<T>::type instead of int ? Generalize everything !Pending
In C++23 we can also use std::to_underlying instead of (most) static_cast<> callsLawerencelawes
Pay attention std::is_enum is true for both non scoped and scoped enums. The correct is to use std::is_scoped_enum .Luis
Wow! This reverts enum to acting exactly like an int..Agrigento
@Luis That's a C++23 thingAgrigento
@Luis actually it's available in magic_enumAgrigento
E
2

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++!).

Exorbitant answered 18/1, 2021 at 23:46 Comment(0)
R
0

By combining distinct values to make new, undefined values, you are totally contradicting the strong-typing paradigm.

It looks like you are setting individual flag bits that are completely independent. In this case, it does not make sense to combine your bits into a datatype where such a combination yields an undefined value.

You should decide on the size of your flag data (char, short, long, long long) and roll with it. You can, however, use specific types to test, set and clear flags:

typedef enum
{
    PadWithZero             = 0x01,
    NegativeSign            = 0x02,
    PositiveSign            = 0x04,
    SpacePrefix             = 0x08
} Flag;

typedef short Flags;

void SetFlag( Flags & flags, Flag f )
{
    flags |= static_cast<Flags>(f);
}

void ClearFlag( Flags & flags, Flag f )
{
    flags &= ~static_cast<Flags>(f);
}

bool TestFlag( const Flags flags, Flag f )
{
    return (flags & static_cast<Flags>)(f)) == static_cast<Flags>(f);
}

This is very basic, and is fine when each flag is only a single bit. For masked flags, it's a bit more complex. There are ways to encapsulate bit flags into a strongly-typed class, but it really has to be worth it. In your case, I'm not convinced that it is.

Rimose answered 8/4, 2013 at 21:57 Comment(10)
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. By the way... typedef enum { } Flag is not the C++11 syntax for enums: enum class Flag { }.Often
Just 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.Rimose
You 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.Often
@Rimose - 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.Midshipmite
@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.Alack
@Alack - 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.Midshipmite
This 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.Rimose
@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 an operator| if and only if it makes sense, but you shouldn't need to spell out one million names for 20 combinations of flags.Cox
For 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.Cordate
There'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.Lepine
H
0

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
};
Hibbard answered 23/3, 2020 at 13:22 Comment(0)
A
0

I'm very surprised that you joas' answer works so well. Basically it reverts C++ to treating enum like an int.

But if you only want bitwise ops defined for some enum by name (and not only those that happen to run into a header with that template in it!), you could just use a macro to define bitwise ops for certain select enum class

#define ENUM_BITWISE_OPS( ENUM )             \
inline ENUM operator|( ENUM a, ENUM b ) {    \
  return (ENUM)( (int&)a | (int&)b );        \
}                                            \
inline ENUM operator&( ENUM a, ENUM b ) {    \
  return (ENUM)( (int&)a & (int&)b );        \
}                                            \
inline ENUM& operator|=( ENUM& a, ENUM b ) { \
  return (ENUM&)((int&)a |= (int&)b);        \
}                                            \
inline ENUM& operator&=( ENUM& a, ENUM b ) { \
  return (ENUM&)((int&)a &= (int&)b);        \
}                                            \

Use:

enum MouseButton {
  Left   = 1 << 0,
  Middle = 1 << 1,
  Right  = 1 << 2,
};

ENUM_BITWISE_OPS( MouseButton )

It's probably a better idea to keep control over your enums and only selectively equip them with bitwise ops as required (there's a reason C++ designers didn't have all enums behave as an int by default!)

Agrigento answered 27/4, 2024 at 5:15 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.