From b9b89f804d4ae03c4371be3951343aacd118c095 Mon Sep 17 00:00:00 2001 From: Chip Hogg Date: Thu, 9 May 2024 11:32:07 -0400 Subject: [PATCH] Add associated unit trait for quantity point (#235) 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. --- au/quantity_point.hh | 53 +++++++++++----------------- au/unit_of_measure.hh | 10 ++++++ docs/discussion/idioms/unit-slots.md | 44 +++++++++++++++++++++++ docs/reference/unit.md | 49 ++++++++++++++++++++----- 4 files changed, 116 insertions(+), 40 deletions(-) diff --git a/au/quantity_point.hh b/au/quantity_point.hh index db4698f4..a8de99fa 100644 --- a/au/quantity_point.hh +++ b/au/quantity_point.hh @@ -121,55 +121,41 @@ class QuantityPoint { template ::value>> + typename = std::enable_if_t>::value>> constexpr auto as(NewUnit u) const { - return make_quantity_point(this->template in(u)); + return make_quantity_point>(this->template in(u)); } - template ::value>> + template >::value>> constexpr auto as(NewUnit u) const { - return make_quantity_point(in(u)); + return make_quantity_point>(in(u)); } template ::value>> + typename = std::enable_if_t>::value>> constexpr NewRep in(NewUnit u) const { using CalcRep = typename detail::IntermediateRep::type; return (rep_cast(x_) - - rep_cast(OriginDisplacement::value())) - .template in(u); + rep_cast( + OriginDisplacement>::value())) + .template in(associated_unit_for_points(u)); } - template ::value>> + template >::value>> constexpr Rep in(NewUnit u) const { - static_assert(detail::OriginDisplacementFitsIn::value, - "Cannot represent origin displacement in desired Rep"); + static_assert( + detail::OriginDisplacementFitsIn, 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(x_ + rep_cast(OriginDisplacement::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 - constexpr auto as(QuantityPointMaker) const { - return as(NewUnit{}); - } - template - constexpr auto as(QuantityPointMaker) const { - return as(NewUnit{}); - } - template - constexpr NewRep in(QuantityPointMaker) const { - return in(NewUnit{}); - } - template - constexpr Rep in(QuantityPointMaker) const { - return in(NewUnit{}); + return rep_cast( + x_ + rep_cast( + OriginDisplacement, Unit>::value())) + .in(associated_unit_for_points(u)); } // "Old-style" overloads with template parameters, and no function parameters. @@ -312,6 +298,9 @@ struct QuantityPointMaker { } }; +template +struct AssociatedUnitForPoints> : stdx::type_identity {}; + // 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 diff --git a/au/unit_of_measure.hh b/au/unit_of_measure.hh index 62110c2b..20b2c8de 100644 --- a/au/unit_of_measure.hh +++ b/au/unit_of_measure.hh @@ -145,6 +145,11 @@ struct AssociatedUnit : stdx::type_identity {}; template using AssociatedUnitT = typename AssociatedUnit::type; +template +struct AssociatedUnitForPoints : stdx::type_identity {}; +template +using AssociatedUnitForPointsT = typename AssociatedUnitForPoints::type; + // `CommonUnitT`: the largest unit that evenly divides all input units. // // A specialization will only exist if all input types are units. @@ -249,6 +254,11 @@ constexpr auto associated_unit(U) { return AssociatedUnitT{}; } +template +constexpr auto associated_unit_for_points(U) { + return AssociatedUnitForPointsT{}; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// // Unit arithmetic traits: products, powers, and derived operations. diff --git a/docs/discussion/idioms/unit-slots.md b/docs/discussion/idioms/unit-slots.md index f2c1446a..56d406b8 100644 --- a/docs/discussion/idioms/unit-slots.md +++ b/docs/discussion/idioms/unit-slots.md @@ -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 @@ -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: diff --git a/docs/reference/unit.md b/docs/reference/unit.md index 7b5a1923..cdc949b0 100644 --- a/docs/reference/unit.md +++ b/docs/reference/unit.md @@ -495,12 +495,10 @@ $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)); @@ -508,16 +506,21 @@ 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:** @@ -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{}); +// ^^^^^^^^^^^^^^^ +``` + +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` +- 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