0

I have this c++11 class which contains a variety of enums. These enums contain bitmasks which eventual all get combined together into an 8-bit values using bitwise operators.

It looks like this:

class SX1509
{
public:

  enum class DriverMode
  {
    LINEAR = 0b00000000,
    LOGARITHMIC = 0b10000000
  };

  enum class ClockSpeed
  {
    ULTRA_FAST = 0b00010000,
    EXTRA_FAST = 0b00100000,
    FAST = 0b00110000,
    MEDIUM = 0b01000000,
    SLOW = 0b01010000,
    EXTRA_SLOW = 0b01100000,
    ULTRA_SLOW = 0b01110000,
  };

  SX1509()
  {
    this->config(DriverMode::LOGARITHMIC, ClockSpeed::EXTRA_FAST)
  };

  void config(DriverMode driverMode, ClockSpeed clockSpeed) {
    uint8_t config = driverMode | clockSpeed;
    i2c->write(config);
  }

}

However, after changing all my enum definitions to enum class the compiler spit out this error which I do not understand

error: no match for 'operator|' (operand types are 'SX1509::DriverMode' and 'SX1509::ClockSpeed')

What is going on here?

scottc11
  • 716
  • 2
  • 7
  • 20
  • 1
    You code is not providing a `uint8_t operator|(DriverMode, ClockSpeed)` function. – Eljay Mar 30 '22 at 14:28
  • 1
    One of the effects of `enum class` is to disable the implicit conversion to the underlying type that `enum` permits. You can define your own `operator| (DriverMode, ClockSpeed)`. This would have the advantage of requiring you to have exactly one `DriverMode` and one `ClockSpeed`, because from the look of it I'm not sure that e.g. `ULTRA_FAST | ULTRA_SLOW` is meaningful but it's valid to the compiler if you're using `enum` instead of `enum class` – Nathan Pierson Mar 30 '22 at 14:28
  • You might also want `operator|(ClockSpeed, DriverMode)` if you want to be able to switch the order of the operands – Nathan Pierson Mar 30 '22 at 14:31
  • 1
    What's going on here is an `enum class` doing the job it was meant to do, the reason why it exists: type-safety. Unless you give your C++ compiler the permission to combine the `enum class`es using the `|` operator, by the virtue of defining one, your C++ compiler will refuse to do so, on its own. So, if you didn't mean to allow something like that your compiler will now complain about it, which is precisely what `enum class`es are for. If, for example, somewhere in the code there was a typo that tried to subtract them, that won't compile either, any more. – Sam Varshavchik Mar 30 '22 at 14:33
  • 1
    Possible duplicate (at least very related): [How to overload |= operator on scoped enum?](https://stackoverflow.com/q/15889414/10871073). I'm sure there are others, too. – Adrian Mole Mar 30 '22 at 14:36
  • If you don't want writing operator|() for all, you can try type casting as static_cast(DriverMode::LOGARITHMIC). But then why use enum class? Better just wrap the enum inside a namespace then. – vikram Mar 30 '22 at 14:36

1 Answers1

1

enum class enumerations do not get implicitly converted to integers, unlike regular enumerations.

I would do at least two things. First, explicitly indicate the underlying type of the enum class, e.g.:

  enum class DriverMode : uint8_t
  {
    LINEAR = 0b00000000,
    LOGARITHMIC = 0b10000000
  };

While this won't help with explicit conversion, it will help with wonkiness due to conversion from signed to unsigned.

Second, explicitly convert the enumeration values, e.g.,

uint8_t config = static_cast<uint8_t>(driverMode) | static_cast<uint8_t>(clockSpeed);

You may want to use a typedef alias for uint8_t, e.g.,

typedef uint8_t MyEnumUnderlyingType;

instead of using the raw uint8_t.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
jjramsey
  • 1,131
  • 7
  • 17
  • Curious, how / why would defining an enum type help? Why would I not want to use `uint8_t`? – scottc11 Mar 30 '22 at 14:56
  • @scottc11 -- you could also provide an overload: `driverMode operator|(driverMode lhs, driverMode rhs) { ... }` that does the conversion suggested in this answer. – Pete Becker Mar 30 '22 at 15:49
  • @PeteBecker I think this would be ideal, but wouldn't I have to re-write that operator overload every time I want to do this with enums? Because I do indeed need to do this all over my code base for other drivers and stuff. – scottc11 Mar 30 '22 at 15:54
  • @scottc11 -- yes, if you change to `enum class` because you want that level of type safety you have to pay for it. – Pete Becker Mar 30 '22 at 15:58
  • @scottc11 "why would defining an enum type help" The reason you may want to avoid using `uint8_t` directly is that you may want to change the underlying type later on. If you've consistently used `MyEnumUnderlyingType` (or whatever you prefer to call it) rather that `uint8_t`, then you only need to change the typedef of `MyEnumUnderlyingType` and recompile. – jjramsey Mar 30 '22 at 16:12