How to overload |= operator on scoped enum?

23,064

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

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.

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
};
Share:
23,064
Daniel A.A. Pelsmaeker
Author by

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, 2022

Comments

  • Daniel A.A. Pelsmaeker
    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
    Daniel A.A. Pelsmaeker about 11 years
    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 { }.
  • paddy
    paddy about 11 years
    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.
  • Daniel A.A. Pelsmaeker
    Daniel A.A. Pelsmaeker about 11 years
    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.
  • Pete Becker
    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
    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
    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
    paddy about 11 years
    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.
  • Daniel A.A. Pelsmaeker
    Daniel A.A. Pelsmaeker about 11 years
    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.
  • jxh
    jxh about 11 years
    @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.
  • MSalters
    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 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.
  • underscore_d
    underscore_d over 5 years
    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.
  • jxh
    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
    Louis Strous over 5 years
    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.
  • Justin Time - Reinstate Monica
    Justin Time - Reinstate Monica over 4 years
    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.
  • Bryce Wagner
    Bryce Wagner about 3 years
    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.
  • LRDPRDX
    LRDPRDX almost 2 years
    Why not to use the std::underlying_type<T>::type instead of int ? Generalize everything !