diff --git a/example/currency.cpp b/example/currency.cpp index c1527b6e..3d9221fb 100644 --- a/example/currency.cpp +++ b/example/currency.cpp @@ -39,8 +39,6 @@ inline constexpr struct great_british_pound : named_unit<"GBP", kind_of> {} japanese_jen; // clang-format on -static_assert(!std::equality_comparable_with, quantity>); - #if 0 // if you have only a few currencies to handle @@ -54,6 +52,11 @@ template #else +template + requires(get_quantity_spec(From) == currency && get_quantity_spec(To) == currency) +inline constexpr is_unit_convertible_result mp_units::is_unit_convertible = + is_unit_convertible_result::non_integral_factor; + [[nodiscard]] std::string_view to_string_view(Unit auto u) { return u.symbol.ascii().c_str(); } template @@ -69,23 +72,21 @@ template #endif -template auto To, ReferenceOf auto From, typename Rep> -quantity exchange_to(quantity q) +template +[[nodiscard]] Rep scale_quantity_number(Rep v, From from, To to) + requires(get_quantity_spec(from) == currency && get_quantity_spec(to) == currency) { - return static_cast(exchange_rate() * q.number()) * To; + return static_cast(exchange_rate() * v); } -template auto To, ReferenceOf auto From, auto PO, typename Rep> -quantity_point exchange_to(quantity_point q) -{ - return static_cast(exchange_rate() * q.absolute().number()) * To; -} +static_assert(!std::equality_comparable_with, quantity>); int main() { - auto price_usd = quantity_point{100 * us_dollar}; - auto price_euro = quantity_point{exchange_to(price_usd)}; + auto price_usd = quantity_point{100. * us_dollar}; + auto price_euro = quantity_point{price_usd[euro]}; std::cout << price_usd.absolute() << " -> " << price_euro.absolute() << "\n"; // std::cout << price_usd.absolute() + price_euro.absolute() << "\n"; // does not compile -} \ No newline at end of file + // std::cout << price_euro.absolute() + price_usd.absolute() << "\n"; // does not compile +} diff --git a/src/core/include/mp_units/bits/sudo_cast.h b/src/core/include/mp_units/bits/sudo_cast.h index 1d4b9bf1..9825c815 100644 --- a/src/core/include/mp_units/bits/sudo_cast.h +++ b/src/core/include/mp_units/bits/sudo_cast.h @@ -35,6 +35,32 @@ class quantity; namespace detail { +// The default implementation for the number scaling customization point +template +[[nodiscard]] constexpr auto scale_quantity_number(Rep v, From from, To to) + requires(have_same_canonical_reference_unit(from, to)) +{ + using multiplier_type = decltype([] { + // widen the type to prevent overflows + using wider_type = decltype(Rep{} * 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(from).mag / detail::get_canonical_unit(to).mag; + constexpr Magnitude auto num = numerator(c_mag); + constexpr Magnitude auto den = denominator(c_mag); + constexpr Magnitude auto irr = c_mag * (den / num); + + constexpr auto val = [](Magnitude auto m) { return get_value(m); }; + return v * val(num) / val(den) * val(irr); +} + /** * @brief Explicit cast of entire quantity * @@ -45,7 +71,7 @@ namespace detail { template requires(castable(get_quantity_spec(R), To::quantity_spec)) && ((get_unit(R) == To::unit && std::constructible_from) || - (get_unit(R) != To::unit)) // && scalable_with_)) + (get_unit(R) != To::unit && convertible(get_unit(R), To::unit))) // && scalable_with_)) // TODO how to constrain the second part here? [[nodiscard]] constexpr Quantity auto sudo_cast(const quantity& q) { @@ -66,26 +92,11 @@ template 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(get_unit(R)).mag / detail::get_canonical_unit(To::unit).mag; - constexpr Magnitude auto num = numerator(c_mag); - constexpr Magnitude auto den = denominator(c_mag); - constexpr Magnitude auto irr = c_mag * (den / num); - - constexpr auto val = [](Magnitude auto m) { return get_value(m); }; - return static_cast(static_cast(q.number()) * val(num) / val(den) * val(irr)) * + // `scale_quantity_number` is a customization point + // Will be found only via ADL (if provided) for user-defined units + return static_cast( + scale_quantity_number(static_cast(q.number()), get_unit(R), To::unit)) * To::reference; } } diff --git a/src/core/include/mp_units/quantity.h b/src/core/include/mp_units/quantity.h index 4862870e..36bdcbe8 100644 --- a/src/core/include/mp_units/quantity.h +++ b/src/core/include/mp_units/quantity.h @@ -50,18 +50,13 @@ template concept RepSafeConstructibleFrom = // exposition only std::constructible_from && (treat_as_floating_point || !treat_as_floating_point); -// UFrom ratio is an exact multiple of UTo -template -concept Harmonic = // exposition only - Unit && Unit && - is_integral(get_canonical_unit(UFrom).mag / get_canonical_unit(UTo).mag); - template concept QuantityConvertibleTo = // exposition only Quantity && Quantity && implicitly_convertible(QFrom::quantity_spec, QTo::quantity_spec) && convertible(QFrom::unit, QTo::unit) && requires(QFrom q) { detail::sudo_cast(q); } && (treat_as_floating_point || - (!treat_as_floating_point && Harmonic)); + (!treat_as_floating_point && + is_unit_convertible == is_unit_convertible_result::integral_factor)); template concept InvokeResultOf = std::regular_invocable && RepresentationOf, Ch>; diff --git a/src/core/include/mp_units/unit.h b/src/core/include/mp_units/unit.h index da4d324a..50c4583e 100644 --- a/src/core/include/mp_units/unit.h +++ b/src/core/include/mp_units/unit.h @@ -594,11 +594,21 @@ inline constexpr struct percent : named_unit<"%", mag * one> {} p inline constexpr struct per_mille : named_unit * one> {} per_mille; // clang-format on +// is_unit_convertible customization point +enum class is_unit_convertible_result { no, integral_factor, non_integral_factor }; -// convertible_to +template +inline constexpr is_unit_convertible_result is_unit_convertible = + detail::have_same_canonical_reference_unit(From, To) + ? (is_integral(detail::get_canonical_unit(From).mag / detail::get_canonical_unit(To).mag) + ? is_unit_convertible_result::integral_factor + : is_unit_convertible_result::non_integral_factor) + : is_unit_convertible_result::no; + +// convertible [[nodiscard]] consteval bool convertible(Unit auto from, Unit auto to) { - return detail::have_same_canonical_reference_unit(from, to); + return is_unit_convertible != is_unit_convertible_result::no; } // Common unit