Skip to content

Commit

Permalink
Add associated unit trait for quantity point (#235)
Browse files Browse the repository at this point in the history
To complement `AssociatedUnitT`, which is for `Quantity`, we add
`AssociatedUnitForPointsT`, to support `QuantityPoint`.  This lets us
simplify the in/as implementations for `QuantityPoint` the same way we
had already done for `Quantity`.  But the main point is to make it
easier to add unit slots to APIs that work with `QuantityPoint`, which
will help #221.

We also update the docs.  The docs for the trait predate the tidy
concept of a "unit slot", so we change the language to be more
consistent.  And on the unit slot page, we add docs for the new options
(symbols and constants) on the quantity side, and say a few more words
about quantity points.
  • Loading branch information
chiphogg committed May 9, 2024
1 parent 30440ed commit b9b89f8
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 40 deletions.
53 changes: 21 additions & 32 deletions au/quantity_point.hh
Original file line number Diff line number Diff line change
Expand Up @@ -121,55 +121,41 @@ class QuantityPoint {

template <typename NewRep,
typename NewUnit,
typename = std::enable_if_t<IsUnit<NewUnit>::value>>
typename = std::enable_if_t<IsUnit<AssociatedUnitForPointsT<NewUnit>>::value>>
constexpr auto as(NewUnit u) const {
return make_quantity_point<NewUnit>(this->template in<NewRep>(u));
return make_quantity_point<AssociatedUnitForPointsT<NewUnit>>(this->template in<NewRep>(u));
}

template <typename NewUnit, typename = std::enable_if_t<IsUnit<NewUnit>::value>>
template <typename NewUnit,
typename = std::enable_if_t<IsUnit<AssociatedUnitForPointsT<NewUnit>>::value>>
constexpr auto as(NewUnit u) const {
return make_quantity_point<NewUnit>(in(u));
return make_quantity_point<AssociatedUnitForPointsT<NewUnit>>(in(u));
}

template <typename NewRep,
typename NewUnit,
typename = std::enable_if_t<IsUnit<NewUnit>::value>>
typename = std::enable_if_t<IsUnit<AssociatedUnitForPointsT<NewUnit>>::value>>
constexpr NewRep in(NewUnit u) const {
using CalcRep = typename detail::IntermediateRep<Rep, NewRep>::type;
return (rep_cast<CalcRep>(x_) -
rep_cast<CalcRep>(OriginDisplacement<Unit, NewUnit>::value()))
.template in<NewRep>(u);
rep_cast<CalcRep>(
OriginDisplacement<Unit, AssociatedUnitForPointsT<NewUnit>>::value()))
.template in<NewRep>(associated_unit_for_points(u));
}

template <typename NewUnit, typename = std::enable_if_t<IsUnit<NewUnit>::value>>
template <typename NewUnit,
typename = std::enable_if_t<IsUnit<AssociatedUnitForPointsT<NewUnit>>::value>>
constexpr Rep in(NewUnit u) const {
static_assert(detail::OriginDisplacementFitsIn<Rep, NewUnit, Unit>::value,
"Cannot represent origin displacement in desired Rep");
static_assert(
detail::OriginDisplacementFitsIn<Rep, AssociatedUnitForPointsT<NewUnit>, Unit>::value,
"Cannot represent origin displacement in desired Rep");

// `rep_cast` is needed because if these are integral types, their difference might become a
// different type due to integer promotion.
return rep_cast<Rep>(x_ + rep_cast<Rep>(OriginDisplacement<NewUnit, Unit>::value())).in(u);
}

// Overloads for passing a QuantityPointMaker.
//
// This is the "magic" that lets us write things like `position.in(meters_pt)`, instead of just
// `position.in(Meters{})`.
template <typename NewRep, typename NewUnit>
constexpr auto as(QuantityPointMaker<NewUnit>) const {
return as<NewRep>(NewUnit{});
}
template <typename NewUnit>
constexpr auto as(QuantityPointMaker<NewUnit>) const {
return as(NewUnit{});
}
template <typename NewRep, typename NewUnit>
constexpr NewRep in(QuantityPointMaker<NewUnit>) const {
return in<NewRep>(NewUnit{});
}
template <typename NewUnit>
constexpr Rep in(QuantityPointMaker<NewUnit>) const {
return in(NewUnit{});
return rep_cast<Rep>(
x_ + rep_cast<Rep>(
OriginDisplacement<AssociatedUnitForPointsT<NewUnit>, Unit>::value()))
.in(associated_unit_for_points(u));
}

// "Old-style" overloads with <U, R> template parameters, and no function parameters.
Expand Down Expand Up @@ -312,6 +298,9 @@ struct QuantityPointMaker {
}
};

template <typename U>
struct AssociatedUnitForPoints<QuantityPointMaker<U>> : stdx::type_identity<U> {};

// Type trait to detect whether two QuantityPoint types are equivalent.
//
// In this library, QuantityPoint types are "equivalent" exactly when they use the same Rep, and are
Expand Down
10 changes: 10 additions & 0 deletions au/unit_of_measure.hh
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ struct AssociatedUnit : stdx::type_identity<U> {};
template <typename U>
using AssociatedUnitT = typename AssociatedUnit<U>::type;

template <typename U>
struct AssociatedUnitForPoints : stdx::type_identity<U> {};
template <typename U>
using AssociatedUnitForPointsT = typename AssociatedUnitForPoints<U>::type;

// `CommonUnitT`: the largest unit that evenly divides all input units.
//
// A specialization will only exist if all input types are units.
Expand Down Expand Up @@ -249,6 +254,11 @@ constexpr auto associated_unit(U) {
return AssociatedUnitT<U>{};
}

template <typename U>
constexpr auto associated_unit_for_points(U) {
return AssociatedUnitForPointsT<U>{};
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Unit arithmetic traits: products, powers, and derived operations.

Expand Down
44 changes: 44 additions & 0 deletions docs/discussion/idioms/unit-slots.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,44 @@ they have two advantages that make them easier to read:
2. You can use grammatically correct names, such as `meters / squared(second)` (note: `second` is
singular), rather than `Meters{} / squared(Seconds{})`.

### Other expressions

There are other monovalue types that would feel right at home in a unit slot. We typically support
those too! Key examples include [unit symbols](../../reference/unit.md#unit-symbols) and
[constants](../../reference/constant.md). Expand the block below to see a worked example.

??? example "Example: using unit symbols and constants in unit slots"
Suppose we have the following preamble, simply to set everything up.

```cpp
struct SpeedOfLight : decltype(Meters{} / Seconds{} * mag<299'792'458>()) {
static constexpr const char label[] = "c";
};
constexpr const char SpeedOfLight::label[];
constexpr auto c = make_constant(SpeedOfLight{});

// These using declarations should be in a `.cc` file, not `.hh`,
// to avoid namespace pollution!
using symbols::m;
using symbols::s;
```

Then we can pass either the unit symbols, or the constants, to our unit slot APIs:

```cpp
constexpr auto v = (miles / hour)(65.0);

std::cout << v.as(m / s) << std::endl;
// ^^^^^
// Passing a unit symbol to the unit slot. Output:
// "29.0576 m / s"

std::cout << v.as(c) << std::endl;
// ^
// Passing a constant to the unit slot. Output:
// "9.69257e-08 c"
```

#### Notes for `QuantityPoint`

`QuantityPoint` doesn't use quantity makers: it uses quantity _point_ makers. For example, instead
Expand All @@ -67,6 +105,12 @@ use the quantity _point_ maker instead of the _quantity_ maker. The library wil
automatically: for example, you can't pass `meters` to a `QuantityPoint`'s unit slot, and you can't
pass `meters_pt` to a `Quantity`'s unit slot.

To get the associated unit for a type, use the
[`AssociatedUnitT`](../../reference/unit.md#associated-unit) trait when you're dealing with
`Quantity`, and use the
[`AssociatedUnitForPointsT`](../../reference/unit.md#associated-unit-for-points) trait when dealing
with `QuantityPoint`.

## Examples: rounding to RPM

Let's look at some examples, using this quantity variable:
Expand Down
49 changes: 41 additions & 8 deletions docs/reference/unit.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,29 +495,32 @@ $273.15 \,\text{K}$.
- For _instances_ `u1` and `u2`:
- `origin_displacement(u1, u2)`

### Associated unit
### Associated unit {#associated-unit}

**Result:** The actual unit associated with a "unit-alike".

What's a "unit-alike"? It's something that can be passed to an API expecting the name of a unit.
Here are a few examples.
**Result:** The actual unit associated with a [unit slot](../discussion/idioms/unit-slots.md) that
is associated with a `Quantity` type. Here are a few examples.

```cpp
round_in(meters, feet(20));
// ^^^^^^
round_in(Meters{}, feet(20));
// ^^^^^^^^

using symbols::m;
round_in(m, feet(20));
// ^

feet(6).in(inches);
// ^^^^^^
feet(6).in(Inches{});
// ^^^^^^^^
```
The underlined arguments are all unit-alikes. In practice, a unit-alike either a `QuantityMaker`
for some unit, or a unit itself.
The underlined arguments are all unit slots. The kinds of things that can be passed here include
a `QuantityMaker` for a unit, a [constant](./constant.md), a [unit symbol](#unit-symbols), or simply
a unit type itself.
The use case for this trait is to _implement_ a function that takes a unit-alike.
The use case for this trait is to _implement_ the unit slot argument for a function.
**Syntax:**
Expand All @@ -526,6 +529,36 @@ The use case for this trait is to _implement_ a function that takes a unit-alike
- For an _instance_ `u`:
- `associated_unit(u)`
### Associated unit (for points) {#associated-unit-for-points}
**Result:** The actual unit associated with a [unit slot](../discussion/idioms/unit-slots.md) that
is associated with a quantity point type. Here are a few examples.
```cpp
round_in(meters_pt, milli(meters_pt)(1200));
// ^^^^^^^^^
round_in(Meters{}, milli(meters_pt)(1200));
// ^^^^^^^^
meters_pt(6).in(centi(meters_pt));
// ^^^^^^^^^^^^^^^^
meters_pt(6).in(Centi<Meters>{});
// ^^^^^^^^^^^^^^^
```

The underlined arguments are unit slots for quantity points. In practice, this will be either
a `QuantityPointMaker` for some unit, or a unit itself.

The use case for this trait is to _implement_ a function or API that takes a unit slot, and is
associated with quantity points.

**Syntax:**

- For a _type_ `U`:
- `AssociatedUnitForPointsT<U>`
- For an _instance_ `u`:
- `associated_unit_for_points(u)`

### Common unit

**Result:** The largest unit that evenly divides its input units. (Read more about the concept of
Expand Down

0 comments on commit b9b89f8

Please sign in to comment.