#pragma once #include #include #include #include #include #include namespace time_units { constexpr double EPSILON = 1e-10; // Base concept for all time values template concept TimeValue = requires(T a, T b) { { a + b } -> std::convertible_to; { a - b } -> std::convertible_to; { a * double{} } -> std::convertible_to; { a / double{} } -> std::convertible_to; { a < b } -> std::convertible_to; { std::numeric_limits::infinity() } -> std::convertible_to; { -std::numeric_limits::infinity() } -> std::convertible_to; }; // Time unit tags (what is being measured) struct SecondsTag {}; struct BeatsTag {}; // Intervals represent durations without a reference point template class TypedTimeDelta final { private: T value_; public: using unit_tag = UnitTag; using value_type = T; explicit TypedTimeDelta(T value) noexcept : value_(value) {} // Allow access to the raw value T raw() const noexcept { return value_; } bool is_finite() const noexcept { return std::isfinite(value_); } // Basic arithmetic operations TypedTimeDelta operator+(const TypedTimeDelta &other) const noexcept { return TypedTimeDelta(value_ + other.value_); } TypedTimeDelta &operator+=(const TypedTimeDelta &other) noexcept { value_ += other.value_; return *this; } TypedTimeDelta operator-(const TypedTimeDelta &other) const noexcept { return TypedTimeDelta(value_ - other.value_); } TypedTimeDelta &operator-=(const TypedTimeDelta &other) noexcept { value_ -= other.value_; return *this; } TypedTimeDelta operator*(double scalar) const noexcept { return TypedTimeDelta(value_ * scalar); } friend TypedTimeDelta operator*(double scalar, const TypedTimeDelta &delta) noexcept { return delta * scalar; } TypedTimeDelta operator-() const noexcept { return TypedTimeDelta(-value_); } TypedTimeDelta operator/(double scalar) const { if (std::abs(scalar) < EPSILON) { throw std::invalid_argument("Division by zero or very small value"); } return TypedTimeDelta(value_ / scalar); } // Comparison operators bool operator<(const TypedTimeDelta &other) const noexcept { return value_ < other.value_; } bool operator>(const TypedTimeDelta &other) const noexcept { return value_ > other.value_; } bool operator<=(const TypedTimeDelta &other) const noexcept { return value_ <= other.value_; } bool operator>=(const TypedTimeDelta &other) const noexcept { return value_ >= other.value_; } bool operator==(const TypedTimeDelta &other) const noexcept { if (value_ == std::numeric_limits::infinity() && other.value_ == std::numeric_limits::infinity()) { return true; } if (value_ == -std::numeric_limits::infinity() && other.value_ == -std::numeric_limits::infinity()) { return true; } return std::abs(value_ - other.value_) < EPSILON; } bool operator!=(const TypedTimeDelta &other) const noexcept { return !(*this == other); } std::string toString() const { return "d" + std::to_string(value_); } }; // Strong typing for time points template class TypedTime final { private: T value_; public: using unit_tag = UnitTag; using value_type = T; explicit TypedTime(T value) noexcept : value_(value) {} // Allow access to the raw value T raw() const noexcept { return value_; } bool is_finite() const noexcept { return std::isfinite(value_); } // Addition with interval of the same unit TypedTime operator+(const TypedTimeDelta &delta) const noexcept { return TypedTime(value_ + delta.raw()); } friend TypedTime operator+(const TypedTimeDelta &delta, const TypedTime &time) noexcept { return time + delta; } TypedTime &operator+=(const TypedTimeDelta &delta) noexcept { value_ += delta.raw(); return *this; } // Subtraction of two time points yields an interval TypedTimeDelta operator-(const TypedTime &other) const noexcept { return TypedTimeDelta(value_ - other.value_); } // Subtraction of an interval yields a time point TypedTime operator-(const TypedTimeDelta &delta) const noexcept { return TypedTime(value_ - delta.raw()); } TypedTime &operator-=(const TypedTimeDelta &delta) noexcept { value_ -= delta.raw(); return *this; } // Comparison operators bool operator<(const TypedTime &other) const noexcept { return value_ < other.value_; } bool operator>(const TypedTime &other) const noexcept { return value_ > other.value_; } bool operator<=(const TypedTime &other) const noexcept { return value_ <= other.value_; } bool operator>=(const TypedTime &other) const noexcept { return value_ >= other.value_; } bool operator==(const TypedTime &other) const noexcept { if (value_ == std::numeric_limits::infinity() && other.value_ == std::numeric_limits::infinity()) { return true; } if (value_ == -std::numeric_limits::infinity() && other.value_ == -std::numeric_limits::infinity()) { return true; } return std::abs(value_ - other.value_) < EPSILON; } bool operator!=(const TypedTime &other) const noexcept { return !(*this == other); } std::string toString() const { return std::to_string(value_); } static TypedTime inf() noexcept { return TypedTime(std::numeric_limits::infinity()); } static TypedTime neg_inf() noexcept { return TypedTime(-std::numeric_limits::infinity()); } }; // Concept to check if a type is a valid TypedTime specialization template concept IsTypedTime = requires { typename TTT::unit_tag; typename TTT::value_type; requires std::is_same_v< TTT, TypedTime>; requires TimeValue; }; // Type aliases for common time domains template using SecondsDelta = TypedTimeDelta; template using BeatsDelta = TypedTimeDelta; template using Seconds = TypedTime; template using Beats = TypedTime; } // namespace time_units