feat: representation type template parameter added to value convertion functions

Resolves #588
This commit is contained in:
Mateusz Pusz
2024-07-04 22:05:40 +01:00
parent 5903e5661f
commit 2cff579650
5 changed files with 112 additions and 20 deletions

View File

@ -148,30 +148,45 @@ Price price{12.95 * USD};
Scaled spx = value_cast<USD_s, std::int64_t>(price); Scaled spx = value_cast<USD_s, std::int64_t>(price);
``` ```
As a shortcut, instead of providing a unit and a representation type to `value_cast`, you may also provide a As a shortcut, instead of providing a unit and a representation type to `value_cast`, you may also
`Quantity` type directly, from which unit and representation type are taken. However, `value_cast<Quantity>`, provide a `Quantity` type directly, from which unit and representation type are taken. However,
still only allows for changes in unit and representation type, but not changing the type of the quantity. `value_cast<Quantity>`, still only allows for changes in unit and representation type, but not
For that, you will have to use a `quantity_cast` instead. 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 ```cpp
static_assert( value_cast<Rep>(qp) == quantity_point{value_cast<Rep>(qp.quantity_from(qp.point_origin)), qp.point_origin} ); static_assert(value_cast<Rep>(qp) == quantity_point{value_cast<Rep>(qp.quantity_from(qp.point_origin)), qp.point_origin});
static_assert( value_cast<U>(qp) == quantity_point{value_cast<U>(qp.quantity_from(qp.point_origin)), qp.point_origin} ); static_assert(value_cast<U>(qp) == quantity_point{value_cast<U>(qp.quantity_from(qp.point_origin)), qp.point_origin});
static_assert( value_cast<U, Rep>(qp) == quantity_point{value_cast<U, Rep>(qp.quantity_from(qp.point_origin)), qp.point_origin} ); static_assert(value_cast<U, Rep>(qp) == quantity_point{value_cast<U, Rep>(qp.quantity_from(qp.point_origin)), qp.point_origin});
static_assert( value_cast<Q>(qp) == quantity_point{value_cast<Q>(qp.quantity_from(qp.point_origin)), qp.point_origin} ); static_assert(value_cast<Q>(qp) == quantity_point{value_cast<Q>(qp.quantity_from(qp.point_origin)), qp.point_origin});
``` ```
Furthermore, there is one additional overload `value_cast<ToQP>(qp)`. Furthermore, there is one additional overload `value_cast<ToQP>(qp)`. This overload permits to
This overload permits to additionally replace the `point_origin` with another compatible one, additionally replace the `point_origin` with another compatible one, while still representing
while still representing the same point in the affine space. the same point in the affine space. Thus, it is roughly equivalent to
Thus, it is roughly equivalent to
`value_cast<ToQP::unit, ToQP::rep>(qp).point_for(ToQP::point_origin)`. `value_cast<ToQP::unit, ToQP::rep>(qp).point_for(ToQP::point_origin)`.
In contrast to a separate `value_cast` followed by `point_for` (or vice-versa), the combined 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 `value_cast` tries to choose the order of the individual conversion steps in a way to avoid both
to avoid both overflow and unnecessary loss of precision. Overflow is a risk because the change of origin point 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), 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. 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<T>()` | |
| No | `T` | `u` | `x.in<T>(u)` | |
| Yes | Same | `u` | `x.force_in(u)` | `value_cast<u>(x)` |
| Yes | `T` | Same | `x.force_in<T>()` | `value_cast<T>(x)` |
| Yes | `T` | `u` | `x.force_in<T>(u)` | `value_cast<u, T>(x)` |

View File

@ -199,6 +199,20 @@ public:
return quantity<detail::make_reference(quantity_spec, ToU{}), Rep>{*this}; return quantity<detail::make_reference(quantity_spec, ToU{}), Rep>{*this};
} }
template<RepresentationOf<get_quantity_spec(R).character> ToRep>
requires detail::QuantityConvertibleTo<quantity, quantity<reference, ToRep>>
[[nodiscard]] constexpr QuantityOf<quantity_spec> auto in() const
{
return quantity<reference, ToRep>{*this};
}
template<RepresentationOf<get_quantity_spec(R).character> ToRep, detail::UnitCompatibleWith<unit, quantity_spec> ToU>
requires detail::QuantityConvertibleTo<quantity, quantity<detail::make_reference(quantity_spec, ToU{}), ToRep>>
[[nodiscard]] constexpr QuantityOf<quantity_spec> auto in(ToU) const
{
return quantity<detail::make_reference(quantity_spec, ToU{}), ToRep>{*this};
}
template<detail::UnitCompatibleWith<unit, quantity_spec> ToU> template<detail::UnitCompatibleWith<unit, quantity_spec> ToU>
requires requires(quantity q) { value_cast<ToU{}>(q); } requires requires(quantity q) { value_cast<ToU{}>(q); }
[[nodiscard]] constexpr QuantityOf<quantity_spec> auto force_in(ToU) const [[nodiscard]] constexpr QuantityOf<quantity_spec> auto force_in(ToU) const
@ -206,6 +220,20 @@ public:
return value_cast<ToU{}>(*this); return value_cast<ToU{}>(*this);
} }
template<RepresentationOf<get_quantity_spec(R).character> ToRep>
requires requires(quantity q) { value_cast<ToRep>(q); }
[[nodiscard]] constexpr QuantityOf<quantity_spec> auto force_in() const
{
return value_cast<ToRep>(*this);
}
template<RepresentationOf<get_quantity_spec(R).character> ToRep, detail::UnitCompatibleWith<unit, quantity_spec> ToU>
requires requires(quantity q) { value_cast<ToU{}, ToRep>(q); }
[[nodiscard]] constexpr QuantityOf<quantity_spec> auto force_in(ToU) const
{
return value_cast<ToU{}, ToRep>(*this);
}
// data access // data access
template<Unit U> template<Unit U>
requires(U{} == unit) requires(U{} == unit)

View File

@ -284,6 +284,20 @@ public:
return ::mp_units::quantity_point{quantity_ref_from(PO).in(ToU{}), PO}; return ::mp_units::quantity_point{quantity_ref_from(PO).in(ToU{}), PO};
} }
template<RepresentationOf<get_quantity_spec(R).character> ToRep>
requires detail::QuantityConvertibleTo<quantity_type, quantity<reference, ToRep>>
[[nodiscard]] constexpr QuantityPointOf<quantity_spec> auto in() const
{
return ::mp_units::quantity_point{quantity_ref_from(PO).template in<ToRep>(), PO};
}
template<RepresentationOf<get_quantity_spec(R).character> ToRep, detail::UnitCompatibleWith<unit, quantity_spec> ToU>
requires detail::QuantityConvertibleTo<quantity_type, quantity<detail::make_reference(quantity_spec, ToU{}), ToRep>>
[[nodiscard]] constexpr QuantityPointOf<quantity_spec> auto in(ToU) const
{
return ::mp_units::quantity_point{quantity_ref_from(PO).template in<ToRep>(ToU{}), PO};
}
template<detail::UnitCompatibleWith<unit, quantity_spec> ToU> template<detail::UnitCompatibleWith<unit, quantity_spec> ToU>
requires requires(quantity_type q) { value_cast<ToU{}>(q); } requires requires(quantity_type q) { value_cast<ToU{}>(q); }
[[nodiscard]] constexpr QuantityPointOf<quantity_spec> auto force_in(ToU) const [[nodiscard]] constexpr QuantityPointOf<quantity_spec> auto force_in(ToU) const
@ -291,6 +305,20 @@ public:
return ::mp_units::quantity_point{quantity_ref_from(PO).force_in(ToU{}), PO}; return ::mp_units::quantity_point{quantity_ref_from(PO).force_in(ToU{}), PO};
} }
template<RepresentationOf<get_quantity_spec(R).character> ToRep>
requires requires(quantity_type q) { value_cast<ToRep>(q); }
[[nodiscard]] constexpr QuantityPointOf<quantity_spec> auto force_in() const
{
return ::mp_units::quantity_point{quantity_ref_from(PO).template force_in<ToRep>(), PO};
}
template<RepresentationOf<get_quantity_spec(R).character> ToRep, detail::UnitCompatibleWith<unit, quantity_spec> ToU>
requires requires(quantity_type q) { value_cast<ToU{}, ToRep>(q); }
[[nodiscard]] constexpr QuantityPointOf<quantity_spec> auto force_in(ToU) const
{
return ::mp_units::quantity_point{quantity_ref_from(PO).template force_in<ToRep>(ToU{}), PO};
}
// conversion operators // conversion operators
template<typename QP_, QuantityPointLike QP = std::remove_cvref_t<QP_>> template<typename QP_, QuantityPointLike QP = std::remove_cvref_t<QP_>>
requires(point_origin == quantity_point_like_traits<QP>::point_origin) && requires(point_origin == quantity_point_like_traits<QP>::point_origin) &&

View File

@ -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 + 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((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<m, mean_sea_level, int>>);
static_assert(is_of_type<(mean_sea_level + 2 * km).in<double>(), quantity_point<km, mean_sea_level>>);
static_assert(is_of_type<(mean_sea_level + 2 * km).in<double>(m), quantity_point<m, mean_sea_level>>);
static_assert(is_of_type<(mean_sea_level + 2500. * m).force_in(km), quantity_point<km, mean_sea_level>>);
static_assert(is_of_type<(mean_sea_level + 2500. * m).force_in<int>(), quantity_point<m, mean_sea_level, int>>);
static_assert(is_of_type<(mean_sea_level + 2500. * m).force_in<int>(km), quantity_point<km, mean_sea_level, int>>);
template<template<auto, auto, typename> typename QP> template<template<auto, auto, typename> typename QP>
concept invalid_unit_conversion = requires { concept invalid_unit_conversion = requires {
requires !requires { QP<isq::height[m], mean_sea_level, int>(2000 * m).in(km); }; // truncating conversion requires !requires { QP<isq::height[m], mean_sea_level, int>(2000 * m).in(km); }; // truncating conversion

View File

@ -208,6 +208,14 @@ static_assert(is_of_type<(2. * km).in(m), quantity<si::metre>>);
static_assert(is_of_type<isq::length(2. * km).in(m), quantity<isq::length[m]>>); static_assert(is_of_type<isq::length(2. * km).in(m), quantity<isq::length[m]>>);
static_assert(is_of_type<isq::height(2. * km).in(m), quantity<isq::height[m]>>); static_assert(is_of_type<isq::height(2. * km).in(m), quantity<isq::height[m]>>);
static_assert(is_of_type<(2 * km).in<double>(m), quantity<si::metre>>);
static_assert(is_of_type<isq::length(2 * km).in<double>(m), quantity<isq::length[m]>>);
static_assert(is_of_type<isq::height(2 * km).in<double>(m), quantity<isq::height[m]>>);
static_assert(is_of_type<(2 * m).in<double>(), quantity<si::metre>>);
static_assert(is_of_type<isq::length(2 * m).in<double>(), quantity<isq::length[m]>>);
static_assert(is_of_type<isq::height(2 * m).in<double>(), quantity<isq::height[m]>>);
static_assert(quantity<isq::length[km]>(2. * km).in(km).numerical_value_in(km) == 2.); static_assert(quantity<isq::length[km]>(2. * km).in(km).numerical_value_in(km) == 2.);
static_assert(quantity<isq::length[km]>(2. * km).in(m).numerical_value_in(m) == 2000.); static_assert(quantity<isq::length[km]>(2. * km).in(m).numerical_value_in(m) == 2000.);
static_assert(quantity<isq::length[m]>(2000. * m).in(km).numerical_value_in(km) == 2.); static_assert(quantity<isq::length[m]>(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<m>(2 * km).numerical_value_in(m) == 2000); static_assert(value_cast<m>(2 * km).numerical_value_in(m) == 2000);
static_assert(value_cast<km>(2000 * m).numerical_value_in(km) == 2); static_assert(value_cast<km>(2000 * m).numerical_value_in(km) == 2);
static_assert(value_cast<int>(1.23 * m).numerical_value_in(m) == 1);
static_assert(value_cast<km / h>(2000.0 * m / (3600.0 * s)).numerical_value_in(km / h) == 2); static_assert(value_cast<km / h>(2000.0 * m / (3600.0 * s)).numerical_value_in(km / h) == 2);
static_assert(value_cast<int>(1.23 * m).numerical_value_in(m) == 1);
static_assert(value_cast<km, int>(1.23 * m).numerical_value_in(km) == 0);
static_assert((2 * km).force_in(m).numerical_value_in(m) == 2000); 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 * 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((2000.0 * m / (3600.0 * s)).force_in(km / h).numerical_value_in(km / h) == 2);
static_assert((1.23 * m).force_in<int>().numerical_value_in(m) == 1);
static_assert((1.23 * m).force_in<int>(km).numerical_value_in(km) == 0);
////////////////// //////////////////
// quantity_cast // quantity_cast
////////////////// //////////////////