From 872caf460a557533e93c6d4842127046fcaea7a4 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Wed, 30 Nov 2022 17:54:13 +0100 Subject: [PATCH] refactor: `quantity_cast` refactored --- src/core/include/units/concepts.h | 4 -- src/core/include/units/quantity.h | 17 +++---- src/core/include/units/quantity_cast.h | 68 +++++++++++++------------- 3 files changed, 43 insertions(+), 46 deletions(-) diff --git a/src/core/include/units/concepts.h b/src/core/include/units/concepts.h index 0315908f..eb9330a1 100644 --- a/src/core/include/units/concepts.h +++ b/src/core/include/units/concepts.h @@ -81,10 +81,6 @@ concept scalable_ = // exposition only castable_number_ || (requires { typename T::value_type; } && castable_number_ && scalable_number_>); -template -concept scalable_with_ = // exposition only - common_type_with_ && scalable_>; - template concept Representation = (!Quantity) && // (!QuantityLike) && (!wrapped_quantity_) && diff --git a/src/core/include/units/quantity.h b/src/core/include/units/quantity.h index 7ecece09..38fda71d 100644 --- a/src/core/include/units/quantity.h +++ b/src/core/include/units/quantity.h @@ -56,10 +56,10 @@ template concept floating_point_ = // exposition only (Quantity && treat_as_floating_point) || (!Quantity && treat_as_floating_point); -template -concept safe_convertible_to_ = // exposition only - (!Quantity) && (!Quantity) && std::convertible_to && - (floating_point_ || (!floating_point_)); +template +concept rep_safe_constructible_from_ = // exposition only + (!Quantity>) && std::constructible_from && + (floating_point_ || (!floating_point_)); // QFrom ratio is an exact multiple of QTo template @@ -69,16 +69,15 @@ concept harmonic_ = // exposition only template concept quantity_convertible_to_ = // exposition only - Quantity && Quantity && - interconvertible(QFrom::reference, QTo::reference) && scalable_with_ && + Quantity && Quantity && requires(QFrom q) { quantity_cast(q); } && (floating_point_ || (!floating_point_ && harmonic_)); template concept quantity_value_for_ = std::regular_invocable && Representation>; template -concept invoke_result_convertible_to_ = - Representation && quantity_value_for_ && safe_convertible_to_>; +concept invoke_result_convertible_to_ = Representation && quantity_value_for_ && + rep_safe_constructible_from_, T>; template concept have_quantity_for_ = Quantity && (!Quantity) && quantity_value_for_; @@ -151,7 +150,7 @@ public: quantity(quantity&&) = default; template - requires safe_convertible_to_, rep> + requires rep_safe_constructible_from_ constexpr explicit(!detail::quantity_one) quantity(Value&& v) : number_(std::forward(v)) { } diff --git a/src/core/include/units/quantity_cast.h b/src/core/include/units/quantity_cast.h index 836c37bc..32df13ac 100644 --- a/src/core/include/units/quantity_cast.h +++ b/src/core/include/units/quantity_cast.h @@ -48,30 +48,6 @@ class quantity; // template U, Representation Rep> // class quantity_point_kind; -namespace detail { - -template -struct cast_traits; - -template - requires common_type_with_, std::intmax_t> -struct cast_traits { - using multiplier_type = std::common_type_t, std::intmax_t>; - using rep_type = multiplier_type; -}; - -template - requires(!common_type_with_, std::intmax_t> && - scalable_number_, std::intmax_t> && - requires { typename std::common_type_t::value_type; } && - common_type_with_::value_type, std::intmax_t>) -struct cast_traits { - using multiplier_type = std::common_type_t::value_type, std::intmax_t>; - using rep_type = std::common_type_t; -}; - -} // namespace detail - /** * @brief Explicit cast of a quantity * @@ -84,16 +60,41 @@ struct cast_traits { * * @tparam To a target quantity type to cast to */ -template Rep> - requires(interconvertible(To::reference, R)) +template + requires(interconvertible(To::reference, R)) && + ((R.unit == To::unit && std::constructible_from) || + (R.unit != To::unit)) // && scalable_with_)) +// TODO how to constrain the second part here? [[nodiscard]] constexpr auto quantity_cast(const quantity& q) { if constexpr (R.unit == To::unit) { - return To(static_cast(q.number())); + // no scaling of the number needed + return To(static_cast(q.number())); // this is the only (and recommended) way to do + // a truncating conversion on a number, so we are + // using static_cast to suppress all the compiler + // warnings on conversions } else { - using traits = detail::cast_traits; - using multiplier_type = TYPENAME traits::multiplier_type; - using rep_type = TYPENAME traits::rep_type; + // scale the number + using rep_type = decltype([] { + // determines the best representation type + if constexpr (requires { typename std::common_type_t; }) + // returns a common type of two representation types if available + // i.e. `double` and `int` will end up with `double` precision + return std::common_type_t{}; + else + return Rep{}; + }()); + using multiplier_type = decltype([] { + // widen the type to prevent overflows + using wider_type = decltype(rep_type{} * std::intmax_t{}); + // check if `wider_type` supports scaling operations + if constexpr (requires(wider_type v) { v* v / v; }) + // if the `wider_type` can handle scaling operations then use it to improve accuracy + return wider_type{}; + else + // needed for example for linear algebra where `op/` on matrix types is not available + return std::intmax_t{}; + }()); constexpr Magnitude auto c_mag = detail::get_canonical_unit(R.unit).mag / detail::get_canonical_unit(To::unit).mag; constexpr Magnitude auto num = numerator(c_mag); @@ -181,8 +182,8 @@ template * * @tparam ToRep a representation type to use for a target quantity */ -template Rep> -// requires(std::constructible_from>) +template + requires std::constructible_from [[nodiscard]] constexpr auto quantity_cast(const quantity& q) { return quantity_cast>(q); @@ -308,7 +309,8 @@ template Rep> // * Implicit conversions between quantity point kinds of different types are allowed only for "safe" // * (i.e. non-truncating) conversion. In other cases an explicit cast has to be used. // * -// * This cast gets the target (quantity) point kind type to cast to or anything that works for quantity_kind_cast. For +// * This cast gets the target (quantity) point kind type to cast to or anything that works for quantity_kind_cast. +// For // * example: // * // * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1 * mm});