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. This page explains how to use it.
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:
-
Use
Quantity<U, R>::NTTP
as the type of the template parameter. -
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 ofQuantity<U, R>
, and not any otherQuantity
type. If it’s not, you’ll get a compiler error. You can fix this by converting toQuantity<U, R>
via the usual library mechanisms — namely, something likeq.as<R>(U{})
.
- Note that you can pass the quantity itself, not
-
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 aQuantity<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.
// 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.
// 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"
.