Skip to content

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 the std::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:

Quantity<Meters, double> height{3.0};  // Does NOT work in Au

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.

  1. If the input quantity has a different dimension, then the operation is intrinsically meaningless and we forbid it.

  2. 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.

  3. 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.

template <typename Unit, typename Rep>
class Quantity {
    Quantity(Zero);
};

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:

QuantityD<Meters> height{ZERO};

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.

template <typename Unit, typename Rep>
class Quantity {
    Quantity();
};

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>:

auto t = seconds(4.56);

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
auto length = inches(60);
length.data_in(inches)++;

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>:

auto length = feet(3);

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.

  1. The output Rep will be T.
  2. 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>:

auto length = feet(3);

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.

  1. The output type will be T.
  2. 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
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 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:

const bool greater = std::chrono::duration<double>{1.0} > milli(seconds)(999.9f);

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:

miles(125) / hours(2);
//         ^--- Forbidden!  Compiler error.

However, this will work just fine:

miles(125) / unblock_int_div(hours(2));

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:

  1. (R1{} % R2{}) is defined (that is, both R1 and R2 are integral types).
  2. CommonUnitT<U1, U2> is defined (that is, U1 and U2 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
template <typename U, typename R>
Quantity<U, R> negate(Quantity<U, R> q) {
    return -q;
}

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.

  1. It exists if and only if U1 and U2 have the same dimension.

  2. When it exists, it is Quantity<U, R>, where U is the common unit of U1 and U2, and R is std::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.

  1. The units U1 and U2 are quantity-equivalent.

  2. The reps R1 and R2 are the same type.

Syntax:

  • For types U1 and U2:
    • AreQuantityTypesEquivalent<U1, U2>::value