Quantity¶
Quantity
is our workhorse type. It combines a runtime numeric value with compile-time information
about the units of measure in which that quantity is expressed.
Quantity
comes with “batteries included”. We try to support as many operations as possible
natively, to avoid incentivizing users to leave the safety of the units library.
Quantity
is a template, Quantity<U, R>
, with two parameters. Both are typenames.
U
is the unit: a type representing the unit of measure.R
is the “rep”, a term we borrow from thestd::chrono
library. It’s the underlying raw numeric type which holds the wrapped value.
Handling temperatures
If you are working with temperatures — in the sense of “what temperature is it?”, rather than
“how much did the temperature change?” — you will want to use
QuantityPoint
instead of Quantity
. For further useful background
reading, see our quantity point discussion.
Naming Quantity
in code¶
You can use both template parameters directly (for example, Quantity<Meters, int>
). However,
multiple template parameters can be awkward to read. It can also give the parser some trouble when
you’re forced to use macros, as in googletest. For that reason, we also provide “Rep-named
aliases”, so you can express the rep more precisely, and put the visual focus on the unit:
Rep-named alias | Equivalent to: |
---|---|
QuantityD<U> |
Quantity<U, double> |
QuantityF<U> |
Quantity<U, float> |
QuantityI<U> |
Quantity<U, int> |
QuantityU<U> |
Quantity<U, unsigned int> |
QuantityI32<U> |
Quantity<U, int32_t> |
QuantityU32<U> |
Quantity<U, uint32_t> |
QuantityI64<U> |
Quantity<U, int64_t> |
QuantityU64<U> |
Quantity<U, uint64_t> |
For more practice with this, see Tutorial 102: API Types.
Constructing Quantity
¶
There are several ways to construct a Quantity
object.
- The preferred way, which we’ll explain below, is to use a quantity maker.
- The other ways are all normal C++ constructors.
One way you cannot construct a Quantity
is via a constructor that takes a single raw number.
Many users find this surprising at first, but it’s important for safety and usability. To learn
more about this policy, expand the box below.
The "missing" raw number constructor
New users often expect Quantity<U, R>
to be constructible from a raw value of type R
. For
example, they expect to be able to write something like:
This example looks innocuous, but enabling it would have other ill effects, and would be a net negative overall.
First, we want to support a wide variety of reasonable usage patterns, safely. One approach people sometimes take is to use dimension-named aliases throughout the codebase, making the actual underlying unit an encapsulated implementation detail. Here’s an example of what this looks like, which shows why we must forbid the default constructor:
// Store all lengths in meters using `double`, but as an implementation detail.
// End users will simply call their type `Length`.
using Length = QuantityD<Meters>;
// In some other file...
Length l1{3.0}; // Does NOT work in Au --- good!
Length l2 = meters(3.0); // Works; unambiguous.
We hope the danger is clear: there’s no such concept as a “length of 3”. For safety and clarity, the user must always name the unit at the callsite.
The second reason is elaborated in the section, “explicit
is not explicit
enough”,
from the standard units library proposal paper P3045R0. Even if you don’t use aliases for your
quantities, you might sometimes have a vector of them. If you do, the .emplace_back()
function accepts a raw number. Not only is this unclear at the callsite, but it can cause
long-range (and silent!) errors if you later refactor the vector to hold a different type. By
contrast, omitting this constructor forces the user to name the unit explicitly at the callsite,
every time. This keeps callsites unambiguous, minimizes cognitive load for the reader, and
enables safe refactoring.
Overall, despite the initial surprise of the “missing” raw number constructor, experience shows that it’s a net benefit. Not only does its absence enhance safety, but thanks to the other construction methods, it also doesn’t sacrifice usability!
Quantity Maker (preferred)¶
The preferred way to construct a Quantity
of a given unit is to use the quantity maker for that
unit. This is a callable whose name is the plural form of that unit, expressed in “snake_case”: for
example, nautical_miles
. When you pass a raw numeric variable of type T
to the quantity maker,
it returns a Quantity
of the unit it represents, whose rep type is T
.
Example
nautical_miles(15)
returns a Quantity<NauticalMiles, int>
.
For more practice with quantity makers, see Tutorial 101: Quantity Makers.
Implicit constructor from another Quantity
¶
This constructor performs a unit conversion. The result will represent the same quantity as the input, but expressed in the units of the newly constructed object’s type. It will also convert the stored value to the rep of the constructed object, if necessary.
Here is the signature. We’ve simplified it slightly for illustration purposes, and enclosed it inside the class definition for context.
template <typename Unit, typename Rep>
class Quantity {
template <typename OtherUnit, typename OtherRep>
Quantity(Quantity<OtherUnit, OtherRep> other);
};
This constructor only exists when this unit-and-rep conversion is both meaningful and safe. It can fail to exist in several ways.
-
If the input quantity has a different dimension, then the operation is intrinsically meaningless and we forbid it.
-
If the constructed quantity’s rep (that is,
Rep
in the code snippet above) is not floating point, then we forbid any conversion that might produce a non-integer value. Examples include:a. When
OtherRep
is floating point, we forbid this conversion.b. When
UnitRatioT<OtherUnit, Unit>
is not an integer, we forbid this conversion. -
If we’re performing an integer-to-integer conversion, and the conversion factor carries a significant risk of overflowing the rep, we forbid the conversion. This is the adaptive “overflow safety surface” which we featured in our Au announcement blog post.
These last two are examples of conversions that are physically meaningful, but forbidden due to the
risk of larger-than-usual errors. The library can still perform these conversions, but not via this
constructor, and it must be “forced” to do so. See .coerce_as(unit)
for more details.
Constructing from Zero
¶
This constructs a Quantity
with a value of 0.
Here is the signature, enclosed in the class definition for context.
Note
Zero
is the name of the type. At the callsite, you would pass an instance of that type,
such as the constant ZERO
. For example:
For more information on the motivation and use of this type, read our Zero
discussion.
Default constructor¶
Here is the signature of the constructor, enclosed in the class template definition for context.
A default-constructed Quantity
is always initialized (which helps avoid certain kinds of memory
safety bugs). It will contain a default-constructed instance of the rep type.
Warning
Avoid relying on the specific value of a default-constructed Quantity
, because it poorly
communicates intent. The only logically valid operation on a default-constructed Quantity
is
to assign to it later on.
The default value for many rep types, including all fundamental arithmetic types, is 0
.
Instead of relying on this behaviour, initialize your Quantity
with au::ZERO
to
better communicate your intent.
Constructing from corresponding quantity¶
Here is the signature. We’ve simplified it slightly for illustration purposes, and enclosed it inside the class definition for context.
template <typename Unit, typename Rep>
class Quantity {
template <typename T>
Quantity(T &&x);
// NOTE: only exists when `T` is an "exactly-equivalent" type. One example:
// `std::chrono::nanoseconds` can construct an `au::QuantityI64<Nano<Seconds>>`.
};
This constructor will only exist when T
has a “corresponding
quantity”, and this Quantity
type is implicitly constructible from
that “corresponding quantity” type by the above mechanism.
Extracting the stored value¶
In order to access the raw numeric value stored inside of Quantity
, you must explicitly name the
unit at the callsite. There are two functions which can do this, depending on whether you want to
access by value or by reference.
By value: .in(unit)
¶
This function returns the underlying stored value, by value. See the unit
slots discussion for valid choices for unit
.
Example: extracting 4.56
from seconds(4.56)
Consider this Quantity<Seconds, double>
:
You can retrieve the underlying value by writing either t.in(seconds)
(passing the
QuantityMaker
), or t.in(Seconds{})
(passing an instance of the unit).
By reference: .data_in(unit)
¶
This function returns a reference to the underlying stored value. See the unit
slots discussion for valid choices for unit
.
Example: incrementing the underlying stored value
Since length
is not const
, the reference returned by .data_in()
will be mutable, and we
can treat it like any other int
lvalue. The above would result in inches(61)
.
Performing unit conversions¶
We have two methods for performing unit conversions. They have identical APIs, but their names are
different. as
returns another Quantity
, but in
exits the library and returns a raw number.
.as(unit)
, .as<T>(unit)
¶
This function produces a new representation of the input Quantity
, converted to the new unit. See
the unit slots discussion for valid choices for unit
.
Example: converting feet(3)
to inches
Consider this Quantity<Feet, int>
:
Then length.as(inches)
re-expresses this quantity in units of inches. Specifically, it
returns a Quantity<Inches, int>
, which is equal to inches(36)
.
The above example used the quantity maker, inches
. One could also use an instance of the unit
type Inches
, writing length.as(Inches{})
. The former is generally preferable; the latter is
mainly useful in generic code where the unit type may be all you have.
Without a template argument, .as(unit)
obeys the same safety checks as for the implicit
constructors: conversions at high risk for integer overflow or truncation
are forbidden. Additionally, the Rep
of the output is identical to the Rep
of the input.
With a template argument, .as<T>(unit)
has two differences.
- The output
Rep
will beT
. - The conversion is considered “forcing”, and will be permitted in spite of any overflow or
truncation risk. The semantics are similar to
static_cast<T>
.
However, note that we may change this second property in the future. The version with the template
arguments may be changed later so that it does prevent lossy conversions. If you want this
“forcing” semantic, prefer to use .coerce_as(unit)
, and add the explicit template
parameter only if you want to change the rep. See
#122 for more details.
Example: forcing a conversion from inches to feet
inches(24).as(feet)
is not allowed. This conversion will divide the underlying value, 24
,
by 12
. Now, it so happens that this particular value would produce an integer result.
However, the compiler must decide whether to permit this operation at compile time, which
means we don’t yet know the value. Since most int
values would not produce integer results,
we forbid this.
inches(24).as<int>(feet)
is allowed. The “explicit rep” template parameter has “forcing”
semantics. This would produce feet(2)
. However, note that this operation uses integer
division, which truncates: so, for example, inches(23).as<int>(feet)
would produce feet(1)
.
Tip
Prefer to omit the template argument if possible, because you will get more safety checks. The risks which the no-template-argument version warns about are real.
.in(unit)
, .in<T>(unit)
¶
This function produces the value of the Quantity
, re-expressed in the new unit. unit
can be
either a QuantityMaker
for the quantity’s unit, or an instance of the unit itself. See the unit
slots discussion for valid choices for unit
.
Example: getting the value of feet(3)
in inches
Consider this Quantity<Feet, int>
:
Then length.in(inches)
converts this quantity to inches, and returns the value, 36
, as an
int
.
The above example used the quantity maker, inches
. One could also use an instance of the unit
type Inches
, writing length.in(Inches{})
. The former is generally preferable; the latter is
mainly useful in generic code where the unit type may be all you have.
Without a template argument, .in(unit)
obeys the same safety checks as for the implicit
constructors: conversions at high risk for integer overflow or truncation
are forbidden. Additionally, the Rep
of the output is identical to the Rep
of the input.
With a template argument, .in<T>(unit)
has two differences.
- The output type will be
T
. - The conversion is considered “forcing”, and will be permitted in spite of any overflow or
truncation risk. The semantics are similar to
static_cast<T>
.
However, note that we may change this second property in the future. The version with the template
arguments may be changed later so that it does prevent lossy conversions. If you want this
“forcing” semantic, prefer to use .coerce_in(unit)
, and add the explicit template
parameter only if you want to change the rep. See
#122 for more details.
Example: forcing a conversion from inches to feet
inches(24).in(feet)
is not allowed. This conversion will divide the underlying value, 24
,
by 12
. Now, it so happens that this particular value would produce an integer result.
However, the compiler must decide whether to permit this operation at compile time, which
means we don’t yet know the value. Since most int
values would not produce integer results,
we forbid this.
inches(24).in<int>(feet)
is allowed. The “explicit rep” template parameter has “forcing”
semantics (at least for now; see #122).
This would produce 2
. However, note that this operation uses integer division, which
truncates: so, for example, inches(23).in<int>(feet)
would produce 1
.
Tip
Prefer to omit the template argument if possible, because you will get more safety checks. The risks which the no-template-argument version warns about are real.
Forcing lossy conversions: .coerce_as(unit)
, .coerce_in(unit)
¶
This function performs the exact same kind of unit conversion as if the string coerce_
were
removed. However, it will ignore any safety checks for overflow or truncation.
Example: forcing a conversion from inches to feet
inches(24).as(feet)
is not allowed. This conversion will divide the underlying value, 24
,
by 12
. While this particular value would produce an integer result, most other int
values
would not. Because our result uses int
for storage — same as the input — we forbid this.
inches(24).coerce_as(feet)
is allowed. The coerce_
prefix has “forcing” semantics. This
would produce feet(2)
. However, note that this operation uses integer division, which
truncates: so, for example, inches(23).coerce_as(feet)
would produce feet(1)
.
These functions also support an explicit template parameter: so, .coerce_as<T>(unit)
and
.coerce_in<T>(unit)
. If you supply this parameter, it will be the rep of the result.
Example: simultaneous unit and type conversion
inches(27.8).coerce_as<int>(feet)
will return feet(2)
.
Tip
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)¶
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: defining and using a template with a Quantity
NTTP
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 leave the safety of the library.
Recall that there are two main categories of operation: “arbitrary unit” operations, and “common unit” operations.
Comparison¶
Comparison is a common unit operation. If the
input Quantity
types are not identical — same Unit
and Rep
— then we first convert them
to their common type. Then, we perform the comparison by delegating to the
comparison operator on the underlying values.
If either input is a non-Quantity
type T
, but it has
a CorrespondingQuantity
, then that input can participate as if it
were its corresponding quantity.
Example of corresponding quantity comparison
We provide built-in equivalence with std::chrono::duration
types. Therefore, this comparison will work:
Here, the std::chrono::duration
variable will be treated identically to seconds(1.0)
. The
comparison will take place in units of milli(seconds)
(since that is the common unit), and
using a Rep of double
(since that is std::common_type_t<double, float>
). The variable
greater
will hold the value true
.
We support the following binary comparison operators:
==
!=
>
>=
<
<=
Addition¶
Addition is a common unit operation. If the
input Quantity
types are not identical — same Unit
and Rep
— then we first convert them
to their common type. The result is a Quantity
of this common type, whose value
is the sum of the input values (after any common type conversions).
If either input is a non-Quantity
type T
, but it has
a CorrespondingQuantity
, then that input can participate as if it
were its corresponding quantity.
Subtraction¶
Subtraction is a common unit operation. If the
input Quantity
types are not identical — same Unit
and Rep
— then we first convert them
to their common type. The result is a Quantity
of this common type, whose value
is the difference of the input values (after any common type conversions).
If either input is a non-Quantity
type T
, but it has
a CorrespondingQuantity
, then that input can participate as if it
were its corresponding quantity.
Multiplication¶
Multiplication is an arbitrary unit operation. This means you can reason independently about the units and the values.
- The output unit is the product of the input units.
- The output value is the product of the input values.
- The output rep (storage type) is the same as the type of the product of the input reps.
The output is always a Quantity
with this unit and rep, unless the units completely cancel
out (returning a unitless unit). If they do, then we return a raw
number.
If either input is a raw number, then it only affects the value, not the unit. It’s equivalent to
a Quantity
whose unit is a unitless unit.
Division¶
Division is an arbitrary unit operation. This means you can reason independently about the units and the values.
- The output unit is the quotient of the input units.
- The output value is the quotient of the input values.
- The output rep (storage type) is the same as the type of the quotient of the input reps.
The output is always a Quantity
with this unit and rep, unless the units completely cancel
out (returning a unitless unit). If they do, then we return a raw
number.
If either input is a raw number, then it only affects the value, not the unit. It’s equivalent to
a Quantity
whose unit is a unitless unit.
unblock_int_div()
¶
Experience has shown that raw integer division can be dangerous in a units library context. It conflicts with intuitions, and can produce code that is silently and grossly incorrect: see the integer division section of the troubleshooting guide for an example.
To use integer division, you must ask for it explicitly by name, by calling unblock_int_div()
on
the denominator.
Using unblock_int_div()
to explicitly opt in to integer division
This will not work:
However, this will work just fine:
It produces (miles / hour)(62)
.
Unary +
and -
¶
For a Quantity
instance q
, you can apply a “unary plus” (+q
) or “unary minus” (-q
). These
produce a Quantity
of the same type, with the unary plus or minus operator applied to the
underlying value.
Shorthand addition and subtraction (+=
, -=
)¶
The input must be a Quantity
which is implicitly convertible to the
target Quantity
type. These operations first perform that conversion, and then replace the target
Quantity
with the result of that addition (for +=
) or subtraction (for -=
).
Shorthand multiplication and division (*=
, /=
)¶
The input must be a raw number which is implicitly convertible to the target Quantity
’s rep.
These operations first perform that conversion, and then replace the target Quantity
with the result of that multiplication (for *=
) or division (for /=
).
Automatic conversion to Rep
¶
For any Quantity<U, Rep>
, if U
is a unitless unit, we provide
implicit conversion to Rep
, which simply returns the underlying value. This enables users to pass
such a Quantity
to an API expecting Rep
.
We do not provide this functionality for quantities of any other unit. In particular — and
unlike many other units libraries — we do not automatically convert other dimensionless units
(such as Percent
) to a raw number. While this operation is intuitively appealing, experience
shows that it does more harm than
good.
Mod (%
)¶
The modulo operator, %
, is the remainder of an integer division. Au only defines this operator
between two Quantity
types, not between a Quantity
and a raw number.
More precisely, suppose we have instances of two Quantity
types: Quantity<U1, R1> q1
and
Quantity<U2, R2> q2
. Then q1 % q2
is defined only when:
(R1{} % R2{})
is defined (that is, bothR1
andR2
are integral types).CommonUnitT<U1, U2>
is defined (that is,U1
andU2
have the same dimension).
When these conditions hold, the result is equivalent to first converting q1
and q2
to their
common unit, and then computing the remainder from performing integer division on their values.
Why this policy?
We restrict to same-dimension quantities because this not only gives a meaningful answer, but
the meaning of that answer is independent of the choice of units. Take length as an example: we
could imagine repeatedly subtracting q2
from q1
until we get a result that is smaller than
q2
. This final result is the answer we seek. It does not depend on the units for either
quantity.
The reason we express that answer in the common unit of q1
and q2
is because it’s the
simplest unit in which we can express it. (Note that this is the same unit we would get from
the operation of repeated subtraction as well, so this choice is consistent.)
rep_cast
¶
rep_cast
performs a static_cast
on the underlying value of a Quantity
. It is used to change
the rep.
Given any Quantity<U, R> q
whose rep is R
, then rep_cast<T>(q)
gives a Quantity<U, T>
, whose
underlying value is static_cast<T>(q.in(U{}))
.
Templates and Traits¶
Matching: typename U, typename R
¶
To specialize a template to match any Quantity
, declare two template parameters: one for the unit,
one for the rep.
Example: function template
This function template will match any Quantity
specialization, and nothing else.
Example: type template
// First, we need to declare the generic type template. It matches a single type, `T`.
template <typename T>
struct Size;
// Now we can declare a specialization that matches any `Quantity`, by templating on its unit
// and rep.
template <typename U, typename R>
struct Size<Quantity<U, R>> {
// Note: this example uses inline variables, a C++17 feature. It's just for illustration.
static constexpr inline std::size_t value = sizeof(R);
};
In this way, Size<T>::value
will exist only when T
is some Quantity
type (unless, of
course, other specializations get defined elsewhere).
::Rep
¶
Every Quantity
type has a public static alias, ::Rep
, which indicates its underlying storage
type (or, its “rep”).
Example
decltype(meters(5))::Rep
is int
.
::Unit
¶
Every Quantity
type has a public static alias, ::Unit
, which indicates its unit type.
Example
decltype(meters(5))::Unit
is Meters
.
::unit
¶
Every Quantity
type has a public static member variable, ::unit
, which is an instance of its
unit type.
Example
decltype(meters(5))::unit
is Meters{}
.
std::common_type
specialization¶
For two Quantity
types, one with unit U1
and rep R1
, and the other with unit U2
and rep
R2
, then std::common_type_t<Quantity<U1, R1>, Quantity<U2, R2>>
has the following properties.
-
It exists if and only if
U1
andU2
have the same dimension. -
When it exists, it is
Quantity<U, R>
, whereU
is the common unit ofU1
andU2
, andR
isstd::common_type_t<R1, R2>
.
As required by the standard, our
std::common_type
specializations are
SFINAE-friendly: improper combinations will
simply not be present, rather than producing a hard error.
AreQuantityTypesEquivalent¶
Result: Indicates whether two Quantity
types are equivalent. Equivalent types may be freely
converted to each other, and no arithmetic operations will be performed in doing so.
More precisely, Quantity<U1, R1>
and Quantity<U2, R2>
are equivalent if and only if both of
the following conditions hold.
-
The units
U1
andU2
are quantity-equivalent. -
The reps
R1
andR2
are the same type.
Syntax:
- For types
U1
andU2
:AreQuantityTypesEquivalent<U1, U2>::value