Przeglądaj źródła

refactor: allow different bound type
refactor: move the throw if oob check into clamp.h
refactor: allow custom min/max
feat: add operator*/operator->/operator<=>
feat: add min/max passthrough on Bound
refactor: improve use of free arithmetic

Sam Jaffe 2 lat temu
rodzic
commit
2676073435
3 zmienionych plików z 101 dodań i 42 usunięć
  1. 16 3
      include/math/clamp.h
  2. 16 9
      include/math/dyn_limit.h
  3. 69 30
      include/math/limit.h

+ 16 - 3
include/math/clamp.h

@@ -8,11 +8,24 @@
 #pragma once
 
 #include <algorithm>
+#include <stdexcept>
 #include <utility>
 
 namespace math {
-template <typename T>
-T const & clamp(T const & value, T const & min, T const & max) {
-  return std::max(min, std::min(value, max));
+template <typename T, typename Bound>
+decltype(auto) clamp(T const & value, Bound const & lower, Bound const & upper) {
+  using std::min;
+  using std::max;
+  return max(min(value, upper), lower);
+}
+
+template <typename T, typename Bound>
+T const & assert_in_bounds(T const & value, Bound const & lower, Bound const & upper) {
+  if (lower > upper) {
+    throw std::domain_error("The minimum value must be less than or equal to the maximum");
+  } else if (clamp(value, lower, upper) != value) {
+    throw std::out_of_range("Must construct a value within range");
+  }
+  return value;
 }
 }

+ 16 - 9
include/math/dyn_limit.h

@@ -9,42 +9,49 @@
 #pragma once
 
 #include <math/clamp.h>
+#include <math/limit.h>
 
 namespace math {
-template <typename T, T, T> class Bound;
-
-template <typename T> class DynBound final {
+template <typename T, typename B = T> class DynBound final {
 public:
   using value_type = T;
   using underlying_type = value_type;
+  using bound_type = B;
 
 private:
-  value_type lower_bound_{};
-  value_type upper_bound_{};
+  bound_type lower_bound_{};
+  bound_type upper_bound_{};
   value_type value_{};
 
 public:
   DynBound() = default;
-  DynBound(value_type value, value_type lower_bound, value_type upper_bound)
+  DynBound(value_type value, bound_type lower_bound, bound_type upper_bound)
       : lower_bound_(lower_bound), upper_bound_(upper_bound),
         value_(clamp(value, lower_bound_, upper_bound_)) {}
 
-  template <value_type MINIMUM_VALUE, value_type MAXIMUM_VALUE>
+  template <bound_type MINIMUM_VALUE, bound_type MAXIMUM_VALUE>
   DynBound(Bound<value_type, MINIMUM_VALUE, MAXIMUM_VALUE> const & other)
       : lower_bound_(MINIMUM_VALUE), upper_bound_(MAXIMUM_VALUE),
         value_(other) {}
 
   operator value_type() const { return value_; }
+  value_type operator*() const { return value_; }
+  value_type const *operator->() const { return &value_; }
+  
+  auto operator<=>(DynBound const &other) const noexcept = default;
+
   bool is_min() const { return value_ == lower_bound_; }
   bool is_max() const { return value_ == upper_bound_; }
 
   DynBound & operator+=(value_type by) {
-    value_ = std::min<value_type>(upper_bound_, value_ + by);
+    using std::min;
+    value_ = min(static_cast<value_type>(value_ + by), upper_bound_);
     return *this;
   }
 
   DynBound & operator-=(value_type by) {
-    value_ = std::max<value_type>(lower_bound_, value_ - by);
+    using std::max;
+    value_ = max(static_cast<value_type>(value_ - by), lower_bound_);
     return *this;
   }
 

+ 69 - 30
include/math/limit.h

@@ -8,7 +8,7 @@
 
 #pragma once
 
-#include <stdexcept>
+#include <type_traits>
 
 #include <math/clamp.h>
 
@@ -18,33 +18,42 @@ struct {
 } assert_bounds;
 using AssertBounds = decltype(assert_bounds);
 
-template <typename T, T MINIMUM_VALUE, T MAXIMUM_VALUE> class Bound final {
+template <typename T, auto MINIMUM_VALUE, auto MAXIMUM_VALUE> class Bound final {
 public:
   static_assert(MINIMUM_VALUE <= MAXIMUM_VALUE,
                 "The minimum value must be less than or equal to the maximum");
 
   using value_type = T;
   using underlying_type = value_type;
+  using bound_type =
+      std::common_type_t<decltype(MAXIMUM_VALUE), decltype(MINIMUM_VALUE)>;
 
-  static constexpr const value_type min = MINIMUM_VALUE;
-  static constexpr const value_type max = MAXIMUM_VALUE;
+  static constexpr const bound_type min = MINIMUM_VALUE;
+  static constexpr const bound_type max = MAXIMUM_VALUE;
 
 private:
-  value_type value{min};
+  value_type value_;
 
 public:
-  constexpr Bound() = default;
+  constexpr Bound() : value_(clamp(value_type(), min, max)) {}
   constexpr Bound(value_type const & val) noexcept
-      : value(clamp(val, min, max)) {}
+      : value_(clamp(val, min, max)) {}
 
-  Bound(AssertBounds, value_type val) : value(val) {
-    if (val < min || max < val) {
-      throw std::out_of_range{"Must construct a value within range"};
-    }
-  }
+  Bound(AssertBounds, value_type val) : value_(assert_in_bounds(val, min, max)) {}
+
+  operator value_type() const { return value_; }
+  value_type operator*() const { return value_; }
+  value_type const *operator->() const { return &value_; }
 
-  Bound & operator-=(value_type by) { return *this = Bound(value - by); }
-  Bound & operator+=(value_type by) { return *this = Bound(value + by); }
+  auto operator<=>(Bound const &other) const noexcept = default;
+  
+  template <typename F> void mutate(F &&func) {
+    std::forward<F>(func)(value_);
+    *this = Bound(value_);
+  }
+  
+  Bound & operator-=(value_type by) { return *this = Bound(value_ - by); }
+  Bound & operator+=(value_type by) { return *this = Bound(value_ + by); }
   Bound operator+(value_type by) const { return Bound{*this} += by; }
   Bound operator-(value_type by) const { return Bound{*this} -= by; }
   Bound & operator--() { return *this -= 1; }
@@ -61,32 +70,62 @@ public:
     operator++();
     return tmp;
   }
-
-  operator value_type() const { return value; }
 };
 
-template <typename T, T MINMAX> using SymBound = Bound<T, -MINMAX, +MINMAX>;
-template <typename T, T VAL>
+template <typename T, auto MINMAX> using SymBound = Bound<T, -MINMAX, +MINMAX>;
+template <typename T, auto VAL>
 using UniBound =
     std::conditional_t<0 <= VAL, Bound<T, 0, VAL>, Bound<T, VAL, 0>>;
 
-template <typename L, L l1, L h1, typename R, R l2, R h2>
-auto operator+(Bound<L, l1, h1> lhs, Bound<R, l2, h2> rhs) {
-  return L(lhs) + R(rhs);
+template <typename T, auto MIN, auto MAX, typename R>
+auto operator+(Bound<T, MIN, MAX> const &lhs, R const &rhs) {
+  return *lhs + rhs;
+}
+
+template <typename L, auto L_MIN, auto L_MAX, typename R, auto R_MIN, auto R_MAX>
+auto operator+(Bound<L, L_MIN, L_MAX> const &lhs, Bound<R, R_MIN, R_MAX> const &rhs) {
+  return *lhs + *rhs;
+}
+
+template <typename T, auto MIN, auto MAX, typename R>
+auto operator-(Bound<T, MIN, MAX> const &lhs, R const &rhs) {
+  return *lhs - rhs;
+}
+
+template <typename L, auto L_MIN, auto L_MAX, typename R, auto R_MIN, auto R_MAX>
+auto operator-(Bound<L, L_MIN, L_MAX> const &lhs, Bound<R, R_MIN, R_MAX> const &rhs) {
+  return *lhs - *rhs;
+}
+
+template <typename T, auto MIN, auto MAX, typename R>
+auto operator*(Bound<T, MIN, MAX> const &lhs, R const &rhs) {
+  return *lhs * rhs;
+}
+
+template <typename L, auto L_MIN, auto L_MAX, typename R, auto R_MIN, auto R_MAX>
+auto operator*(Bound<L, L_MIN, L_MAX> const &lhs, Bound<R, R_MIN, R_MAX> const &rhs) {
+  return *lhs * *rhs;
+}
+
+template <typename T, auto MIN, auto MAX, typename R>
+auto operator/(Bound<T, MIN, MAX> const &lhs, R const &rhs) {
+  return *lhs / rhs;
 }
 
-template <typename L, L l1, L h1, typename R, R l2, R h2>
-auto operator-(Bound<L, l1, h1> lhs, Bound<R, l2, h2> rhs) {
-  return L(lhs) - R(rhs);
+template <typename L, auto L_MIN, auto L_MAX, typename R, auto R_MIN, auto R_MAX>
+auto operator/(Bound<L, L_MIN, L_MAX> const &lhs, Bound<R, R_MIN, R_MAX> const &rhs) {
+  return *lhs / *rhs;
 }
 
-template <typename L, L l1, L h1, typename R, R l2, R h2>
-auto operator*(Bound<L, l1, h1> lhs, Bound<R, l2, h2> rhs) {
-  return L(lhs) * R(rhs);
+template <typename T, auto MIN, auto MAX, typename R>
+auto min(Bound<T, MIN, MAX> const &lhs, R const &rhs) {
+  using std::min;
+  return min(*lhs, rhs);
 }
 
-template <typename L, L l1, L h1, typename R, R l2, R h2>
-auto operator/(Bound<L, l1, h1> lhs, Bound<R, l2, h2> rhs) {
-  return L(lhs) / R(rhs);
+template <typename T, auto MIN, auto MAX, typename R>
+auto max(Bound<T, MIN, MAX> const &lhs, R const &rhs) {
+  using std::max;
+  return max(*lhs, rhs);
 }
 }