diff --git a/CHANGELOG.md b/CHANGELOG.md index 68644208..26e8a6b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - feat: `fma`, `isfinite`, `isinf`, and `isnan` math function added by @NAThompson - feat: `quantity_point` support added for `quantity_cast` and `value_cast` +- feat: `value_cast` added - (!) refactor: `zero_Fahrenheit` renamed to `zeroth_Fahrenheit` - refactor: math functions constraints refactored - fix: `QuantityLike` conversions required `Q::rep` instead of using one provided by `quantity_like_traits` diff --git a/docs/users_guide/examples/avg_speed.md b/docs/users_guide/examples/avg_speed.md index 1fc1c401..c2aade6c 100644 --- a/docs/users_guide/examples/avg_speed.md +++ b/docs/users_guide/examples/avg_speed.md @@ -83,7 +83,7 @@ Next, let's do the same for integral and floating-point representations, but thi using US Customary units: ```cpp title="avg_speed.cpp" linenums="54" ---8<-- "example/avg_speed.cpp:93:125" +--8<-- "example/avg_speed.cpp:93:124" ``` One important difference here is the fact that as it is not possible to make a lossless conversion @@ -108,8 +108,8 @@ Please note how the first and third results get truncated using integral represe In the end, we repeat the scenario for CGS units: -```cpp title="avg_speed.cpp" linenums="87" ---8<-- "example/avg_speed.cpp:127:157" +```cpp title="avg_speed.cpp" linenums="86" +--8<-- "example/avg_speed.cpp:126:155" ``` Again, we observe `value_cast` being used in the same places and consistent truncation errors @@ -129,6 +129,6 @@ Average speed of a car that makes 2.2e+07 cm in 7200 s is 110 km/h. The example file ends with a simple `main()` function: -```cpp title="avg_speed.cpp" linenums="118" ---8<-- "example/avg_speed.cpp:159:" +```cpp title="avg_speed.cpp" linenums="116" +--8<-- "example/avg_speed.cpp:157:" ``` diff --git a/docs/users_guide/framework_basics/value_conversions.md b/docs/users_guide/framework_basics/value_conversions.md index 4eacf947..2b634436 100644 --- a/docs/users_guide/framework_basics/value_conversions.md +++ b/docs/users_guide/framework_basics/value_conversions.md @@ -77,3 +77,76 @@ quantity q3 = value_cast(3.14 * m); It is often fine to use an integral as a representation type, but in general, floating-point types provide better precision and are privileged in the library as they are considered to be value-preserving. + +In some cases, a unit and a representation type should be changed simultaneously. Moreover, +sometimes, the order of doing those operations matters. In such cases, the library provides +the `value_cast(q)` which always returns the most precise result: + +=== "C++23" + + ```cpp + inline constexpr struct dim_currency : base_dimension<"$"> {} dim_currency; + inline constexpr struct currency : quantity_spec {} currency; + + inline constexpr struct us_dollar : named_unit<"USD", kind_of> {} us_dollar; + inline constexpr struct scaled_us_dollar : named_unit<"USD_s", mag_power<10, -8> * us_dollar> {} scaled_us_dollar; + + namespace unit_symbols { + + inline constexpr auto USD = us_dollar; + inline constexpr auto USD_s = scaled_us_dollar; + + } // namespace unit_symbols + + using Price = quantity; + using Scaled = quantity; + + Price price = 12.95 * USD; + Scaled spx = value_cast(price); + ``` + +=== "C++20" + + ```cpp + inline constexpr struct dim_currency : base_dimension<"$"> {} dim_currency; + inline constexpr struct currency : quantity_spec {} currency; + + inline constexpr struct us_dollar : named_unit<"USD", kind_of> {} us_dollar; + inline constexpr struct scaled_us_dollar : named_unit<"USD_s", mag_power<10, -8> * us_dollar> {} scaled_us_dollar; + + namespace unit_symbols { + + inline constexpr auto USD = us_dollar; + inline constexpr auto USD_s = scaled_us_dollar; + + } // namespace unit_symbols + + using Price = quantity; + using Scaled = quantity; + + Price price = 12.95 * USD; + Scaled spx = value_cast(price); + ``` + +=== "Portable" + + ```cpp + inline constexpr struct dim_currency : base_dimension<"$"> {} dim_currency; + QUANTITY_SPEC(currency, dim_currency); + + inline constexpr struct us_dollar : named_unit<"USD", kind_of> {} us_dollar; + inline constexpr struct scaled_us_dollar : named_unit<"USD_s", mag_power<10, -8> * us_dollar> {} scaled_us_dollar; + + namespace unit_symbols { + + inline constexpr auto USD = us_dollar; + inline constexpr auto USD_s = scaled_us_dollar; + + } // namespace unit_symbols + + using Price = quantity; + using Scaled = quantity; + + Price price = 12.95 * USD; + Scaled spx = value_cast(price); + ``` diff --git a/example/avg_speed.cpp b/example/avg_speed.cpp index c59bacb7..6744ede1 100644 --- a/example/avg_speed.cpp +++ b/example/avg_speed.cpp @@ -118,8 +118,7 @@ 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(value_cast(distance.force_in(m)), value_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)); } @@ -148,8 +147,7 @@ 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(value_cast(distance.force_in(m)), value_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)); diff --git a/src/core/include/mp-units/bits/value_cast.h b/src/core/include/mp-units/bits/value_cast.h index 88316ad8..eb9dbd14 100644 --- a/src/core/include/mp-units/bits/value_cast.h +++ b/src/core/include/mp-units/bits/value_cast.h @@ -75,6 +75,26 @@ template return detail::sudo_cast::reference, ToRep>>(std::forward(q)); } +/** + * @brief Explicit cast of a quantity's unit and representation type + * + * Implicit conversions between quantities of different types are allowed only for "safe" + * (e.g. 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 Quantity> && (convertible(std::remove_reference_t::reference, ToU)) && + RepresentationOf::quantity_spec.character> && + std::constructible_from::rep> +[[nodiscard]] constexpr Quantity auto value_cast(Q&& q) +{ + using q_type = std::remove_reference_t; + return detail::sudo_cast>(std::forward(q)); +} + /** * @brief Explicit cast of a quantity point's unit * @@ -112,4 +132,24 @@ template return {value_cast(std::forward(qp).quantity_from_origin_is_an_implementation_detail_), qp.point_origin}; } +/** + * @brief Explicit cast of a quantity's unit and representation type + * + * Implicit conversions between quantities of different types are allowed only for "safe" + * (e.g. 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 QuantityPoint> && (convertible(std::remove_reference_t::reference, ToU)) && + RepresentationOf::quantity_spec.character> && + std::constructible_from::rep> +[[nodiscard]] constexpr QuantityPoint auto value_cast(QP&& qp) +{ + return quantity_point{value_cast(std::forward(qp).quantity_from_origin_is_an_implementation_detail_), + qp.point_origin}; +} + } // namespace mp_units