Skip to content

Commit

Permalink
feat: representation type template parameter added to value convertio…
Browse files Browse the repository at this point in the history
…n functions

Resolves #588
  • Loading branch information
mpusz committed Jul 4, 2024
1 parent 5903e56 commit 2cff579
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 21 deletions.
55 changes: 35 additions & 20 deletions docs/users_guide/framework_basics/value_conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,30 +148,45 @@ Price price{12.95 * USD};
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
`Quantity` type directly, from which unit and representation type are taken. However, `value_cast<Quantity>`,
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:
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<Quantity>`, 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:
```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<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<Q>(qp) == quantity_point{value_cast<Q>(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, 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});
```

Furthermore, there is one additional overload `value_cast<ToQP>(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<ToQP>(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<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
`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<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)` |
28 changes: 28 additions & 0 deletions src/core/include/mp-units/framework/quantity.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,41 @@ class quantity {
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>
requires requires(quantity q) { value_cast<ToU{}>(q); }
[[nodiscard]] constexpr QuantityOf<quantity_spec> auto force_in(ToU) const
{
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
template<Unit U>
requires(U{} == unit)
Expand Down
28 changes: 28 additions & 0 deletions src/core/include/mp-units/framework/quantity_point.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,41 @@ class quantity_point {
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>
requires requires(quantity_type q) { value_cast<ToU{}>(q); }
[[nodiscard]] constexpr QuantityPointOf<quantity_spec> auto force_in(ToU) const
{
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
template<typename QP_, QuantityPointLike QP = std::remove_cvref_t<QP_>>
requires(point_origin == quantity_point_like_traits<QP>::point_origin) &&
Expand Down
8 changes: 8 additions & 0 deletions test/static/quantity_point_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<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>
concept invalid_unit_conversion = requires {
requires !requires { QP<isq::height[m], mean_sea_level, int>(2000 * m).in(km); }; // truncating conversion
Expand Down
15 changes: 14 additions & 1 deletion test/static/quantity_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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::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(m).numerical_value_in(m) == 2000.);
static_assert(quantity<isq::length[m]>(2000. * m).in(km).numerical_value_in(km) == 2.);
Expand Down Expand Up @@ -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<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<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((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<int>().numerical_value_in(m) == 1);
static_assert((1.23 * m).force_in<int>(km).numerical_value_in(km) == 0);

//////////////////
// quantity_cast
//////////////////
Expand Down

0 comments on commit 2cff579

Please sign in to comment.