Unit slots¶
Many APIs in the library have a parameter for you to name the desired units. We call these parameters “unit slots”.
Let’s look at some common examples. In what follows, assume q
is some Quantity
type, and T
is
some arithmetic type. Then u
will be
what goes in the unit slot:
q.as(u)
round_as(u, q)
inverse_as(u, q)
- “Explicit-Rep” versions of the above (such as
q.as<T>(u)
) - “
in
” versions of the above, which exit the library (q.in(u)
,round_in(u, q)
, etc.)
This page will explain what kinds of things “fit” in the slot, and which styles to prefer in different situations.
What “fits” in a unit slot?¶
The golden rule — the reason these slots exist — is that you should be naming your units at the callsite, concisely but explicitly. This makes the code easier to read and understand at a glance.
It turns out that there are multiple styles for explicitly naming units. For example, if our target
unit is Meters
, then we could either pass Meters{}
or meters
in the unit slot, and get the
same result. While they both fulfill the main goal — namely, they name the unit explicitly —
they are two different kinds of objects.
Meters{}
is a unit expression.meters
is a quantity maker expression.
Let’s explore these concepts in more detail.
Unit expression¶
A unit expression is simply an instance of a unit type. This could be
something as simple as Meters{}
, an instance of the unit type Meters
.
It could also be the result of combining several such instances, via arithmetic. For example,
Meters{} / squared(Seconds{})
is also a unit expression. It’s an instance of the type
UnitQuotientT<Meters, UnitPowerT<Seconds, 2>>
. However, the unit expression is much easier to
write and to read than the UnitQuotientT<...>
version! That’s why we recommend using them to
create new units.
Quantity maker expression¶
A quantity maker expression is similar to the unit expression defined just above, except that it
combines instances of QuantityMaker
(such as meters
) and, optionally, SingularNameFor
(such as
meter
). The result of this expression will be an instance of a QuantityMaker
.
Quantity maker expressions support all of the same arithmetic operations as unit expressions, but they have two advantages that make them easier to read:
-
You don’t need to add the
{}
, since the participating elements are already instances rather than types. -
You can use grammatically correct names, such as
meters / squared(second)
(note:second
is singular), rather thanMeters{} / 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 and constants. Expand the block below to see a worked example.
Example: using unit symbols and constants in unit slots
Suppose we have the following preamble, simply to set everything up.
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:
Notes for QuantityPoint
¶
QuantityPoint
doesn’t use quantity makers: it uses quantity point makers. For example, instead
of the quantity maker meters
, it uses the quantity point maker meters_pt
.
The implications are straightforward. If you have a QuantityPoint
instead of a Quantity
, then
use the quantity point maker instead of the quantity maker. The library will enforce this
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
trait when you’re dealing with
Quantity
, and use the
AssociatedUnitForPointsT
trait when dealing
with QuantityPoint
.
Examples: rounding to RPM¶
Let’s look at some examples, using this quantity variable:
Our goal will be to round it to the nearest value in revolutions per minute (RPM). Let’s look at our options for doing that.
Best choice: quantity maker expression¶
QuantityMaker
instances have the name of the unit, so they meet our core criterion (which is,
again, to name the units explicitly at the callsite). They also compose just as naturally as unit
instances. In fact, they also work nicely with SingularNameFor
instances — such as minute
,
for the unit Minutes
— to further enhance readability.
Here’s how it looks to pass a QuantityMaker
— fluently composed, on the fly — to this unit
expression API.
constexpr auto rpm = round_as(revolutions / minute, angular_velocity);
// ^^^^^^^^^^^^^^^^^^^^
// Quantity maker expression
Also acceptable: unit expression¶
We could, of course, also pass a unit expression to the unit expression slot.
// Usual unit-expression approach (doing arithmetic on *instances*):
constexpr auto rpm = round_as(Revolutions{} / Minutes{}, angular_velocity);
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// unit expression
// Alternative, clunkier unit-expression approach (doing arithmetic on *types*):
constexpr auto rpm = round_as(
UnitQuotientT<Revolutions, Minutes>{}, angular_velocity);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// unit expression
These are OK, but they have a couple of drawbacks compared to the quantity maker approach.
-
They’re grammatically incorrect: “revolutions per minutes”, instead of “revolutions per minute”.
-
You need to sprinkle extra
{}
throughout to turn the unit types into instances.
The main reason to use this method is for generic code. In these cases, you don’t usually have a quantity maker handy, but you do know the unit type.
Poor choice: manually constructed QuantityMaker
¶
This may look counterintuitive, but we mention it because we’ve seen a few instances of this approach in the wild! Here is an example:
//
// !! Do not do this! !!
//
constexpr auto rpm = round_as(
QuantityMaker<UnitQuotientT<Revolutions, Minutes>>{}, angular_velocity);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// manual QuantityMaker instance
Presumably, this mistake comes from reading the signatures in the source code without understanding their core design goal: namely, to provide a place to specify the units, concisely and explicitly, at a callsite.
This provides no benefit at all. We could replace the QuantityMaker<UnitQuotientT<Revolutions,
Minutes>>
with its contents (UnitQuotientT<Revolutions, Minutes>
) and it would be strictly
better.
The reason we endorse the QuantityMaker
overloads is because of the convention to provide “canned”
QuantityMaker
instances which are named after their corresponding units. If you have to construct
a new QuantityMaker
on the fly, then this benefit vanishes. (This is why unit expressions are
preferred for generic code.)
What doesn’t fit in a unit slot?¶
A Quantity
! It can certainly be tempting, as in some ways a Quantity
can “feel like” a unit.
However, the Quantity
also has a runtime value attached. By contrast, unit slots can only take
things that have a single, unambiguous value, known at compile time.
Fortunately, if you make this mistake, you’ll get a readable compiler error that directs you to our troubleshooting page, so you can learn more about why this isn’t allowed, and what you can do to fix it.
Summary¶
Many Au APIs have a “unit slot”. These are designed for you to name the units explicitly at the
callsite. Pass whatever is the simplest construct that meets that goal — usually, this will be
a pre-existing QuantityMaker
, or several of them composed together.