30
votes

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.

5
I'm not sure this makes sense. The individual bits are enumerated, but PadWithZero | NegativeSign = 0x03 which is not a valid enumerated constant. - Useless
@Useless 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#. - Daniel A.A. Pelsmaeker
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. - Useless
@Useless - 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. - Pete Becker
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. - ereOn

5 Answers

38
votes
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.

3
votes

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.

1
votes

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
};
1
votes

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

0
votes

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.