From 8d0506412c32f08fcb277900a170da2988b5485a Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Mon, 18 Dec 2023 23:09:50 +0000 Subject: [PATCH] Strong typedef: Add mixin for integer with "delta" behaviour Subtracting returns a separate delta type e.g. for absolute times to durations --- src/core/strong_typedef_type.hpp | 86 ++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/core/strong_typedef_type.hpp b/src/core/strong_typedef_type.hpp index bb9df55a0e..c37bac26ef 100644 --- a/src/core/strong_typedef_type.hpp +++ b/src/core/strong_typedef_type.hpp @@ -167,6 +167,91 @@ namespace StrongType { }; }; + /** + * Mix-in which makes the new Typedef behave more like an integer. This means you can add and subtract from it. + * + * Operators like divide, multiply and module are explicitly denied, as that often makes little sense for the + * new type. If you want to do these actions on the new Typedef, you are better off first casting it to the + * base type. + * + * Subtracting the new Typedef from another new Typedef produces TDeltaType, instead of another new Typedef. + * e.g. subtracting an absolute time from another absolute time should produce a duration, not another absolute time. + * Adding a new Typedef to another new Typedef is not allowed. + * TDeltaType should be another StrongType::Typedef. + */ + template + struct IntegerDelta { + template + struct mixin { + friend constexpr TType &operator ++(TType &lhs) { lhs.value++; return lhs; } + friend constexpr TType &operator --(TType &lhs) { lhs.value--; return lhs; } + friend constexpr TType operator ++(TType &lhs, int) { TType res = lhs; lhs.value++; return res; } + friend constexpr TType operator --(TType &lhs, int) { TType res = lhs; lhs.value--; return res; } + + template::value>::type> + friend constexpr TType &operator +=(TType &lhs, const T &rhs) = delete; + template::value>::type> + friend constexpr TType &operator +(const TType &lhs, const T &rhs) = delete; + + friend constexpr TType &operator +=(TType &lhs, const TDeltaType &rhs) { lhs.value += rhs.value; return lhs; } + friend constexpr TType operator +(const TType &lhs, const TDeltaType &rhs) { return TType{ lhs.value + rhs.value }; } + friend constexpr TType operator +(const TDeltaType &lhs, const TType &rhs) { return TType{ lhs.value + rhs.value }; } + friend constexpr TType operator +(const TType &lhs, const TBaseType &rhs) { return TType{ lhs.value + rhs }; } + friend constexpr TType operator +(const TBaseType &lhs, const TType &rhs) { return TType{ lhs + rhs.value }; } + + template::value>::type> + friend constexpr TType &operator -=(TType &lhs, const T &rhs) = delete; + + friend constexpr TType &operator -=(TType &lhs, const TDeltaType &rhs) { lhs.value -= rhs.value; return lhs; } + friend constexpr TType &operator -=(TType &lhs, const TBaseType &rhs) { lhs.value -= rhs; return lhs; } + friend constexpr TDeltaType operator -(const TType &lhs, const TType &rhs) { return TDeltaType{ lhs.value - rhs.value }; } + friend constexpr TType operator -(const TType &lhs, const TBaseType &rhs) { return TType{ lhs.value - rhs }; } + + constexpr TDeltaType AsDelta() const { return TDeltaType{ static_cast(*this).value }; } + + /* For most new types, the rest of the operators make no sense. For example, + * what does it actually mean to multiply a Year with a value. Or to do a + * bitwise OR on a Date. Or to divide a TileIndex by 2. Conceptually, they + * don't really mean anything. So force the user to first cast it to the + * base type, so the operation no longer returns the new Typedef. */ + + constexpr TType &operator *=(const TType &rhs) = delete; + constexpr TType operator *(const TType &rhs) = delete; + constexpr TType operator *(const TBaseType &rhs) = delete; + + constexpr TType &operator /=(const TType &rhs) = delete; + constexpr TType operator /(const TType &rhs) = delete; + constexpr TType operator /(const TBaseType &rhs) = delete; + + constexpr TType &operator %=(const TType &rhs) = delete; + constexpr TType operator %(const TType &rhs) = delete; + constexpr TType operator %(const TBaseType &rhs) = delete; + + constexpr TType &operator &=(const TType &rhs) = delete; + constexpr TType operator &(const TType &rhs) = delete; + constexpr TType operator &(const TBaseType &rhs) = delete; + + constexpr TType &operator |=(const TType &rhs) = delete; + constexpr TType operator |(const TType &rhs) = delete; + constexpr TType operator |(const TBaseType &rhs) = delete; + + constexpr TType &operator ^=(const TType &rhs) = delete; + constexpr TType operator ^(const TType &rhs) = delete; + constexpr TType operator ^(const TBaseType &rhs) = delete; + + constexpr TType &operator <<=(const TType &rhs) = delete; + constexpr TType operator <<(const TType &rhs) = delete; + constexpr TType operator <<(const TBaseType &rhs) = delete; + + constexpr TType &operator >>=(const TType &rhs) = delete; + constexpr TType operator >>(const TType &rhs) = delete; + constexpr TType operator >>(const TBaseType &rhs) = delete; + + constexpr TType operator ~() = delete; + constexpr TType operator -() = delete; + }; + }; + /** * Mix-in which makes the new Typedef compatible with another type (which is not the base type). * @@ -226,6 +311,7 @@ namespace StrongType { friend struct Compare; friend struct Integer; friend struct IntegerScalable; + template friend struct IntegerDelta; template friend struct Compatible; /* GCC / MSVC don't pick up on the "friend struct" above, where CLang does.