3 ms·
> Note that we don't explicitly set [enum values] to be powers of two > return 1 << static_cast<underlying_type>(o); Relying on default enum values to and aut
by pdkl95 8y ago
> Note that we don't explicitly set [enum values] to be powers of two
> return 1 << static_cast<underlying_type>(o);
Relying on default enum values to and automagically converting these into power of two bit masks is very brittle, and can create unexpected bugs and interoperability problems. The mask values change if the enum changes, unused bits in the middle of the bitmask are hard to handle, and the bit order is assumed with the first item being the LSB. The last point is particularly relevant given the NES buttons example, because it uses a bit order opposite to how the controller data is usually read[1]. This article defines
#define BTN_A 0x01 /* 00000001 in binary */
#define BTN_B 0x02 /* 00000010 in binary */
/* ... */
#define BTN_RIGHT 0x80 /* 10000000 */
The standard controller[2] state is sent from a shift register in the controller one bit at a time. This is usually read by the program as 8 reads from the I/O port, and shifted into a button state bitmask. E.g. something like:
n = 8;
buttons = 0;
while(buttons--) {
buttons = buttons << 1;
buttons |= (CONTROLLER_1_IO_PORT & 0x01);
}
With the buttons being read in the order [A, B, ..., RIGHT], the recommended bitmask is:
BUTTON_A = 1 << 7
BUTTON_B = 1 << 6
/* ... */
BUTTON_RIGHT = 1 << 0
Obviously this is a minor mistake in a (probably untested) example. I just want to highlight how easily this type of "clever" - but brittle - coding style can introduce errors. Defining bit values (and other externally defined magic numbers) explicitly in e.g. a defined-to-be-canonical header file (which might be just those 8 #defines) is much safer and easier to debug/maintain. Then wrap those explicitly defined values into the C++ template for type safety or other fancy features.
[1] https://wiki.nesdev.com/w/index.php/Controller_Reading https://wiki.nesdev.com/w/index.php/Controller_Reading
[2] https://wiki.nesdev.com/w/index.php/Standard_controller https://wiki.nesdev.com/w/index.php/Standard_controller
- unwind 8y agoYou meant: while (n--)
- pubby 8y agoOh, the NES controller bit order is 100% arbitrary. There's no standard - it all depends on if the programmer decided to write a ROL or ROR instruction that day. But I agree with your point.
- theincredulousk 8y agoDefining bit values (and other externally defined magic numbers) explicitly in e.g. a defined-to-be-canonical header file (which might be just those 8 #defines) is much safer and easier to debug/maintain I'm all for modern C++, but this is correct, at least IME with embedded systems. You're overall better off sticking with C-style if working at an abstraction level where the interface itself uses bitmasks. At this point you're manipulating memory manually which is (a) C's wheelhouse and (b) where type safety becomes largely irrelevant. There is a lot to be said for using custom enum class implementations (e.g. http://www.drdobbs.com/when-enum-just-isnt-enough-enumeration-c/184403955 http://www.drdobbs.com/when-enum-just-isnt-enough-enumeratio...) but it isn't clear to me how this kind of thing benefits bitmasks when the goal is presumably absolute maximum efficiency.