4

I have this problem in C++: can I typedef a bitfield whose values come from an enum?

Code will be more explainatory:

typedef {
 AUDIO    = 0x01,
 VIDEO    = 0x02,
 SUBTITLE = 0x04,
 DATA     = 0x08,
 GUARD,
 ALL      = 0xFF
} my_enum_e;

// I'd like to replace 'unsigned int' by 'my_enum_e' or similar
int myFunction( unsigned int mask ) 
{
  // code
}

// called like this:
myFunction( AUDIO|VIDEO|DATA );

In the prototype of the function, I'd like to use my_enum_e as an input value type, so that when exploring the code, you can immediately know which values you're supposed to put in there.

Now, changing the prototype to

int myFunction( my_enum_e mask );

makes the compiler whine about a cast error. I cant fix it by casting the function calls like this:

int myFunction( my_enum_e mask )
{
    // code
}

myFunction( (my_enum_e)(VIDEO|AUDIO|DATA) );

But I find this quite horrible, and I'm not even sure it is legal (could it truncate the value??).
Do you have a solution?

Gui13
  • 12,993
  • 17
  • 57
  • 104
  • 2
    Did you omit the `enum` keyword after `typedef` intentionally? – bitmask Jul 23 '12 at 13:58
  • Kinda, yeah, I don't know if it's dangerous, though. All my typedefs use "unnamed" struct/enum. Upvote for relevant username. – Gui13 Jul 23 '12 at 13:59
  • 1
    @Gui13, In C++, you're better off `enum E {};` than `typedef enum {} E;` anyway. – chris Jul 23 '12 at 14:05
  • Is it my old C luggage knocking on the door again? :) – Gui13 Jul 23 '12 at 14:06
  • Your enum for GUARD will be 0x09 at the moment - is that what you intended? – noelicus Jul 23 '12 at 14:09
  • Kinda too, even though I do not use it in my code. I usually use this for automatically incremented enums, I figured I'd keep it there too. – Gui13 Jul 23 '12 at 14:18
  • 1
    I just realized, if your function takes an enum, and you're combining values, you're going to need something in that enum for every combination, otherwise it's UB. – chris Jul 23 '12 at 14:18
  • I recommend against replacing 'unsigned int' by 'my_enum_e' in function prototype. Masking integer values gets you out of enum range and casting it back is against the meaning of "enum". I'm also pretty sure any static code analysis will point to this cast as "suspicious". – Lyth Jul 23 '12 at 14:26
  • @Lyth: yeah, that's why I doubt it's legal to re-cast to the original enum. The thing is that in an API, having the type passed as an explicit enum is WAY better than having it as an unnamed unsigned int. This way, in IDEs, you can CTRL clik to go directly to the enum which you should use. – Gui13 Jul 23 '12 at 14:33

5 Answers5

4

Add an explicit overload for | and possibly other operators.

my_enum_e operator|(my_enum_e a, my_enum_e b) 
    { return my_enum_e(unsigned(a)|unsigned(b)); }

One can write a macro that defines all needed operators for a given bitmask type.

#define BITMASK_OPERATORS(T)   T operator|(T a, T b) { return T(unsigned(a)|unsigned(b)); } \
                                 T operator^(T a, T b) ...
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
2

How about implementing a special function to deal with this:

template <typename Enum>
Enum bitField(unsigned bits) {
  return static_cast<Enum>(bits);
}

It adds expressiveness to what you're doing:

myFunction(bitField<my_enum_e>(VIDEO|AUDIO|DATA));

If you want more sophistication, you can do this:

template <typename Enum>
struct BitField {
  Enum value;
  BitField(Enum value) : value(value) {}
  BitField operator|(Enum more) {
    return BitField(value | more);
  }
  BitField operator&(Enum more) {
    return BitField(value & more);
  }
  BitField operator~() {
    return BitField(~value);
  }
  operator Enum() {
    return value;
  }
}

Which will allow you to write

myFunction(BitField<my_enum_e>(VIDEO) | AUDIO | DATA);
bitmask
  • 32,434
  • 14
  • 99
  • 159
0

As you performing a bitwise operation the compiler consider VIDEO|AUDIO|DATA as an integer value, so you have to cast it with *my_enum_e*.

A.G.
  • 1,279
  • 1
  • 11
  • 28
0

I recommend that you look at this thread: Which Typesafe Enum in C++ Are You Using? - The mentioned implemention in the [Boost Vault] (filename enum_rev4.6.zip). 1 provides also the possibility to declare a BOOST_BITFIELD :

BOOST_BITFIELD(my_enum_e, 
  (AUDIO)    (0x01)
  (VIDEO)    (0x02)
  (SUBTITLE) (0x04)
  (DATA)     (0x08)
);

you can then declare your function:

int myFunction( const my_enum_e & in_enum ) 
{
  if ( in_enum[AUDIO] ) ...
}

The usage is then

void callerFunction()
{
  my_enum_e mask;
  mask.set(my_enum_e::AUDIO);
  mask.set(my_enum_e::VIDEO | my_enum_e::SUBTITLE );
  cout << mask << " = " << hex << mask.value() << endl; // will print: AUDIO|VIDEO|SUBTITLE = 0x7
  myFunction( mask );

}

The documentation of the api is not that good. But the packages comes with a test that shows several usages.

Community
  • 1
  • 1
Carsten Greiner
  • 2,928
  • 2
  • 16
  • 20
-2

One way around it is to typedef int MY_FLAGS; and then #define all your values instead: #define AUDIO 0x01 etc. That way the compiler won't moan and you still get types in your function calls:

int myFunction( MY_FLAGS mask );

myFunction( VIDEO|AUDIO|DATA );
noelicus
  • 14,468
  • 3
  • 92
  • 111