Skip to content

Commit

Permalink
Merge branch 'main' into chiphogg/first-constants#90
Browse files Browse the repository at this point in the history
  • Loading branch information
chiphogg committed Dec 2, 2024
2 parents f9e826e + d450960 commit 69f1dae
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 0 deletions.
14 changes: 14 additions & 0 deletions au/code/au/quantity.hh
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,20 @@ constexpr auto operator%(Quantity<U1, R1> q1, Quantity<U2, R2> q2) {
return make_quantity<U>(q1.in(U{}) % q2.in(U{}));
}

// Callsite-readable way to convert a `Quantity` to a raw number.
//
// Only works for dimensionless `Quantities`; will return a compile-time error otherwise.
//
// Identity for non-`Quantity` types.
template <typename U, typename R>
constexpr R as_raw_number(Quantity<U, R> q) {
return q.as(UnitProductT<>{});
}
template <typename T>
constexpr T as_raw_number(T x) {
return x;
}

// Type trait to detect whether two Quantity types are equivalent.
//
// In this library, Quantity types are "equivalent" exactly when they use the same Rep, and are
Expand Down
29 changes: 29 additions & 0 deletions au/code/au/quantity_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ static constexpr QuantityMaker<Meters> meters{};
static_assert(are_units_quantity_equivalent(Centi<Meters>{} * mag<254>(), Inches{} * mag<100>()),
"Double-check this ad hoc definition of meters");

struct Unos : decltype(UnitProductT<>{}) {};
constexpr auto unos = QuantityMaker<Unos>{};

struct Percent : decltype(Unos{} / mag<100>()) {};
constexpr auto percent = QuantityMaker<Percent>{};

struct Hours : UnitImpl<Time> {};
constexpr auto hour = SingularNameFor<Hours>{};
constexpr auto hours = QuantityMaker<Hours>{};
Expand All @@ -63,6 +69,12 @@ struct Minutes : decltype(Hours{} / mag<60>()) {};
constexpr auto minute = SingularNameFor<Minutes>{};
constexpr auto minutes = QuantityMaker<Minutes>{};

struct Seconds : decltype(Minutes{} / mag<60>()) {};
constexpr auto seconds = QuantityMaker<Seconds>{};

struct Hertz : decltype(inverse(Seconds{})) {};
constexpr auto hertz = QuantityMaker<Hertz>{};

struct Days : decltype(Hours{} * mag<24>()) {};
constexpr auto days = QuantityMaker<Days>{};

Expand Down Expand Up @@ -786,6 +798,23 @@ TEST(QuantityMaker, ProvidesAssociatedUnit) {
StaticAssertTypeEq<AssociatedUnitT<QuantityMaker<Hours>>, Hours>();
}

TEST(AsRawNumber, ExtractsRawNumberForUnitlessQuantity) {
EXPECT_THAT(as_raw_number(unos(3)), SameTypeAndValue(3));
EXPECT_THAT(as_raw_number(unos(3.1415f)), SameTypeAndValue(3.1415f));
}

TEST(AsRawNumber, PerformsConversionsWherePermissible) {
EXPECT_THAT(as_raw_number(percent(75.0)), SameTypeAndValue(0.75));
EXPECT_THAT(as_raw_number(kilo(hertz)(7) * seconds(3)), SameTypeAndValue(21'000));
}

TEST(AsRawNumber, IdentityForBuiltInNumericTypes) {
EXPECT_THAT(as_raw_number(3), SameTypeAndValue(3));
EXPECT_THAT(as_raw_number(3u), SameTypeAndValue(3u));
EXPECT_THAT(as_raw_number(3.1415), SameTypeAndValue(3.1415));
EXPECT_THAT(as_raw_number(3.1415f), SameTypeAndValue(3.1415f));
}

TEST(WillConversionOverflow, SensitiveToTypeBoundariesForPureIntegerMultiply) {
{
auto will_m_to_mm_overflow_i32 = [](int32_t x) {
Expand Down
28 changes: 28 additions & 0 deletions docs/reference/quantity.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,32 @@ These functions also support an explicit template parameter: so, `.coerce_as<T>(
Prefer **not** to use the "coercing versions" if possible, because you will get more safety
checks. The risks which the "base" versions warn about are real.

### Special case: dimensionless and unitless results {#as-raw-number}

Users may expect that the product of quantities such as `seconds` and `hertz` would completely
cancel out, and produce a raw, simple C++ numeric type. Currently, this is indeed the case, but we
have also found that it makes the library harder to reason about. Instead, we hope in the future to
return a `Quantity` type _consistently_ from arithmetical operations on `Quantity` inputs (see
[#185]).

In order to obtain that raw number robustly, both now and in the future, you can use the
`as_raw_number` function, a callsite-readable way to "exit" the library. This will also opt into
all mechanisms and safety features of the library. In particular:

- We will automatically perform all necessary conversions.
- This will not compile unless the input is _dimensionless_.
- If the conversion is dangerous (say, from `Quantity<Percent, int>`, which cannot in general be
represented exactly as a raw `int`, we will also fail to compile.

Users should get in the habit of using `as_raw_number` whenever they really want a raw number. This
communicates intent, and also works both before and after [#185] is implemented.

!!! example
```cpp
constexpr auto num_beats = as_raw_number(kilo(hertz)(7) * seconds(3));
// Result: 21'000 (of type `int`)
```

## Non-Type Template Parameters (NTTPs) {#nttp}

A _non-type template parameter_ (NTTP) is a template parameter that is not a _type_, but rather some
Expand Down Expand Up @@ -698,3 +724,5 @@ the following conditions hold.

- For _types_ `U1` and `U2`:
- `AreQuantityTypesEquivalent<U1, U2>::value`

[#185]: https://github.com/aurora-opensource/au/issues/185

0 comments on commit 69f1dae

Please sign in to comment.