Normally, when overloading operators, the arithmetic operators (including bitwise operators) are defined in terms of the associated compound assignment operators (e.g., operator+
and operator|
typically rely on operator+=
and operator|=
).
class SomeClass {
SomeType data;
// ...
public:
SomeClass& operator+=(const SomeClass& other) {
data += other.data;
return *this;
}
friend SomeClass operator+(SomeClass l, const SomeClass& r) { return l += r; }
friend SomeClass& operator|=(SomeClass& l, SomeClass r) {
l.data |= r.data;
return l;
}
friend SomeClass operator|(SomeClass l, SomeClass r) { return l |= r; }
};
However, when it comes to enum
s (whether unscoped or enum class
), the logic is typically reversed, with the preferred solution often being to instead define compound assignment operators in terms of the arithmetic operators1.
using Underlying = uint8_t;
enum class SomeEnum : Underlying {
A_VALUE = 0b0000'0001,
B_VALUE = 0b0000'0010,
// ...
H_VALUE = 0b1000'0000,
CLEAR_ALL = 0,
SET_ALL = static_cast<uint8_t>(-1),
};
// Arithmetic first.
constexpr SomeEnum operator|(SomeEnum l, SomeEnum r) {
return static_cast<SomeEnum>(static_cast<Underlying>(l) | static_cast<Underlying>(r));
}
constexpr SomeEnum& operator|=(SomeEnum& l, SomeEnum r) { return l = l | r; }
// Compound assignment first.
constexpr SomeEnum operator+=(SomeEnum& l, SomeEnum r) {
Underlying ul = static_cast<Underlying>(l);
ul += static_cast<Underlying>(r);
return l = static_cast<SomeEnum>(ul);
}
constexpr SomeEnum operator+(SomeEnum l, SomeEnum r) { return l += r; }
1: While the question's answers provide examples of both defining operator|=
in terms of operator|
and defining operator|
in terms of operator|=
, with both answers being posted within half an hour of each other (and thus having nearly identical exposure), the former has significantly more upvotes. This suggests that it's the more preferred solution by far.
Now, I can see the reasoning for this approach: The code is noticeably cleaner if we flip things around and define the arithmetic operator first, especially if we also desire compile-time availability. However, it also goes against the pattern used nearly(?) everywhere else, which makes it seem a bit suspect IMO (mainly because it violates the principle of least astonishment).
In light of that, I can't help but wonder: Should we flip things around here? Is the benefit of simplicity worth breaking the unspoken guarantee that "a += b
is, in general, more efficient than a + b
and should be preferred if possible"2?
Put simply, if defining operators3 for an enumeration, should we define compound operators in terms of the associated arithmetic operators instead of the other way around, as seems to be the common recommendation?
2: See footnote #3 on first link.
3: Typically bitwise operators, but I wanted to speak generally.