Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type safe enum bit flags

I'm looking to use a set of bit flags for my current issue. These flags are (nicely) defined as part of an enum, however I understand that when you OR two values from an enum the return type of the OR operation has type int.

What I'm currently looking for is a solution which will allow the users of the bit mask to remain type safe, as such I have created the following overload for operator |

enum ENUM
{
    ONE     = 0x01,
    TWO     = 0x02,
    THREE   = 0x04,
    FOUR    = 0x08,
    FIVE    = 0x10,
    SIX     = 0x20
};

ENUM operator | ( ENUM lhs, ENUM rhs )
{
    // Cast to int first otherwise we'll just end up recursing
    return static_cast< ENUM >( static_cast< int >( lhs ) | static_cast< int >( rhs ) );
}

void enumTest( ENUM v )
{
}

int main( int argc, char **argv )
{
    // Valid calls to enumTest
    enumTest( ONE | TWO | FIVE );
    enumTest( TWO | THREE | FOUR | FIVE );
    enumTest( ONE | TWO | THREE | FOUR | FIVE | SIX );

    return 0;
}

Does this overload really provide type safety? Does casting an int containing values not defined in the enum cause undefined behaviour? Are there any caveats to be aware of?

like image 344
ctor Avatar asked Oct 09 '13 09:10

ctor


3 Answers

Does this overload really provide type safety?

In this case, yes. The valid range of values for the enumeration goes at least up to (but not necessarily including) the next largest power of two after the largest named enumerator, in order to allow it to be used for bitmasks like this. So any bitwise operation on two values will give a value representable by this type.

Does casting an int containing values not defined in the enum cause undefined behaviour?

No, as long as the values are representable by the enumeration, which they are here.

Are there any caveats to be aware of?

If you were doing operations such as arithmetic, which could take the value out of range, then you'd get an implementation-defined result, but not undefined behavoiur.

like image 196
Mike Seymour Avatar answered Nov 18 '22 01:11

Mike Seymour


If you think about type safety, it is better to use std::bitset

enum BITS { A, B, C, D }; 
std::bitset<4> bset, bset1;
bset.set(A); bset.set(C);
bset1[B] = 1;
assert(bset[A] == bset[C]);
assert(bset[A] != bset[B]);
assert(bset1 != bset);
like image 4
SergV Avatar answered Nov 17 '22 23:11

SergV


The values of your constants are not closed under OR. In other words, it's possible that the result of an OR of two ENUM constants will result in a value that is not an ENUM constant:

0x30 == FIVE | SIX;

The standard says that this is ok, an enumaration can have a value not equal to any of its enumarators (constants). Presumably it's to allow this type of usage.

In my opinion this is not type safe because if you were to look at the implementation of enumTest you have to be aware that the argument type is ENUM but it might have a value that's not an ENUM enumerator.

I think that if these are simply bit flags then do what the compiler wants you to: use an int for the combination of flags.

like image 3
Adam Avatar answered Nov 18 '22 00:11

Adam