简体   繁体   中英

How to overload |= operator on scoped enum?

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.

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.

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.

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

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

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM