Skip to content

Commit

Permalink
Merge branch 'main' into chiphogg/isnan#221
Browse files Browse the repository at this point in the history
  • Loading branch information
chiphogg committed May 9, 2024
2 parents 1ed8c89 + b9b89f8 commit cb1f18b
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 cb1f18b

Please sign in to comment.