Codechange: make TimerGameCalendar Date and Year types strongly typed (#10761)

This commit is contained in:
Patric Stout
2023-08-12 20:14:21 +02:00
committed by GitHub
parent 0238a2b567
commit 299570b2c1
55 changed files with 390 additions and 203 deletions

View File

@@ -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) {

View File

@@ -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);
}
}
/**

View File

@@ -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 */