Skip to content

quantity.hpp

Quantity — compile-time dimensional analysis.

This header declares 1 type (13 members), 2 free functions, and 10 type aliass.

Extracted from include/shulib/units/quantity.hpp — this page is that header's documentation, reformatted, so it cannot disagree with the code. Prose about how to think about the API lives in the user guide; worked recipes live in the cookbook; this page is the complete, mechanical list of what exists.

Contents

class Quantity

template <int L, int A, int T, int E, int I> class Quantity

A double that carries its DIMENSION in its type: the five integer exponents are length, angle, time, voltage, current. The stored value is ALWAYS canonical — inches, radians, seconds, volts, amperes, and combinations of them — so there is no unit tag to read and no conversion left for a caller to forget. Same-dimension arithmetic behaves normally; mixing dimensions under +/- names no overload and fails to compile, while * and / derive the result's dimension. One double wide and constexpr throughout, so the checking costs nothing at run time. A HEADING is not this type: use math::Angle, which owns wrapping (see the banner above).

class, declared at include/shulib/units/quantity.hpp:38.

Quantity::Quantity

constexpr Quantity() = default

The dimensioned zero. Never indeterminate — the stored value is 0.0 whether or not the declaration writes braces.

function, declared at include/shulib/units/quantity.hpp:42.

Quantity::Quantity (overload 2)

constexpr explicit Quantity(double canonicalValue) noexcept

Construct from a value ALREADY in canonical units. Explicit, so a bare double never silently becomes a dimensioned quantity — and unchecked, so a NaN, or a number that is really in degrees or milliseconds, is stored as though it were canonical. Prefer the units::literals (24_in, 20_ms), which do the conversion once, where the number is written.

function, declared at include/shulib/units/quantity.hpp:49.

Quantity::value

[[nodiscard]] constexpr double value() const noexcept

The value in canonical units (inch / radian / second / volt / ampere, or a derived combination). The one way out of the type — everything past this point is a bare double again, so unwrap as late as possible.

function, declared at include/shulib/units/quantity.hpp:54.

Quantity::operator+=

constexpr Quantity& operator+=(Quantity o) noexcept

Add in place. A different dimension is a different TYPE, so there is no conversion left to get wrong: len += dt names no overload at all.

function, declared at include/shulib/units/quantity.hpp:59.

Quantity::operator-=

constexpr Quantity& operator-=(Quantity o) noexcept

Subtract in place. Signed — the value goes negative rather than saturating at zero.

function, declared at include/shulib/units/quantity.hpp:61.

Quantity::operator+

[[nodiscard]] friend constexpr Quantity operator+(Quantity a, Quantity b) noexcept

Sum, dimension unchanged — + is the one operator here that CANNOT derive a new type: the exponents * and / add and subtract simply carry through. A bare number is not an operand: the constructor is explicit and there is no Quantity + double overload (unlike * and /, which do take a dimensionless scalar), so len + 2.0 names no overload at all — write len + 2_in.

function, declared at include/shulib/units/quantity.hpp:68.

Quantity::operator-

[[nodiscard]] friend constexpr Quantity operator-(Quantity a, Quantity b) noexcept

Difference a - b, signed and unclamped: b larger than a yields a negative quantity of the same dimension.

function, declared at include/shulib/units/quantity.hpp:73.

Quantity::operator- (overload 2)

[[nodiscard]] friend constexpr Quantity operator-(Quantity a) noexcept

Negation — reverses the sense of a signed quantity (a displacement, a velocity, a voltage). shulib declares no abs for Quantity; take std::abs(q.value()) when a magnitude is what you want.

function, declared at include/shulib/units/quantity.hpp:79.

Quantity::operator*

[[nodiscard]] friend constexpr Quantity operator*(Quantity a, double s) noexcept

Scale by a DIMENSIONLESS factor; the dimension is unchanged. When the factor itself carries units, use the Quantity-by-Quantity operator below — that one changes it.

function, declared at include/shulib/units/quantity.hpp:84.

Quantity::operator* (overload 2)

[[nodiscard]] friend constexpr Quantity operator*(double s, Quantity a) noexcept

The same scaling with the scalar written on the left, so 0.5 * dt and dt * 0.5 are both spellable and identical.

function, declared at include/shulib/units/quantity.hpp:87.

Quantity::operator/

[[nodiscard]] friend constexpr Quantity operator/(Quantity a, double s) noexcept

Divide by a dimensionless factor; dimension unchanged. s == 0 yields infinity by IEEE rules, not an error. There is deliberately no double / Quantity overload, and the asymmetry with the DOUBLED multiply above is the point rather than an oversight: this block is scalar SCALING, which leaves the dimension alone, while a scalar divided by a quantity always INVERTS it — a different operation, belonging with the Quantity-by-Quantity operators. Write Number{1.0} / dt, which produces the inverse dimension through exactly those.

function, declared at include/shulib/units/quantity.hpp:95.

Quantity::operator==

[[nodiscard]] friend constexpr bool operator==(Quantity a, Quantity b) noexcept

EXACT equality of the canonical doubles — there is no tolerance anywhere in here. Two quantities computed by different routes will rarely compare equal; test std::abs((a - b).value()) against a tolerance you chose instead.

function, declared at include/shulib/units/quantity.hpp:101.

Quantity::operator<=>

[[nodiscard]] friend constexpr std::partial_ordering operator<=>(Quantity a, Quantity b) noexcept

Orders by canonical value. partial_ordering, not strong_ordering, because doubles are: if either side is NaN the result is unordered, and <, >, <= and >= are then ALL false.

function, declared at include/shulib/units/quantity.hpp:105.

operator*

template <int L1, int A1, int T1, int E1, int I1, int L2, int A2, int T2, int E2, int I2> [[nodiscard]] constexpr auto operator*(Quantity<L1, A1, T1, E1, I1> a, Quantity<L2, A2, T2, E2, I2> b) noexcept -> Quantity<L1 + L2, A1 + A2, T1 + T2, E1 + E2, I1 + I2>

Multiply two dimensioned quantities: the five exponents ADD, so the result TYPE is derived rather than declared anywhere. Velocity * Time -> Length; Voltage * Current -> Power; Number * anything -> that same dimension.

free function, declared at include/shulib/units/quantity.hpp:118.

operator/

template <int L1, int A1, int T1, int E1, int I1, int L2, int A2, int T2, int E2, int I2> [[nodiscard]] constexpr auto operator/(Quantity<L1, A1, T1, E1, I1> a, Quantity<L2, A2, T2, E2, I2> b) noexcept -> Quantity<L1 - L2, A1 - A2, T1 - T2, E1 - E2, I1 - I2>

Divide two dimensioned quantities: the five exponents SUBTRACT. Length / Time -> Velocity, Velocity / Time -> Acceleration, Length / Length -> Number. Dividing by a zero quantity yields infinity by IEEE rules, not an error.

free function, declared at include/shulib/units/quantity.hpp:127.

Number

using Number = Quantity<0, 0, 0, 0, 0>

dimensionless — a ratio, a gain, Length/Length

type alias, declared at include/shulib/units/quantity.hpp:133.

Length

using Length = Quantity<1, 0, 0, 0, 0>

inches; signed — a displacement, not a size

type alias, declared at include/shulib/units/quantity.hpp:134.

AngleDim

using AngleDim = Quantity<0, 1, 0, 0, 0>

radians, for RATES; a heading is math::Angle

type alias, declared at include/shulib/units/quantity.hpp:135.

Time

using Time = Quantity<0, 0, 1, 0, 0>

seconds; 20_ms converts to 0.02 at the literal

type alias, declared at include/shulib/units/quantity.hpp:136.

Voltage

using Voltage = Quantity<0, 0, 0, 1, 0>

volts — what IMotor::setVoltage commands

type alias, declared at include/shulib/units/quantity.hpp:137.

Current

using Current = Quantity<0, 0, 0, 0, 1>

amperes — the 5th dim, for IMotor/IBattery

type alias, declared at include/shulib/units/quantity.hpp:138.

Velocity

using Velocity = Quantity<1, 0, -1, 0, 0>

in/s — Length/Time, derived, never declared

type alias, declared at include/shulib/units/quantity.hpp:139.

Acceleration

using Acceleration = Quantity<1, 0, -2, 0, 0>

in/s^2 — Velocity/Time, i.e. Length/Time^2

type alias, declared at include/shulib/units/quantity.hpp:140.

AngularVelocity

using AngularVelocity = Quantity<0, 1, -1, 0, 0>

rad/s — a yaw RATE; ChassisSpeeds' third term

type alias, declared at include/shulib/units/quantity.hpp:141.

Power

using Power = Quantity<0, 0, 0, 1, 1>

watts (V·A) — proof that Current composes

type alias, declared at include/shulib/units/quantity.hpp:142.

Design commentary, from the header

The header opens with the reasoning behind these shapes. It is reproduced here in full because a reference that only lists signatures teaches nobody why.

The header’s own reasoning — 22 lines
 Quantity<L, A, T, E, I> — compile-time dimensional analysis.

 The integer exponents of the five base dimensions live in the TYPE:
     L = length, A = angle, T = time, E = electric potential (voltage),
     I = electric current (amperes).
 (I was added 2026-06-19, additively, for IMotor/IBattery current() — F4 review.
  The original four dims and their semantics are unchanged; F3 stays intact.)
 Consequences, all enforced by the compiler:
   * adding/subtracting different dimensions FAILS TO COMPILE,
   * multiply/divide compute the resulting dimension automatically
     (Length / Time -> Velocity, Velocity / Time -> Acceleration, ...),
   * the stored value is ALWAYS canonical: inch, radian, second, volt, AMPERE. The ampere
     was missing from this list while the same header declared the current dimension and
     the Current alias, and hal/pros/battery.hpp stores units::Current{ma / 1000.0} — a
     canonical ampere, not a derived combination of the other four.

 This kills two bug classes at compile time: "degrees into cos/sin" and
 "milliseconds into a seconds-based gain". (master plan §7, §13 #3; Freeze F3.)

 Headings use the dedicated wrapping type shulib::math::Angle; the angle
 dimension here exists for RATES (e.g. angular velocity rad/s) and bookkeeping.

Last updated 2026-08-15 — published from the main release branch, commit fa545ff. This site tracks releases, so between them it can lag the repository — that lag is deliberate, and it is what keeps the development log off the public site. For the current state of the code, see the repository.