Jump to content

  • Log In with Google      Sign In   
  • Create Account

Enum class : bitwise operations?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
18 replies to this topic

#1 Juliean   GDNet+   -  Reputation: 2607

Like
0Likes
Like

Posted 03 April 2013 - 09:22 AM

Hello,

 

I've recently converted all my enums to strongly typed enums, like this:

 

enum class WindowBorders : unsigned char {
    None            = 0x00,
    Left            = 0x01,
    Right            = 0x02,
    Bottom            = 0x04,
    LeftBottom        = 0x05,
    RightBottom        = 0x06,
};

 

Now these work as flags, and previously I could have been checking if a variable contained one of those flags by coding:

 

unsigned char m_cBorder = LeftBottom;
if( (m_cBorder & Left) == Left )
//left border was clicked

Now this won't work anymore, I have to treat LeftBottom and RightBottom as seperate cases, while they essentially are just an addition of bottom and eigther left or right border, and also work that way. That causes a lot of unecessary code for my window object. The (german) compiler gives me somemessage about "&" only working for enums without a range restriction (?). Is this really impossible to achieve or is there some way to still do this?


Edited by The King2, 03 April 2013 - 09:23 AM.


Sponsor:

#2 phil_t   Crossbones+   -  Reputation: 3915

Like
0Likes
Like

Posted 03 April 2013 - 09:33 AM

You can define a | and & operator for your enum (static_cast'ing them to ints then back to the enum type after applying the operation). To avoid re-typing the same code for each different enum type, you can put it in a macro or something.



#3 Vortez   Crossbones+   -  Reputation: 2698

Like
0Likes
Like

Posted 03 April 2013 - 09:40 AM

Well it should work, did you tried debugging to see if m_cBorder has the right bits set? Im pretty sure the problem isn't in the code posted above.


Edited by jbadams, 04 April 2013 - 05:31 AM.
Restored post contents from history.


#4 Juliean   GDNet+   -  Reputation: 2607

Like
0Likes
Like

Posted 03 April 2013 - 09:51 AM

Well it should work, did you tried debugging to see if m_cBorder has the right bits set? Im pretty sure the problem isn't in the code posted above.

 

The code included above does work, having just "enum WindowBorders". That is the code that will fail:

 

WindowBorders m_cBorder = WindowBorders::LeftBottom;
if( m_cBorder & WindowBorders::Left) == WindowBorders::Left) //won't compile here

It simply won't compile, so there is no need/option for debugging.

 

You can define a | and & operator for your enum (static_cast'ing them to ints then back to the enum type after applying the operation). To avoid re-typing the same code for each different enum type, you can put it in a macro or something.

 

Since strongly typed enums can't have an operator overloaded, I assume you generally suggest eigther writing a macro or templated global function? How costly are static_casts anyway? Because there could be potentially a lot of checks throughout my code. Given this setting I'd somehow doubt the benefit of strongly typed enums over the overhead of "simulating" a bitwise operator... what would you rather choose?



#5 Vortez   Crossbones+   -  Reputation: 2698

Like
-3Likes
Like

Posted 03 April 2013 - 10:02 AM

Well you could solve the issue by using #define's instead

 

#define WINBORDER_NONE 0x00

... ect

 

Why would you need strongly typed enums? Using your old code will work regardless of the type used (int, short, char, byte ect), no need to overcomplicate things rolleyes.gif


Edited by jbadams, 04 April 2013 - 06:01 AM.
Restored post contents from history.


#6 Juliean   GDNet+   -  Reputation: 2607

Like
1Likes
Like

Posted 03 April 2013 - 10:41 AM

I'm not a big fan of #defines, as far as there are better alternatives really. It would work for normal enums so thats not the problem. I choose to go with strongly typed enums for multiple reasons:

 

- It makes code much more clear. Consider this:

 

void Cursor::ChangeState(CursorState newState)
{
	m_state = newState;
	switch(m_state)
	{
	case CursorState::IDLE:
		m_sFileName = L"CursorIdle.png";
		m_offsetX = 0;
		m_offsetY = 0;
		break;
	case CursorState::DRAG_V:
		m_sFileName = L"CursorDragV.png";
		m_offsetX = 16;
		m_offsetY = 16;
		break;
	case CursorState::DRAG_H:
		m_sFileName = L"CursorDragH.png";
		m_offsetX = 16;
		m_offsetY = 16;
		break;
	case CursorState::DRAG_LD:
		m_sFileName = L"CursorDragLD.png";
		m_offsetX = 16;
		m_offsetY = 16;
		break;
	case CursorState::DRAG_RD:
		m_sFileName = L"CursorDragRD.png";
		m_offsetX = 16;
		m_offsetY = 16;
		break;
	}
}

over this:

void Cursor::ChangeState(UINT newState)
{
	m_state = newState;
	switch(m_state)
	{
	case IDLE:
		m_sFileName = L"CursorIdle.png";
		m_offsetX = 0;
		m_offsetY = 0;
		break;
	case DRAG_V:
		m_sFileName = L"CursorDragV.png";
		m_offsetX = 16;
		m_offsetY = 16;
		break;
	case DRAG_H:
		m_sFileName = L"CursorDragH.png";
		m_offsetX = 16;
		m_offsetY = 16;
		break;
	case DRAG_LD:
		m_sFileName = L"CursorDragLD.png";
		m_offsetX = 16;
		m_offsetY = 16;
		break;
	case DRAG_RD:
		m_sFileName = L"CursorDragRD.png";
		m_offsetX = 16;
		m_offsetY = 16;
		break;
	}
}

Its not something that absolutely needs to be, but I think in the first code it everything is much clearer, extending to how the m_state variable is declared (CursorState instead of UINT) etc...

 

- I won't possibly ever run in a name clash for these enums.

 

- There is no possibility to asign a wrong value. Thing is, that like you said, the old code works regardeles of the actual type used, I don't like that very much. I can pass in My NoResize - flag from the WindowStyleFlags to the CursorState, which isn't possible with strongly typed enums.

 

Over all I just tend to prefern them after I read about them some while ago, I just decided to implement them tody. Surely the old code works, and I wouldn't need them, the "enum class" makes things just more clean, in my opinion, and my goal after getting code to work is now to polish it up... except for that bitwise-thingy, that is :/


Edited by The King2, 03 April 2013 - 10:41 AM.


#7 phil_t   Crossbones+   -  Reputation: 3915

Like
1Likes
Like

Posted 03 April 2013 - 11:55 AM

Since strongly typed enums can't have an operator overloaded, I assume you generally suggest eigther writing a macro or templated global function? How costly are static_casts anyway? Because there could be potentially a lot of checks throughout my code. Given this setting I'd somehow doubt the benefit of strongly typed enums over the overhead of "simulating" a bitwise operator... what would you rather choose?

 

There is an example of what I meant here, a few answers down:

http://stackoverflow.com/questions/1448396/how-to-use-enums-as-flags-in-c

 

I'm pretty sure static_cast from an int to enum (or vice versa) would just be compiled away, so it should be free.



#8 Juliean   GDNet+   -  Reputation: 2607

Like
0Likes
Like

Posted 03 April 2013 - 12:17 PM

Ah, I see, that worked. Thanks! A lot better again, thankfully. Now I only gotta find a way to automatice this for all enums and, aditionally possibly hide away this:

(border & WindowBorders::Bottom) == WindowBorders::Bottom

And other repetitive bit-checks into some sort of helper functions. Should be easiely doable, though.



#9 irreversible   Crossbones+   -  Reputation: 1346

Like
1Likes
Like

Posted 03 April 2013 - 11:18 PM

Here's what I'm doing to somewhat "automate" the process. You could write a macro to further cut down code.

 

enum ENUM_DrawElements : int {
GD_DRAW_ELEMENT_NONE = 0,
GD_DRAW_ELEMENT_STATIC_GEOMETRY = 1,
GD_DRAW_ELEMENT_DYNAMIC_GEOMETRY = 2,
GD_DRAW_ELEMENT_ENTITIES = 4,
GD_DRAW_ELEMENT_EDITORFUNCTIONS = 8,
//...
};


static inline ENUM_DrawElements operator|(ENUM_DrawElements a, ENUM_DrawElements b) { return static_cast<ENUM_DrawElements>(static_cast<int>(a) | static_cast<int>(b)); }

 

 

You can now use bitwise or on members of ENUM_DrawElements: GD_DRAW_ELEMENT_STATIC_GEOMETRY | GD_DRAW_ELEMENT_ENTITIES | ...
 
NOTE: you do have to mind possible precision issues, though - casting to int is limiting the precision to 32 bits.


#10 Álvaro   Crossbones+   -  Reputation: 13311

Like
3Likes
Like

Posted 04 April 2013 - 02:45 AM

How is casting to int a problem if the enum is defined to use int as the underlying type?

#11 Juliean   GDNet+   -  Reputation: 2607

Like
0Likes
Like

Posted 04 April 2013 - 02:58 AM

Here's what I'm doing to somewhat "automate" the process. You could write a macro to further cut down code.

 

Yup, thats how I've got it, too. As Álvaro pointed out correctly, casting from/to the obvious data type shouldn't be the problem, and since I trust phil_t that casting should be almost free, there is really no issue with that method whatsover.



#12 BitMaster   Crossbones+   -  Reputation: 4083

Like
0Likes
Like

Posted 04 April 2013 - 04:34 AM

How is casting to int a problem if the enum is defined to use int as the underlying type?

To make things completely tight one could use (not tested but documentation suggests it should work):


static inline ENUM_DrawElements operator | (ENUM_DrawElements a, ENUM_DrawElements b) 
{ 
   return static_cast<ENUM_DrawElements>(
      static_cast<std::underlying_type<ENUM_DrawElements>::type>(a) | 
      static_cast<std::underlying_type<ENUM_DrawElements>::type>(b)); 
}
A bit overkill in this particular scenario, but there are cases where types have to be changed during development. Besides, it's nice to know there is something to get the base type in the general case...

Edited by BitMaster, 04 April 2013 - 05:21 AM.


#13 irreversible   Crossbones+   -  Reputation: 1346

Like
0Likes
Like

Posted 04 April 2013 - 07:18 AM

How is casting to int a problem if the enum is defined to use int as the underlying type?

 

What if the underlying type is not an int?


Edited by irreversible, 04 April 2013 - 07:19 AM.


#14 BitMaster   Crossbones+   -  Reputation: 4083

Like
0Likes
Like

Posted 04 April 2013 - 07:24 AM

Well, in your own example you definitely fixed the type to int. If the type is not known or is likely to change there is still my suggestion.

Edited by BitMaster, 04 April 2013 - 07:25 AM.


#15 irreversible   Crossbones+   -  Reputation: 1346

Like
0Likes
Like

Posted 04 April 2013 - 07:27 AM

Well, in your own example you definitely fixed the type to int.

 

And what does this little side note have to do with my copy-pasted code?

Seriously though - it was just poor wording on my part. I'm glad it was picked up, but the interpretation of its meaning is misplaced here.



#16 Álvaro   Crossbones+   -  Reputation: 13311

Like
1Likes
Like

Posted 04 April 2013 - 09:11 AM


How is casting to int a problem if the enum is defined to use int as the underlying type?

 
What if the underlying type is not an int?


I am confused. My post was a comment on the post right above it, which showed some code which specified int as the underlying type and then used a cast to int.

I do like BitMaster's code, though, despite how verbose it is. Instead of overloading any operators, I would prefer to provide a function to test if some bits are active. That function can be a template so it works for all enum types automatically (a feature that was requested by The King2), and then you have to add `typename', making the code even uglier.
#include <iostream>
#include <type_traits>

enum class WindowBorders
: unsigned char {
  None        = 0x00,
  Left        = 0x01,
  Right       = 0x02,
  Bottom      = 0x04,
  LeftBottom  = 0x05,
  RightBottom = 0x06,
};

// Returns whether the bits set in x contain the bits set in y
template <typename T>
bool contains_bits(T x, T y) {
  return (static_cast<typename std::underlying_type<T>::type>(x)
          & static_cast<typename std::underlying_type<T>::type>(y))
    == static_cast<typename std::underlying_type<T>::type>(y);
}

int main() {
  WindowBorders wb = WindowBorders::LeftBottom;
  if (contains_bits(wb, WindowBorders::Left))
    std::cout << "Yay!\n";
}

Edited by Álvaro, 04 April 2013 - 09:42 AM.


#17 Juliean   GDNet+   -  Reputation: 2607

Like
0Likes
Like

Posted 04 April 2013 - 10:42 AM


I do like BitMaster's code, though, despite how verbose it is. Instead of overloading any operators, I would prefer to provide a function to test if some bits are active. That function can be a template so it works for all enum types automatically (a feature that was requested by The King2), and then you have to add `typename', making the code even uglier.

 

Since I've already writte a set of bithelper-classes, this would fair quite well, actually. But is there any chance to use template specialisation here so I don't need a seperate function? It's just a minor annoyance, though...

 

namespace acl
{
	namespace bit
	{
		template<typename b>
		bool contains(b left, b right)
		{
			return (left & right) == right;
		}

		template <typename T>//can't we use template specialistion here so we can name this "contains" too?
		bool enum_contains(T x, T y) {
		  return (static_cast<typename std::underlying_type<T>::type>(x)
				  & static_cast<typename std::underlying_type<T>::type>(y))
			== static_cast<typename std::underlying_type<T>::type>(y);
		}
	}
}


#18 Álvaro   Crossbones+   -  Reputation: 13311

Like
1Likes
Like

Posted 04 April 2013 - 11:10 AM

But is there any chance to use template specialisation here so I don't need a seperate function?

Of course there is, but it gets even uglier...
template <typename T>
bool contains_bits(T x, T y, std::true_type) {
  return (static_cast<typename std::underlying_type<T>::type>(x)
          & static_cast<typename std::underlying_type<T>::type>(y))
    == static_cast<typename std::underlying_type<T>::type>(y);
}

template <typename T>
bool contains_bits(T x, T y, std::false_type) {
  return (x & y) == y;
}

template <typename T>
bool contains_bits(T x, T y) {
  return contains_bits(x, y, std::integral_constant<bool, std::is_enum<T>::value>());
}

Edited by Álvaro, 04 April 2013 - 11:11 AM.


#19 Juliean   GDNet+   -  Reputation: 2607

Like
0Likes
Like

Posted 04 April 2013 - 11:44 AM

Of course there is, but it gets even uglier...

 

Gee wiz, thanks! I won't say I like it when code is ugly but at least I've got everything hid away in my beatiful convienient namespace and seperate bit-helper file I'm going to touch every decade or so, that it doesn't even matter.






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS