Skip to content

Commit

Permalink
Merge branch 'main' into chiphogg/update-troubleshoot#288
Browse files Browse the repository at this point in the history
  • Loading branch information
chiphogg committed Oct 31, 2024
2 parents 748e657 + f70d703 commit f0c53a8
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 0 deletions.
28 changes: 28 additions & 0 deletions au/code/au/quantity.hh
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,34 @@ class Quantity {
CorrespondingQuantityT<T>{*this}.in(typename CorrespondingQuantity<T>::Unit{}));
}

////////////////////////////////////////////////////////////////////////////////////////////////
// Pre-C++20 Non-Type Template Parameter (NTTP) functionality.
//
// If `Rep` is a built in integral type, then `Quantity::NTTP` can be used as a template
// parameter.

enum class NTTP : std::conditional_t<std::is_integral<Rep>::value, Rep, bool> {
ENUM_VALUES_ARE_UNUSED
};

constexpr Quantity(NTTP val) : value_{static_cast<Rep>(val)} {
static_assert(std::is_integral<Rep>::value,
"NTTP functionality only works when rep is built-in integral type");
}

constexpr operator NTTP() const {
static_assert(std::is_integral<Rep>::value,
"NTTP functionality only works when rep is built-in integral type");
return static_cast<NTTP>(value_);
}

template <typename C, C x = C::ENUM_VALUES_ARE_UNUSED>
constexpr operator C() const = delete;
// If you got here ^^^, then you need to do your unit conversion **manually**. Check the type
// of the template parameter, and convert it to that same unit and rep.

friend constexpr Quantity from_nttp(NTTP val) { return val; }

private:
template <typename OtherUnit, typename OtherRep>
static constexpr void warn_if_integer_division() {
Expand Down
16 changes: 16 additions & 0 deletions au/code/au/quantity_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,22 @@ TEST(Quantity, CommonUnitAlwaysCompletelyIndependentOfOrder) {
check_units(kilo(meters), miles, milli(meters));
}

template <QuantityI<Meters>::NTTP Length>
struct TemplateOnLength {
QuantityI<Meters> value = Length;
};

TEST(QuantityNTTP, SupportsPreCpp20NttpTypes) {
constexpr auto length = TemplateOnLength<meters(18)>{}.value;
EXPECT_THAT(length, SameTypeAndValue(meters(18)));
}

TEST(QuantityNTTP, CanConvertFromNttpToAnyCompatibleQuantityType) {
constexpr QuantityI<Meters>::NTTP LengthNTTP = meters(18);
constexpr QuantityI<Milli<Meters>> length = from_nttp(LengthNTTP);
EXPECT_THAT(length, SameTypeAndValue(milli(meters)(18'000)));
}

TEST(Quantity, CommonTypeRespectsImplicitRepSafetyChecks) {
// The following test should fail to compile. Uncomment both lines to check.
// constexpr auto feeters = QuantityMaker<CommonUnitT<Meters, Feet>>{};
Expand Down
44 changes: 44 additions & 0 deletions docs/reference/quantity.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,50 @@ 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.

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

A _non-type template parameter_ (NTTP) is a template parameter that is not a _type_, but rather some
kind of _value_. Common examples include `template<int N>`, or `template<bool B>`. Before C++20,
only a small number of types could be used as NTTPs: very roughly, these were _integral_ types,
_pointer_ types, and _enumerations_.

Au provides a workaround for pre-C++20 users that lets you _effectively_ encode any `Quantity<U, R>`
as an NTTP, _as long as_ its rep `R` is an **integral** type. To do this, use the
`Quantity<U, R>::NTTP` type as the template parameter. You will be able to assign between
`Quantity<U, R>` and `Quantity<U, R>::NTTP`, _in either direction_, but only in the case of exact
match of both `U` and `R`. For all other cases, you'll need to perform a conversion (using the
usual mechanisms for `Quantity` described elsewhere on this page).

!!! warning
It is undefined behavior to invoke `Quantity<U, R>::NTTP` whenever `std::is_integral<R>::value`
is `false`.

We cannot strictly prevent users from doing this. However, in practice, it is very unlikely for
this to happen by accident. Both conversion operators between `Quantity<U, R>` and
`Quantity<U, R>::NTTP` would fail with a hard compiler error, based on a `static_assert` that
explains this situation. So users can name this type, but they cannot assign to it or from it
without prohibitive difficulty.

??? example "Example: defining and using a template with a `Quantity` NTTP"
```cpp
template <QuantityI<Hertz>::NTTP Frequency>
struct TemplatedOnFrequency {
QuantityI<Hertz> value = Frequency; // Assigning `Quantity` from NTTP
};

using T = TemplatedOnFrequency<hertz(440)>; // Setting template parameter from `Quantity`
```

### `from_nttp(Quantity<U, R>::NTTP)`

Calling `from_nttp` on a `Quantity<U, R>::NTTP` will convert it back into the corresponding
`Quantity<U, R>` that was encoded in the template parameter. This lets it automatically participate
in all of the usual `Quantity` operations and conversions.

!!! note
If you are simply _assigning_ a `Quantity<U, R>::NTTP` to a `Quantity<U, R>`, where `U` and `R`
are identical, you do not need to call `from_nttp`. We support implcit conversion in that case.

## Operations

Au includes as many common operations as possible. Our goal is to avoid incentivizing users to
Expand Down

0 comments on commit f0c53a8

Please sign in to comment.