From b4f47c3fef4da67bdf9179b55b9014855d93babd Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 3 Feb 2023 11:08:13 +0100 Subject: [PATCH] feat: `value_cast` support added Resolves #239 and #120 --- example/avg_speed.cpp | 20 +- example/capacitor_time_curve.cpp | 2 +- example/conversion_factor.cpp | 2 +- example/foot_pound_second.cpp | 2 +- example/glide_computer/glide_computer.cpp | 2 +- example/glide_computer_example.cpp | 4 +- example/hello_units.cpp | 6 +- example/linear_algebra.cpp | 2 +- example/total_energy.cpp | 2 +- src/core/CMakeLists.txt | 2 + .../include/mp_units/bits/quantity_cast.h | 189 ++---------------- .../mp_units/bits/representation_concepts.h | 2 +- src/core/include/mp_units/bits/sudo_cast.h | 93 +++++++++ src/core/include/mp_units/bits/value_cast.h | 74 +++++++ src/core/include/mp_units/quantity.h | 12 +- src/core/include/mp_units/unit.h | 2 +- test/unit_test/runtime/fmt_test.cpp | 12 +- .../unit_test/runtime/linear_algebra_test.cpp | 2 +- test/unit_test/static/angle_test.cpp | 2 +- 19 files changed, 220 insertions(+), 212 deletions(-) create mode 100644 src/core/include/mp_units/bits/sudo_cast.h create mode 100644 src/core/include/mp_units/bits/value_cast.h diff --git a/example/avg_speed.cpp b/example/avg_speed.cpp index 8b39b06c..bf1243cb 100644 --- a/example/avg_speed.cpp +++ b/example/avg_speed.cpp @@ -52,8 +52,7 @@ constexpr QuantityOf auto avg_speed(QuantityOf auto d, template D, QuantityOf T, QuantityOf V> void print_result(D distance, T duration, V speed) { - constexpr auto kmph = si::kilo / si::hour; - const auto result_in_kmph = quantity_cast(speed); + const auto result_in_kmph = value_cast(speed); std::cout << "Average speed of a car that makes " << distance << " in " << duration << " is " << result_in_kmph << ".\n"; } @@ -80,8 +79,7 @@ void example() std::cout << "\nSI units with 'double' as representation\n"; // conversion from a floating-point to an integral type is a truncating one so an explicit cast is needed - print_result(distance, duration, - fixed_int_si_avg_speed(quantity_cast(distance), quantity_cast(duration))); + print_result(distance, duration, fixed_int_si_avg_speed(value_cast(distance), value_cast(duration))); print_result(distance, duration, fixed_double_si_avg_speed(distance, duration)); print_result(distance, duration, avg_speed(distance, duration)); } @@ -97,7 +95,7 @@ void example() // it is not possible to make a lossless conversion of miles to meters on an integral type // (explicit cast needed) - print_result(distance, duration, fixed_int_si_avg_speed(quantity_cast(distance), duration)); + print_result(distance, duration, fixed_int_si_avg_speed(value_cast(distance), duration)); print_result(distance, duration, fixed_double_si_avg_speed(distance, duration)); print_result(distance, duration, avg_speed(distance, duration)); } @@ -114,9 +112,8 @@ void example() // conversion from a floating-point to an integral type is a truncating one so an explicit cast is needed // also it is not possible to make a lossless conversion of miles to meters on an integral type // (explicit cast needed) - print_result( - distance, duration, - fixed_int_si_avg_speed(quantity_cast>(distance), quantity_cast(duration))); + print_result(distance, duration, + fixed_int_si_avg_speed(value_cast(value_cast(distance)), value_cast(duration))); print_result(distance, duration, fixed_double_si_avg_speed(distance, duration)); print_result(distance, duration, avg_speed(distance, duration)); } @@ -130,7 +127,7 @@ void example() // it is not possible to make a lossless conversion of centimeters to meters on an integral type // (explicit cast needed) - print_result(distance, duration, fixed_int_si_avg_speed(quantity_cast(distance), duration)); + print_result(distance, duration, fixed_int_si_avg_speed(value_cast(distance), duration)); print_result(distance, duration, fixed_double_si_avg_speed(distance, duration)); print_result(distance, duration, avg_speed(distance, duration)); } @@ -145,9 +142,8 @@ void example() // conversion from a floating-point to an integral type is a truncating one so an explicit cast is needed // it is not possible to make a lossless conversion of centimeters to meters on an integral type // (explicit cast needed) - print_result( - distance, duration, - fixed_int_si_avg_speed(quantity_cast>(distance), quantity_cast(duration))); + print_result(distance, duration, + fixed_int_si_avg_speed(value_cast(value_cast(distance)), value_cast(duration))); print_result(distance, duration, fixed_double_si_avg_speed(distance, duration)); print_result(distance, duration, avg_speed(distance, duration)); diff --git a/example/capacitor_time_curve.cpp b/example/capacitor_time_curve.cpp index 01e30fb8..a9f99482 100644 --- a/example/capacitor_time_curve.cpp +++ b/example/capacitor_time_curve.cpp @@ -40,7 +40,7 @@ int main() constexpr auto R = isq::resistance(4.7, si::kilo); for (auto t = isq::time(0, ms); t <= isq::time(50, ms); ++t) { - const weak_quantity_of auto Vt = V0 * mp_units::exp(-t / (R * C)); + const WeakQuantityOf auto Vt = V0 * mp_units::exp(-t / (R * C)); std::cout << "at " << t << " voltage is "; diff --git a/example/conversion_factor.cpp b/example/conversion_factor.cpp index cbdaaef0..21561688 100644 --- a/example/conversion_factor.cpp +++ b/example/conversion_factor.cpp @@ -32,7 +32,7 @@ template requires std::constructible_from inline constexpr double conversion_factor(Target, Source) { - return quantity_cast(1. * Source::reference).number(); + return value_cast(1. * Source::reference).number(); } } // namespace diff --git a/example/foot_pound_second.cpp b/example/foot_pound_second.cpp index 70d013b2..6d6c456d 100644 --- a/example/foot_pound_second.cpp +++ b/example/foot_pound_second.cpp @@ -52,7 +52,7 @@ struct Ship { template auto fmt_line(const Q& q) { - return STD_FMT::format("{:22}", q) + (STD_FMT::format(",{:20}", quantity_cast(q)) + ...); + return STD_FMT::format("{:22}", q) + (STD_FMT::format(",{:20}", value_cast(q)) + ...); } // Print the ship details in the units as defined in the Ship struct, in other si::imperial units, and in SI diff --git a/example/glide_computer/glide_computer.cpp b/example/glide_computer/glide_computer.cpp index 0ec5aca4..cd8afb1e 100644 --- a/example/glide_computer/glide_computer.cpp +++ b/example/glide_computer/glide_computer.cpp @@ -77,7 +77,7 @@ void print(std::string_view phase_name, timestamp start_ts, const glide_computer std::cout << STD_FMT::format( "| {:<12} | {:>9%.1Q %q} (Total: {:>9%.1Q %q}) | {:>8%.1Q %q} (Total: {:>8%.1Q %q}) | {:>7%.0Q %q} ({:>6%.0Q %q}) " "|\n", - phase_name, quantity_cast(new_point.ts - point.ts), quantity_cast(new_point.ts - start_ts), + phase_name, value_cast(new_point.ts - point.ts), value_cast(new_point.ts - start_ts), new_point.dist - point.dist, new_point.dist, new_point.alt - point.alt, new_point.alt); } diff --git a/example/glide_computer_example.cpp b/example/glide_computer_example.cpp index 75c7b9b8..22cc93f0 100644 --- a/example/glide_computer_example.cpp +++ b/example/glide_computer_example.cpp @@ -85,11 +85,11 @@ void print(const R& gliders) std::cout << "- Name: " << g.name << "\n"; std::cout << "- Polar:\n"; for (const auto& p : g.polar) { - const auto ratio = quantity_cast(glide_ratio(g.polar[0])); + const auto ratio = value_cast(glide_ratio(g.polar[0])); std::cout << STD_FMT::format(" * {:%.4Q %q} @ {:%.1Q %q} -> {:%.1Q %q} ({:%.1Q %q})\n", p.climb, p.v, ratio, // TODO is it possible to make ADL work below (we need another set of trig functions // for strong angle in a different namespace) - quantity_cast(isq::asin(1 / ratio))); + value_cast(isq::asin(1 / ratio))); } std::cout << "\n"; } diff --git a/example/hello_units.cpp b/example/hello_units.cpp index 194ffa6f..4acc8e0a 100644 --- a/example/hello_units.cpp +++ b/example/hello_units.cpp @@ -43,9 +43,9 @@ int main() constexpr auto v2 = isq::speed(70., mph); constexpr auto v3 = avg_speed(isq::distance(220, km), isq::duration(2, h)); constexpr auto v4 = avg_speed(quantity{140}, quantity{2}); - constexpr auto v5 = quantity_cast>(v3); - constexpr auto v6 = quantity_cast(v4); - constexpr auto v7 = quantity_cast(v6); + constexpr auto v5 = value_cast(v3); + constexpr auto v6 = value_cast(v4); + constexpr auto v7 = value_cast(v6); std::cout << v1 << '\n'; // 110 km/h std::cout << v2 << '\n'; // 70 mi/h diff --git a/example/linear_algebra.cpp b/example/linear_algebra.cpp index a18b1330..b159bb75 100644 --- a/example/linear_algebra.cpp +++ b/example/linear_algebra.cpp @@ -95,7 +95,7 @@ void quantity_of_vector_cast() std::cout << "v = " << v << "\n"; std::cout << "u = " << u << "\n"; - std::cout << "v[km] = " << quantity_cast(v) << "\n"; + std::cout << "v[km] = " << value_cast(v) << "\n"; std::cout << "u[m] = " << u[m] << "\n"; } diff --git a/example/total_energy.cpp b/example/total_energy.cpp index dcec9e92..7c5e04c7 100644 --- a/example/total_energy.cpp +++ b/example/total_energy.cpp @@ -80,7 +80,7 @@ void si_example() << "E = " << E3 << "\n"; std::cout << "\n[converted from SI units back to GeV]\n" - << "E = " << quantity_cast(E3) << "\n"; + << "E = " << value_cast(E3) << "\n"; } void natural_example() diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7248c12c..1dc6bc50 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -54,9 +54,11 @@ add_library( include/mp_units/bits/ratio.h include/mp_units/bits/reference_concepts.h include/mp_units/bits/representation_concepts.h + include/mp_units/bits/sudo_cast.h include/mp_units/bits/symbol_text.h include/mp_units/bits/text_tools.h include/mp_units/bits/unit_concepts.h + include/mp_units/bits/value_cast.h include/mp_units/concepts.h include/mp_units/customization_points.h diff --git a/src/core/include/mp_units/bits/quantity_cast.h b/src/core/include/mp_units/bits/quantity_cast.h index 780ed487..2654842f 100644 --- a/src/core/include/mp_units/bits/quantity_cast.h +++ b/src/core/include/mp_units/bits/quantity_cast.h @@ -25,197 +25,42 @@ #include #include #include +#include +#include +#include #include #include #include #include -UNITS_DIAGNOSTIC_PUSH -// warning C4244: 'argument': conversion from 'intmax_t' to 'T', possible loss of data with T=int -UNITS_DIAGNOSTIC_IGNORE_LOSS_OF_DATA - namespace mp_units { template Rep> class quantity; -// template U, Representation Rep> -// class quantity_point; - /** - * @brief Explicit cast of a quantity + * @brief Explicit cast of a quantity type * - * Implicit conversions between quantities of different types are allowed only for "safe" - * (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used. + * This cast converts only a quantity type. It might be used to force some quantity type + * conversions that are not implicitly allowed but are allowed explicitly. * - * This cast gets the target quantity type to cast to. For example: + * For example: * - * auto q1 = 1234. * isq::length[mm]; - * auto q2 = quantity_cast>(q1); + * @code{.cpp} + * auto length = isq::length(42 * m); + * auto distance = quantity_cast(length); + * @endcode * - * @tparam To a target quantity type to cast to - */ -template - requires(interconvertible(To::reference, R)) && - ((get_unit(R) == To::unit && std::constructible_from) || - (get_unit(R) != To::unit)) // && scalable_with_)) -// TODO how to constrain the second part here? -[[nodiscard]] constexpr auto quantity_cast(const quantity& q) -{ - if constexpr (get_unit(R) == To::unit) { - // 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 { - // 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(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 To(static_cast(static_cast(q.number()) * val(num) / val(den) * val(irr))); - } -} - -/** - * @brief Explicit cast of a quantity - * - * Implicit conversions between quantities of different types are allowed only for "safe" - * (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used. - * - * This cast gets only the target quantity specification to cast to. For example: - * - * auto v = quantity_cast(120 * isq::length[km] / (2 * isq::time[h])); + * @note This cast does not affect the underlying value of a number stored in a quantity. * * @tparam ToQS a quantity specification to use for a target quantity */ -template - requires(interconvertible(ToQS, get_quantity_spec(R))) -[[nodiscard]] constexpr auto quantity_cast(const quantity& q) +template + requires(interconvertible(ToQ, get_quantity_spec(R))) +[[nodiscard]] constexpr Quantity auto quantity_cast(const quantity& q) { - constexpr reference::unit> r; - return quantity_cast>(q); + constexpr reference::unit> r; + return q.count() * r; } -/** - * @brief Explicit cast of a quantity - * - * Implicit conversions between quantities of different types are allowed only for "safe" - * (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used. - * - * This cast gets only the target unit to cast to. For example: - * - * auto d = quantity_cast(1234 * isq::time[ms]); - * - * @tparam ToU a unit to use for a target quantity - */ -template - requires(interconvertible(ToU, get_unit(R))) -[[nodiscard]] constexpr auto quantity_cast(const quantity& q) -{ - constexpr reference::quantity_spec, ToU> r; - return quantity_cast>(q); -} - -/** - * @brief Explicit cast of a quantity - * - * Implicit conversions between quantities of different types are allowed only for "safe" - * (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used. - * - * This cast gets only representation to cast to. For example: - * - * auto q = quantity_cast(1.23 * isq::time[ms]); - * - * @tparam ToRep a representation type to use for a target quantity - */ -template - requires RepresentationOf && std::constructible_from -[[nodiscard]] constexpr auto quantity_cast(const quantity& q) -{ - return quantity_cast>(q); -} - -// /** -// * @brief Explicit cast of a quantity point -// * -// * Implicit conversions between quantity points 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 type to cast to or anything that works for quantity_cast. For example: -// * -// * auto q1 = mp_units::quantity_point_cast(quantity_point{1_q_ms}); -// * auto q1 = -// mp_units::quantity_point_cast>(quantity_point{1_q_ms}); -// * auto q1 = mp_units::quantity_point_cast(quantity_point{200_q_Gal}); -// * auto q1 = mp_units::quantity_point_cast(quantity_point{1_q_ms}); -// * auto q1 = mp_units::quantity_point_cast(quantity_point{1_q_ms}); -// * -// * @tparam CastSpec a target quantity point type to cast to or anything that works for quantity_cast -// */ -// template -// [[nodiscard]] constexpr auto quantity_point_cast(const quantity_point& qp) -// requires requires { -// requires is_specialization_of; -// requires requires { quantity_cast(qp.relative()); }; -// requires equivalent; -// } || // TODO: Simplify when Clang catches up. -// requires { quantity_cast(qp.relative()); } -// { -// if constexpr (is_specialization_of) -// return quantity_point(quantity_cast(qp.relative())); -// else -// return quantity_point(quantity_cast(qp.relative())); -// } - -// /** -// * @brief Explicit cast of a quantity point -// * -// * Implicit conversions between quantity points 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 both the target dimension and unit to cast to. For example: -// * -// * auto q1 = mp_units::quantity_point_cast(v1); -// * -// * @note This cast is especially useful when working with quantity points of unknown dimensions -// * (@c unknown_dimension). -// * -// * @tparam ToD a dimension type to use for a target quantity -// * @tparam ToU a unit type to use for a target quantity -// */ -// template -// requires equivalent && UnitOf && RebindablePointOriginFor -// [[nodiscard]] constexpr auto quantity_point_cast(const quantity_point& q) -// { -// return quantity_point_cast, ToU, Rep>>(q); -// } - } // namespace mp_units - -UNITS_DIAGNOSTIC_POP diff --git a/src/core/include/mp_units/bits/representation_concepts.h b/src/core/include/mp_units/bits/representation_concepts.h index aba35c59..0d49ca11 100644 --- a/src/core/include/mp_units/bits/representation_concepts.h +++ b/src/core/include/mp_units/bits/representation_concepts.h @@ -63,7 +63,7 @@ template concept castable_number_ = // exposition only common_type_with_ && scalable_number_>; -// TODO Fix it according to quantity_cast implementation +// TODO Fix it according to sudo_cast implementation template concept scalable_ = // exposition only castable_number_ || (requires { typename T::value_type; } && castable_number_ && diff --git a/src/core/include/mp_units/bits/sudo_cast.h b/src/core/include/mp_units/bits/sudo_cast.h new file mode 100644 index 00000000..d16041b4 --- /dev/null +++ b/src/core/include/mp_units/bits/sudo_cast.h @@ -0,0 +1,93 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include +#include +#include + +namespace mp_units { + +template Rep> +class quantity; + +namespace detail { + +/** + * @brief Explicit cast of entire quantity + * + * @note This is too powerful to be used by users. + * + * @tparam To a target quantity type to cast to + */ +template + requires(interconvertible(To::reference, R)) && + ((get_unit(R) == To::unit && std::constructible_from) || + (get_unit(R) != To::unit)) // && scalable_with_)) +// TODO how to constrain the second part here? +[[nodiscard]] constexpr Quantity auto sudo_cast(const quantity& q) +{ + if constexpr (get_unit(R) == To::unit) { + // 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 { + // 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(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 To(static_cast(static_cast(q.number()) * val(num) / val(den) * val(irr))); + } +} + +} // namespace detail +} // namespace mp_units diff --git a/src/core/include/mp_units/bits/value_cast.h b/src/core/include/mp_units/bits/value_cast.h new file mode 100644 index 00000000..419a50a7 --- /dev/null +++ b/src/core/include/mp_units/bits/value_cast.h @@ -0,0 +1,74 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include +#include + +namespace mp_units { + +template Rep> +class quantity; + +/** + * @brief Explicit cast of a quantity's unit + * + * Implicit conversions between quantities of different types are allowed only for "safe" + * (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used. + * + * auto d = value_cast(1234 * ms); + * + * @tparam ToU a unit to use for a target quantity + */ +template + requires(interconvertible(ToU, get_unit(R))) +[[nodiscard]] constexpr Quantity auto value_cast(const quantity& q) +{ + if constexpr (detail::is_specialization_of_reference || !AssociatedUnit) { + constexpr reference::quantity_spec, ToU> r; + return detail::sudo_cast>(q); + } else { + return detail::sudo_cast>(q); + } +} + +/** + * @brief Explicit cast of a quantity's representation type + * + * Implicit conversions between quantities of different types are allowed only for "safe" + * (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used. + * + * auto q = value_cast(1.23 * ms); + * + * @tparam ToRep a representation type to use for a target quantity + */ +template + requires RepresentationOf && std::constructible_from +[[nodiscard]] constexpr Quantity auto value_cast(const quantity& q) +{ + return detail::sudo_cast>(q); +} + +} // namespace mp_units diff --git a/src/core/include/mp_units/quantity.h b/src/core/include/mp_units/quantity.h index d186f186..ff7d5a0d 100644 --- a/src/core/include/mp_units/quantity.h +++ b/src/core/include/mp_units/quantity.h @@ -24,11 +24,11 @@ #pragma once #include -#include #include #include #include #include +#include #include #include #include @@ -36,8 +36,6 @@ #include #include #include -// IWYU pragma: end_exports - #include namespace mp_units { @@ -62,7 +60,7 @@ concept Harmonic = // exposition only template concept QuantityConvertibleTo = // exposition only - Quantity && Quantity && requires(QFrom q) { quantity_cast(q); } && + Quantity && Quantity && requires(QFrom q) { detail::sudo_cast(q); } && (treat_as_floating_point || (!treat_as_floating_point && Harmonic)); @@ -129,7 +127,7 @@ public: } template Q> - constexpr explicit(false) quantity(const Q& q) : number_(quantity_cast(q).number()) + constexpr explicit(false) quantity(const Q& q) : number_(detail::sudo_cast(q).number()) { } @@ -514,7 +512,7 @@ template [[nodiscard]] constexpr auto operator<=>(const Q1& lhs, const Q2& rhs) { constexpr auto ref = common_reference(Q1::reference, Q2::reference); - return quantity_cast(lhs).number() <=> quantity_cast(rhs).number(); + return quantity(lhs).number() <=> quantity(rhs).number(); } template @@ -523,7 +521,7 @@ template [[nodiscard]] constexpr bool operator==(const Q1& lhs, const Q2& rhs) { constexpr auto ref = common_reference(Q1::reference, Q2::reference); - return quantity_cast(lhs).number() == quantity_cast(rhs).number(); + return quantity(lhs).number() == quantity(rhs).number(); } } // namespace mp_units diff --git a/src/core/include/mp_units/unit.h b/src/core/include/mp_units/unit.h index 651138d8..03406b86 100644 --- a/src/core/include/mp_units/unit.h +++ b/src/core/include/mp_units/unit.h @@ -158,7 +158,7 @@ struct named_unit : std::remove_const_t { * operations. Also, if the user prefers integral types for a quantity representation, this will * not force the user to convert to a floating-point type right away. Only when a final quantity * number needs to actually account for the constant value, the floating-point operation (if any) - * can be triggered lazily with the `quantity_cast()`. + * can be triggered lazily with the `value_cast()`. * * For example: * diff --git a/test/unit_test/runtime/fmt_test.cpp b/test/unit_test/runtime/fmt_test.cpp index 0572cfc3..8fa21504 100644 --- a/test/unit_test/runtime/fmt_test.cpp +++ b/test/unit_test/runtime/fmt_test.cpp @@ -205,7 +205,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("percents") { - const auto q = quantity_cast(15. * isq::length[m] / (100. * isq::length[m])); + const auto q = value_cast(15. * isq::length[m] / (100. * isq::length[m])); os << q; SECTION("iostream") { CHECK(os.str() == "15 %"); } @@ -724,7 +724,7 @@ TEST_CASE("localization with the 'L' specifier", "[text][fmt][localization]") } } -TEST_CASE("quantity_cast", "[text][ostream]") +TEST_CASE("value_cast", "[text][ostream]") { std::ostringstream os; @@ -740,13 +740,13 @@ TEST_CASE("quantity_cast", "[text][ostream]") SECTION("int") { - os << quantity_cast(q); + os << value_cast(q); CHECK(os.str() == "60 km/h"); } SECTION("double") { - os << quantity_cast(q); + os << value_cast(q); CHECK(os.str() == "60 km/h"); } } @@ -763,13 +763,13 @@ TEST_CASE("quantity_cast", "[text][ostream]") SECTION("int") { - os << quantity_cast(q); + os << value_cast(q); CHECK(os.str() == "60 km/h"); } SECTION("double") { - os << quantity_cast(q); + os << value_cast(q); CHECK(os.str() == "60.5 km/h"); } } diff --git a/test/unit_test/runtime/linear_algebra_test.cpp b/test/unit_test/runtime/linear_algebra_test.cpp index e64ec2af..3dcfd48c 100644 --- a/test/unit_test/runtime/linear_algebra_test.cpp +++ b/test/unit_test/runtime/linear_algebra_test.cpp @@ -105,7 +105,7 @@ TEST_CASE("vector quantity", "[la]") SECTION("truncating") { const quantity> v{vector{1001, 1002, 1003}}; - CHECK(quantity_cast(v).number() == vector{1, 1, 1}); + CHECK(value_cast(v).number() == vector{1, 1, 1}); } } diff --git a/test/unit_test/static/angle_test.cpp b/test/unit_test/static/angle_test.cpp index 726ab2d4..3c9c690b 100644 --- a/test/unit_test/static/angle_test.cpp +++ b/test/unit_test/static/angle_test.cpp @@ -37,7 +37,7 @@ static_assert(std::numbers::pi * 2 * rad == 1. * rev); static_assert(360_q_deg == 1_q_rev); static_assert(400_q_grad == 1_q_rev); -static_assert(std::numbers::pi * quantity_cast(2._q_rad) == quantity_cast(1._q_rev)); +static_assert(std::numbers::pi * value_cast(2._q_rad) == value_cast(1._q_rev)); static_assert(mp_units::aliases::deg<>(360.) == mp_units::aliases::rev<>(1.)); static_assert(mp_units::aliases::deg(360) == mp_units::aliases::rev(1));