Skip to content

Commit

Permalink
Merge branch 'main' into chiphogg/dependabot-11-12-13
Browse files Browse the repository at this point in the history
  • Loading branch information
chiphogg committed Dec 9, 2024
2 parents 70b2bc4 + 7d4530c commit 85b2120
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/howto/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
This section provides detailed instructions for various common tasks that come up in the course of
using the library. Here's a summary of what you'll find.

- **[Quantity template parameters](./quantity-template-parameters.md).** How to use a `Quantity`
_value_ as a template parameter, emulating C++20's expanded support for new kinds of "non-type
template parameters" (NTTPs).

- **[New units](./new-units.md).** How to add a new unit of measure that the library doesn't
include.

Expand Down
86 changes: 86 additions & 0 deletions docs/howto/quantity-template-parameters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Quantity template parameters

Sometimes, you may want to use a `Quantity` _value_ as a template parameter. Generally, this isn't
possible prior to C++20: the set of types whose values can be template parameters is severely
limited. However, if your `Quantity` has an _integral_ rep, we can provide
a [workaround](../reference/quantity.md#nttp). This page explains how to use it.

??? info "How does it work, under the hood?"
Before C++20, non-type template parameters had to be, very roughly, _integral_ types, _pointer_
types (including references), or _enumerations_. With integral and pointer types, there's no
way to provide unit safety. However, with enumerations, we _were_ able to find a way.

The way it works is that every `Quantity<U, R>` type defines its own custom enumeration type,
which is associated only with that `Quantity` type: we call it `Quantity<U, R>::NTTP`. The type
system preserves the association between the two, so we don't get mixed up with the `NTTP` type
for any other `Quantity`. This lets us safely provide implicit conversion back and forth between
the `Quantity` type and its `NTTP` type. What's more, it's legal C++ to `static_cast` between an
enumeration and its underlying type, even for values that aren't part of the enumeration ---
therefore, we're able to hold any value that the `Quantity` type can.

So, even though we can't _exactly_ use a `Quantity` _value_ as a template parameter, what we
_can_ do is use a special type that has _low-friction conversion_ to and from that `Quantity`,
while still providing the unit safety that we need.

## How to use

Let's say that you have a specific specialization of `Quantity<U, R>`, with some concrete unit `U`
and rep `R`. (Remember that `std::is_integral<R>::value` **must** be true in order to use this
feature.) Here's how to use this type as a template parameter:

1. Use `Quantity<U, R>::NTTP` as the _type_ of the template parameter.

2. When instantiating the template, pass any instance of `Quantity<U, R>`.

- Note that you can pass the quantity itself, not `Quantity<U, R>::NTTP`! Implicit conversion
makes this possible.
- Note also that the value (call it `q`) must be **exactly** an instance of `Quantity<U, R>`,
and not any other `Quantity` type. If it's not, you'll get a compiler error. You can fix
this by converting to `Quantity<U, R>` via the usual library mechanisms --- namely, something
like `q.as<R>(U{})`.

3. To use the value of the template parameter _as a `Quantity`_ in your code, you have two options.

- You can assign it to a `Quantity<U, R>` variable. If you do this, note again that the type
must match **exactly**. If it does not, your best bet will be the following alternative.

- You can pass it to the `from_nttp()` function. This converts it to a `Quantity<U, R>`
variable, which means that all of the usual library conversion mechanisms will automatically
work, with no further effort.

## Worked example

Suppose we have a `Processor` class that has some specific clock frequency. We may want to use
a `QuantityU64<Hertz>` to represent this frequency, and we may want to _template the class_ on this
frequency as well. When we specialize this template, we'd like to provide the value in megahertz,
which could be more convenient for our use case. Finally, we want to make it easy for end users to
get the frequency as a variable, but as a twist, we'll provide it in gigahertz, with a `double` rep.

Here's how we'd define that template.

```cpp
// Step (1): use the NTTP type as the template parameter.
template <QuantityU64<Hertz>::NTTP ClockFreq>
class Processor {
public:
static constexpr QuantityD<Giga<Hertz>> clock_freq() {

// Step (3): use `from_nttp()` to make it a `Quantity`.
//
// Note how we don't have to specify the conversion explicitly!
// Now that it's a `Quantity`, the usual mechanisms suffice.
return from_nttp(ClockFreq);
}
};
```

And here's how we'd use it.

```cpp
// Step (2): when instantiating, convert to the exact right `Quantity` type.
using Proc = Processor<mega(hertz)(1'200).as<uint64_t>(hertz)>;

std::cout << Proc::clock_freq() << std::endl;
```

This program prints out `"1.2 GHz"`.

0 comments on commit 85b2120

Please sign in to comment.