From 2cff579650b1eb7f8beee02dacfb6350f9068230 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Thu, 4 Jul 2024 22:05:40 +0100 Subject: [PATCH] feat: representation type template parameter added to value convertion functions Resolves #588 --- .../framework_basics/value_conversions.md | 53 ++++++++++++------- .../include/mp-units/framework/quantity.h | 28 ++++++++++ .../mp-units/framework/quantity_point.h | 28 ++++++++++ test/static/quantity_point_test.cpp | 8 +++ test/static/quantity_test.cpp | 15 +++++- 5 files changed, 112 insertions(+), 20 deletions(-) diff --git a/docs/users_guide/framework_basics/value_conversions.md b/docs/users_guide/framework_basics/value_conversions.md index bb9d6559..1ec7e340 100644 --- a/docs/users_guide/framework_basics/value_conversions.md +++ b/docs/users_guide/framework_basics/value_conversions.md @@ -148,30 +148,45 @@ Price price{12.95 * USD}; Scaled spx = value_cast(price); ``` -As a shortcut, instead of providing a unit and a representation type to `value_cast`, you may also provide a -`Quantity` type directly, from which unit and representation type are taken. However, `value_cast`, -still only allows for changes in unit and representation type, but not changing the type of the quantity. -For that, you will have to use a `quantity_cast` instead. +As a shortcut, instead of providing a unit and a representation type to `value_cast`, you may also +provide a `Quantity` type directly, from which unit and representation type are taken. However, +`value_cast`, still only allows for changes in unit and representation type, but not +changing the type of the quantity. For that, you will have to use a `quantity_cast` instead. + +Overloads are also provided for instances of `quantity_point`. All variants of `value_cast<...>(q)` +that apply to instances of `quantity` have a corresponding version applicable to `quantity_point`, +where the `point_origin` remains untouched, and the cast changes how the "offset" from the origin +is represented. Specifically, for any `quantity_point` instance `qp`, all of the following +equivalences hold: -Overloads are also provided for instances of `quantity_point`. -All variants of `value_cast<...>(q)` that apply to instances of `quantity` -have a corresponding version applicable to `quantity_point`, where the `point_origin` remains untouched, -and the cast changes how the "offset" from the origin is represented. -Specifically, for any `quantity_point` instance `qp`, all of the following equivalences hold: ```cpp -static_assert( value_cast(qp) == quantity_point{value_cast(qp.quantity_from(qp.point_origin)), qp.point_origin} ); -static_assert( value_cast(qp) == quantity_point{value_cast(qp.quantity_from(qp.point_origin)), qp.point_origin} ); -static_assert( value_cast(qp) == quantity_point{value_cast(qp.quantity_from(qp.point_origin)), qp.point_origin} ); -static_assert( value_cast(qp) == quantity_point{value_cast(qp.quantity_from(qp.point_origin)), qp.point_origin} ); +static_assert(value_cast(qp) == quantity_point{value_cast(qp.quantity_from(qp.point_origin)), qp.point_origin}); +static_assert(value_cast(qp) == quantity_point{value_cast(qp.quantity_from(qp.point_origin)), qp.point_origin}); +static_assert(value_cast(qp) == quantity_point{value_cast(qp.quantity_from(qp.point_origin)), qp.point_origin}); +static_assert(value_cast(qp) == quantity_point{value_cast(qp.quantity_from(qp.point_origin)), qp.point_origin}); ``` -Furthermore, there is one additional overload `value_cast(qp)`. -This overload permits to additionally replace the `point_origin` with another compatible one, -while still representing the same point in the affine space. -Thus, it is roughly equivalent to +Furthermore, there is one additional overload `value_cast(qp)`. This overload permits to +additionally replace the `point_origin` with another compatible one, while still representing +the same point in the affine space. Thus, it is roughly equivalent to `value_cast(qp).point_for(ToQP::point_origin)`. In contrast to a separate `value_cast` followed by `point_for` (or vice-versa), the combined -`value_cast` tries to choose the order of the individual conversion steps in a way -to avoid both overflow and unnecessary loss of precision. Overflow is a risk because the change of origin point +`value_cast` tries to choose the order of the individual conversion steps in a way to avoid both +overflow and unnecessary loss of precision. Overflow is a risk because the change of origin point may require an addition of a potentially large offset (the difference between the origin points), which may well be outside the range of one or both quantity types. + + +## Value conversions summary + +The table below provides all the value conversions functions that may be run on `x` being the +instance of either `quantity` or `quantity_point`: + +| Forcing | Representation | Unit | Member function | Conversion function | +|:-------:|:--------------:|:----:|--------------------|-----------------------| +| No | Same | `u` | `x.in(u)` | | +| No | `T` | Same | `x.in()` | | +| No | `T` | `u` | `x.in(u)` | | +| Yes | Same | `u` | `x.force_in(u)` | `value_cast(x)` | +| Yes | `T` | Same | `x.force_in()` | `value_cast(x)` | +| Yes | `T` | `u` | `x.force_in(u)` | `value_cast(x)` | diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index 36f5216d..960835b3 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -199,6 +199,20 @@ public: return quantity{*this}; } + template ToRep> + requires detail::QuantityConvertibleTo> + [[nodiscard]] constexpr QuantityOf auto in() const + { + return quantity{*this}; + } + + template ToRep, detail::UnitCompatibleWith ToU> + requires detail::QuantityConvertibleTo> + [[nodiscard]] constexpr QuantityOf auto in(ToU) const + { + return quantity{*this}; + } + template ToU> requires requires(quantity q) { value_cast(q); } [[nodiscard]] constexpr QuantityOf auto force_in(ToU) const @@ -206,6 +220,20 @@ public: return value_cast(*this); } + template ToRep> + requires requires(quantity q) { value_cast(q); } + [[nodiscard]] constexpr QuantityOf auto force_in() const + { + return value_cast(*this); + } + + template ToRep, detail::UnitCompatibleWith ToU> + requires requires(quantity q) { value_cast(q); } + [[nodiscard]] constexpr QuantityOf auto force_in(ToU) const + { + return value_cast(*this); + } + // data access template requires(U{} == unit) diff --git a/src/core/include/mp-units/framework/quantity_point.h b/src/core/include/mp-units/framework/quantity_point.h index af075311..1b5e22ab 100644 --- a/src/core/include/mp-units/framework/quantity_point.h +++ b/src/core/include/mp-units/framework/quantity_point.h @@ -284,6 +284,20 @@ public: return ::mp_units::quantity_point{quantity_ref_from(PO).in(ToU{}), PO}; } + template ToRep> + requires detail::QuantityConvertibleTo> + [[nodiscard]] constexpr QuantityPointOf auto in() const + { + return ::mp_units::quantity_point{quantity_ref_from(PO).template in(), PO}; + } + + template ToRep, detail::UnitCompatibleWith ToU> + requires detail::QuantityConvertibleTo> + [[nodiscard]] constexpr QuantityPointOf auto in(ToU) const + { + return ::mp_units::quantity_point{quantity_ref_from(PO).template in(ToU{}), PO}; + } + template ToU> requires requires(quantity_type q) { value_cast(q); } [[nodiscard]] constexpr QuantityPointOf auto force_in(ToU) const @@ -291,6 +305,20 @@ public: return ::mp_units::quantity_point{quantity_ref_from(PO).force_in(ToU{}), PO}; } + template ToRep> + requires requires(quantity_type q) { value_cast(q); } + [[nodiscard]] constexpr QuantityPointOf auto force_in() const + { + return ::mp_units::quantity_point{quantity_ref_from(PO).template force_in(), PO}; + } + + template ToRep, detail::UnitCompatibleWith ToU> + requires requires(quantity_type q) { value_cast(q); } + [[nodiscard]] constexpr QuantityPointOf auto force_in(ToU) const + { + return ::mp_units::quantity_point{quantity_ref_from(PO).template force_in(ToU{}), PO}; + } + // conversion operators template> requires(point_origin == quantity_point_like_traits::point_origin) && diff --git a/test/static/quantity_point_test.cpp b/test/static/quantity_point_test.cpp index 55ce3d2b..39ea755f 100644 --- a/test/static/quantity_point_test.cpp +++ b/test/static/quantity_point_test.cpp @@ -863,6 +863,14 @@ static_assert((tower_peak + 2. * km).in(km).quantity_from(tower_peak).numerical_ static_assert((tower_peak + 2. * km).in(m).quantity_from(tower_peak).numerical_value_in(m) == 2000.); static_assert((tower_peak + 2000. * m).in(km).quantity_from(tower_peak).numerical_value_in(km) == 2.); +static_assert(is_of_type<(mean_sea_level + 2 * km).in(m), quantity_point>); +static_assert(is_of_type<(mean_sea_level + 2 * km).in(), quantity_point>); +static_assert(is_of_type<(mean_sea_level + 2 * km).in(m), quantity_point>); + +static_assert(is_of_type<(mean_sea_level + 2500. * m).force_in(km), quantity_point>); +static_assert(is_of_type<(mean_sea_level + 2500. * m).force_in(), quantity_point>); +static_assert(is_of_type<(mean_sea_level + 2500. * m).force_in(km), quantity_point>); + template typename QP> concept invalid_unit_conversion = requires { requires !requires { QP(2000 * m).in(km); }; // truncating conversion diff --git a/test/static/quantity_test.cpp b/test/static/quantity_test.cpp index 5d9161be..b813731d 100644 --- a/test/static/quantity_test.cpp +++ b/test/static/quantity_test.cpp @@ -208,6 +208,14 @@ static_assert(is_of_type<(2. * km).in(m), quantity>); static_assert(is_of_type>); static_assert(is_of_type>); +static_assert(is_of_type<(2 * km).in(m), quantity>); +static_assert(is_of_type(m), quantity>); +static_assert(is_of_type(m), quantity>); + +static_assert(is_of_type<(2 * m).in(), quantity>); +static_assert(is_of_type(), quantity>); +static_assert(is_of_type(), quantity>); + static_assert(quantity(2. * km).in(km).numerical_value_in(km) == 2.); static_assert(quantity(2. * km).in(m).numerical_value_in(m) == 2000.); static_assert(quantity(2000. * m).in(km).numerical_value_in(km) == 2.); @@ -923,13 +931,18 @@ static_assert((50. * percent).numerical_value_in(one) == 0.5); static_assert(value_cast(2 * km).numerical_value_in(m) == 2000); static_assert(value_cast(2000 * m).numerical_value_in(km) == 2); -static_assert(value_cast(1.23 * m).numerical_value_in(m) == 1); static_assert(value_cast(2000.0 * m / (3600.0 * s)).numerical_value_in(km / h) == 2); +static_assert(value_cast(1.23 * m).numerical_value_in(m) == 1); +static_assert(value_cast(1.23 * m).numerical_value_in(km) == 0); + static_assert((2 * km).force_in(m).numerical_value_in(m) == 2000); static_assert((2000 * m).force_in(km).numerical_value_in(km) == 2); static_assert((2000.0 * m / (3600.0 * s)).force_in(km / h).numerical_value_in(km / h) == 2); +static_assert((1.23 * m).force_in().numerical_value_in(m) == 1); +static_assert((1.23 * m).force_in(km).numerical_value_in(km) == 0); + ////////////////// // quantity_cast //////////////////