Codechange: make TimerGameCalendar Date and Year types strongly typed (#10761)
This commit is contained in:
@@ -28,8 +28,8 @@ struct fmt::formatter<E, Char, std::enable_if_t<std::is_enum<E>::value>> : fmt::
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct fmt::formatter<T, Char, std::enable_if_t<std::is_base_of<StrongTypedefBase, T>::value>> : fmt::formatter<typename T::Type> {
|
||||
using underlying_type = typename T::Type;
|
||||
struct fmt::formatter<T, Char, std::enable_if_t<std::is_base_of<StrongTypedefBase, T>::value>> : fmt::formatter<typename T::BaseType> {
|
||||
using underlying_type = typename T::BaseType;
|
||||
using parent = typename fmt::formatter<underlying_type>;
|
||||
|
||||
constexpr fmt::format_parse_context::iterator parse(fmt::format_parse_context &ctx) {
|
||||
|
@@ -10,6 +10,7 @@
|
||||
#ifndef MATH_FUNC_HPP
|
||||
#define MATH_FUNC_HPP
|
||||
|
||||
#include "strong_typedef_type.hpp"
|
||||
|
||||
/**
|
||||
* Returns the absolute value of (scalar) variable.
|
||||
@@ -162,7 +163,7 @@ static inline uint ClampU(const uint a, const uint min, const uint max)
|
||||
* for the return type.
|
||||
* @see Clamp(int, int, int)
|
||||
*/
|
||||
template <typename To, typename From>
|
||||
template <typename To, typename From, std::enable_if_t<std::is_integral<From>::value, int> = 0>
|
||||
constexpr To ClampTo(From value)
|
||||
{
|
||||
static_assert(std::numeric_limits<To>::is_integer, "Do not clamp from non-integer values");
|
||||
@@ -213,6 +214,15 @@ constexpr To ClampTo(From value)
|
||||
return static_cast<To>(std::min<BiggerType>(value, std::numeric_limits<To>::max()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialization of ClampTo for #StrongType::Typedef.
|
||||
*/
|
||||
template <typename To, typename From, std::enable_if_t<std::is_base_of<StrongTypedefBase, From>::value, int> = 0>
|
||||
constexpr To ClampTo(From value)
|
||||
{
|
||||
return ClampTo<To>(static_cast<typename From::BaseType>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the (absolute) difference between two (scalar) variables
|
||||
*
|
||||
@@ -254,10 +264,14 @@ static inline bool IsInsideBS(const T x, const size_t base, const size_t size)
|
||||
* @param max The maximum of the interval
|
||||
* @see IsInsideBS()
|
||||
*/
|
||||
template <typename T>
|
||||
template <typename T, std::enable_if_t<std::disjunction_v<std::is_convertible<T, size_t>, std::is_base_of<StrongTypedefBase, T>>, int> = 0>
|
||||
static constexpr inline bool IsInsideMM(const T x, const size_t min, const size_t max) noexcept
|
||||
{
|
||||
return (size_t)(x - min) < (max - min);
|
||||
if constexpr (std::is_base_of_v<StrongTypedefBase, T>) {
|
||||
return (size_t)(static_cast<typename T::BaseType>(x) - min) < (max - min);
|
||||
} else {
|
||||
return (size_t)(x - min) < (max - min);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -10,74 +10,199 @@
|
||||
#ifndef STRONG_TYPEDEF_TYPE_HPP
|
||||
#define STRONG_TYPEDEF_TYPE_HPP
|
||||
|
||||
/** Non-templated base for #StrongTypedef for use with type trait queries. */
|
||||
#include "../3rdparty/fmt/format.h"
|
||||
|
||||
/** Non-templated base for #StrongType::Typedef for use with type trait queries. */
|
||||
struct StrongTypedefBase {};
|
||||
|
||||
/**
|
||||
* Templated helper to make a type-safe 'typedef' representing a single POD value.
|
||||
* A normal 'typedef' is not distinct from its base type and will be treated as
|
||||
* identical in many contexts. This class provides a distinct type that can still
|
||||
* be assign from and compared to values of its base type.
|
||||
*
|
||||
* @note This is meant to be used as a base class, not directly.
|
||||
* @tparam T Storage type
|
||||
* @tparam Tthis Type of the derived class (i.e. the concrete usage of this class).
|
||||
*/
|
||||
template <class T, class Tthis>
|
||||
struct StrongTypedef : StrongTypedefBase {
|
||||
using Type = T;
|
||||
namespace StrongType {
|
||||
/**
|
||||
* Mix-in which makes the new Typedef comparable with itself and its base type.
|
||||
*/
|
||||
struct Compare {
|
||||
template <typename TType, typename TBaseType>
|
||||
struct mixin {
|
||||
friend constexpr bool operator ==(const TType &lhs, const TType &rhs) { return lhs.value == rhs.value; }
|
||||
friend constexpr bool operator ==(const TType &lhs, const TBaseType &rhs) { return lhs.value == rhs; }
|
||||
|
||||
T value{}; ///< Backing storage field.
|
||||
friend constexpr bool operator !=(const TType &lhs, const TType &rhs) { return lhs.value != rhs.value; }
|
||||
friend constexpr bool operator !=(const TType &lhs, const TBaseType &rhs) { return lhs.value != rhs; }
|
||||
|
||||
debug_inline constexpr StrongTypedef() = default;
|
||||
debug_inline constexpr StrongTypedef(const StrongTypedef &o) = default;
|
||||
debug_inline constexpr StrongTypedef(StrongTypedef &&o) = default;
|
||||
friend constexpr bool operator <=(const TType &lhs, const TType &rhs) { return lhs.value <= rhs.value; }
|
||||
friend constexpr bool operator <=(const TType &lhs, const TBaseType &rhs) { return lhs.value <= rhs; }
|
||||
|
||||
debug_inline constexpr StrongTypedef(const T &value) : value(value) {}
|
||||
friend constexpr bool operator <(const TType &lhs, const TType &rhs) { return lhs.value < rhs.value; }
|
||||
friend constexpr bool operator <(const TType &lhs, const TBaseType &rhs) { return lhs.value < rhs; }
|
||||
|
||||
debug_inline constexpr Tthis &operator =(const Tthis &rhs) { this->value = rhs.value; return static_cast<Tthis &>(*this); }
|
||||
debug_inline constexpr Tthis &operator =(Tthis &&rhs) { this->value = std::move(rhs.value); return static_cast<Tthis &>(*this); }
|
||||
debug_inline constexpr Tthis &operator =(const T &rhs) { this->value = rhs; return static_cast<Tthis &>(*this); }
|
||||
friend constexpr bool operator >=(const TType &lhs, const TType &rhs) { return lhs.value >= rhs.value; }
|
||||
friend constexpr bool operator >=(const TType &lhs, const TBaseType &rhs) { return lhs.value >= rhs; }
|
||||
|
||||
explicit constexpr operator T() const { return this->value; }
|
||||
friend constexpr bool operator >(const TType &lhs, const TType &rhs) { return lhs.value > rhs.value; }
|
||||
friend constexpr bool operator >(const TType &lhs, const TBaseType &rhs) { return lhs.value > rhs; }
|
||||
};
|
||||
};
|
||||
|
||||
constexpr bool operator ==(const Tthis &rhs) const { return this->value == rhs.value; }
|
||||
constexpr bool operator !=(const Tthis &rhs) const { return this->value != rhs.value; }
|
||||
constexpr bool operator ==(const T &rhs) const { return this->value == rhs; }
|
||||
constexpr bool operator !=(const T &rhs) const { return this->value != rhs; }
|
||||
};
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
struct Integer {
|
||||
template <typename TType, typename TBaseType>
|
||||
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; }
|
||||
|
||||
/**
|
||||
* Extension of #StrongTypedef with operators for addition and subtraction.
|
||||
* @tparam T Storage type
|
||||
* @tparam Tthis Type of the derived class (i.e. the concrete usage of this class).
|
||||
*/
|
||||
template <class T, class Tthis>
|
||||
struct StrongIntegralTypedef : StrongTypedef<T, Tthis> {
|
||||
using StrongTypedef<T, Tthis>::StrongTypedef;
|
||||
friend constexpr TType &operator +=(TType &lhs, const TType &rhs) { lhs.value += rhs.value; return lhs; }
|
||||
friend constexpr TType operator +(const TType &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 }; }
|
||||
|
||||
debug_inline constexpr StrongIntegralTypedef() = default;
|
||||
debug_inline constexpr StrongIntegralTypedef(const StrongIntegralTypedef &o) = default;
|
||||
debug_inline constexpr StrongIntegralTypedef(StrongIntegralTypedef &&o) = default;
|
||||
friend constexpr TType &operator -=(TType &lhs, const TType &rhs) { lhs.value -= rhs.value; return lhs; }
|
||||
friend constexpr TType operator -(const TType &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 }; }
|
||||
|
||||
debug_inline constexpr StrongIntegralTypedef(const T &value) : StrongTypedef<T, Tthis>(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. */
|
||||
|
||||
debug_inline constexpr Tthis &operator =(const Tthis &rhs) { this->value = rhs.value; return static_cast<Tthis &>(*this); }
|
||||
debug_inline constexpr Tthis &operator =(Tthis &&rhs) { this->value = std::move(rhs.value); return static_cast<Tthis &>(*this); }
|
||||
debug_inline constexpr Tthis &operator =(const T &rhs) { this->value = rhs; return static_cast<Tthis &>(*this); }
|
||||
constexpr TType &operator *=(const TType &rhs) = delete;
|
||||
constexpr TType operator *(const TType &rhs) = delete;
|
||||
constexpr TType operator *(const TBaseType &rhs) = delete;
|
||||
|
||||
constexpr Tthis &operator ++() { this->value++; return static_cast<Tthis &>(*this); }
|
||||
constexpr Tthis &operator --() { this->value--; return static_cast<Tthis &>(*this); }
|
||||
constexpr Tthis operator ++(int) { auto res = static_cast<Tthis &>(*this); this->value++; return res; }
|
||||
constexpr Tthis operator --(int) { auto res = static_cast<Tthis &>(*this); this->value--; return res; }
|
||||
constexpr TType &operator /=(const TType &rhs) = delete;
|
||||
constexpr TType operator /(const TType &rhs) = delete;
|
||||
constexpr TType operator /(const TBaseType &rhs) = delete;
|
||||
|
||||
constexpr Tthis &operator +=(const Tthis &rhs) { this->value += rhs.value; return *static_cast<Tthis *>(this); }
|
||||
constexpr Tthis &operator -=(const Tthis &rhs) { this->value -= rhs.value; return *static_cast<Tthis *>(this); }
|
||||
constexpr TType &operator %=(const TType &rhs) = delete;
|
||||
constexpr TType operator %(const TType &rhs) = delete;
|
||||
constexpr TType operator %(const TBaseType &rhs) = delete;
|
||||
|
||||
constexpr Tthis operator +(const Tthis &rhs) const { return Tthis{ this->value + rhs.value }; }
|
||||
constexpr Tthis operator -(const Tthis &rhs) const { return Tthis{ this->value - rhs.value }; }
|
||||
constexpr Tthis operator +(const T &rhs) const { return Tthis{ this->value + rhs }; }
|
||||
constexpr Tthis operator -(const T &rhs) const { return Tthis{ this->value - rhs }; }
|
||||
};
|
||||
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).
|
||||
*
|
||||
* @note The base type of the new Typedef will be cast to the other type; so make sure they are compatible.
|
||||
*
|
||||
* @tparam TCompatibleType The other type to be compatible with.
|
||||
*/
|
||||
template <typename TCompatibleType>
|
||||
struct Compatible {
|
||||
template <typename TType, typename TBaseType>
|
||||
struct mixin {
|
||||
friend constexpr bool operator ==(const TType &lhs, TCompatibleType rhs) { return lhs.value == static_cast<TBaseType>(rhs); }
|
||||
friend constexpr bool operator !=(const TType &lhs, TCompatibleType rhs) { return lhs.value != static_cast<TBaseType>(rhs); }
|
||||
|
||||
friend constexpr bool operator <=(const TType &lhs, TCompatibleType rhs) { return lhs.value <= static_cast<TBaseType>(rhs); }
|
||||
friend constexpr bool operator <(const TType &lhs, TCompatibleType rhs) { return lhs.value < static_cast<TBaseType>(rhs); }
|
||||
friend constexpr bool operator >=(const TType &lhs, TCompatibleType rhs) { return lhs.value >= static_cast<TBaseType>(rhs); }
|
||||
friend constexpr bool operator >(const TType &lhs, TCompatibleType rhs) { return lhs.value > static_cast<TBaseType>(rhs); }
|
||||
|
||||
friend constexpr TType operator +(const TType &lhs, TCompatibleType rhs) { return { static_cast<TBaseType>(lhs.value + rhs) }; }
|
||||
friend constexpr TType operator -(const TType &lhs, TCompatibleType rhs) { return { static_cast<TBaseType>(lhs.value - rhs) }; }
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Mix-in which makes the new Typedef implicitly convertible to its base type.
|
||||
*
|
||||
* Be careful: when allowing implicit conversion, you won't notice if this type is assigned to a compatible, but different, type.
|
||||
* For example:
|
||||
*
|
||||
* StrongType::Typedef<int, struct MyTypeTag, Implicit> a = 1;
|
||||
* StrongType::Typedef<int, struct MyTypeTag, Implicit> b = 2;
|
||||
* a = b; // OK
|
||||
*/
|
||||
struct Implicit {
|
||||
template <typename TType, typename TBaseType>
|
||||
struct mixin {
|
||||
constexpr operator TBaseType () const { return static_cast<const TType &>(*this).value; }
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Mix-in which makes the new Typedef explicitly convertible to its base type.
|
||||
*/
|
||||
struct Explicit {
|
||||
template <typename TType, typename TBaseType>
|
||||
struct mixin {
|
||||
explicit constexpr operator TBaseType () const { return static_cast<const TType &>(*this).value; }
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Templated helper to make a type-safe 'typedef' representing a single POD value.
|
||||
* A normal 'typedef' is not distinct from its base type and will be treated as
|
||||
* identical in many contexts. This class provides a distinct type that can still
|
||||
* be assign from and compared to values of its base type.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* using MyType = StrongType::Typedef<int, struct MyTypeTag, StrongType::Explicit, StrongType::Compare, StrongType::Integer>;
|
||||
*
|
||||
* @tparam TBaseType Type of the derived class (i.e. the concrete usage of this class).
|
||||
* @tparam TTag An unique struct to keep types of the same TBaseType distinct.
|
||||
* @tparam TProperties A list of mixins to add to the class.
|
||||
*/
|
||||
template <typename TBaseType, typename TTag, typename... TProperties>
|
||||
struct EMPTY_BASES Typedef : public StrongTypedefBase, public TProperties::template mixin<Typedef<TBaseType, TTag, TProperties...>, TBaseType>... {
|
||||
using BaseType = TBaseType;
|
||||
|
||||
constexpr Typedef() = default;
|
||||
constexpr Typedef(const Typedef &) = default;
|
||||
constexpr Typedef(Typedef &&) = default;
|
||||
|
||||
constexpr Typedef(const TBaseType &value) : value(value) {}
|
||||
|
||||
constexpr Typedef &operator =(const Typedef &rhs) { this->value = rhs.value; return *this; }
|
||||
constexpr Typedef &operator =(Typedef &&rhs) { this->value = std::move(rhs.value); return *this; }
|
||||
constexpr Typedef &operator =(const TBaseType &rhs) { this->value = rhs; return *this; }
|
||||
|
||||
/* Only allow TProperties classes access to the internal value. Everyone else needs to do an explicit cast. */
|
||||
friend struct Explicit;
|
||||
friend struct Implicit;
|
||||
friend struct Compare;
|
||||
friend struct Integer;
|
||||
template <typename TCompatibleType> friend struct Compatible;
|
||||
|
||||
/* GCC / MSVC don't pick up on the "friend struct" above, where CLang does.
|
||||
* As in our CI we compile for all three targets, it is sufficient to have one
|
||||
* that errors on this; but nobody should be using "value" directly. Instead,
|
||||
* use a static_cast<> to convert to the base type. */
|
||||
#ifdef __clang__
|
||||
protected:
|
||||
#endif /* __clang__ */
|
||||
TBaseType value{};
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* STRONG_TYPEDEF_TYPE_HPP */
|
||||
|
Reference in New Issue
Block a user