In September 1999, NASA lost the Mars Climate Orbiter because one engineering team was using pound-force seconds and another was using newton-seconds. The spacecraft missed its orbital insertion window, skimmed too deep into the Martian atmosphere, and disintegrated. Total cost: $327.6 million. The root cause was a raw double passed between two subsystems with no type information attached to it.
That was 27 years ago. C++ developers have had compile-time type systems capable of catching exactly this class of error for almost as long, and yet most codebases still pass around bare floats and rely on naming conventions or comments to communicate units. Wu Yongwei’s recent overview on isocpp.org surveys the current library landscape and points toward P3045R7, the active proposal to add a quantities and units library to the C++ standard. It is worth going deeper than a survey, because the design decisions in P3045 are more interesting than the headline feature.
The Chrono Precedent
The standard library already proves that typed units work in practice. std::chrono has been in the standard since C++11, and its design shows exactly what is possible:
auto t1 = std::chrono::steady_clock::now();
std::this_thread::sleep_for(500ms);
auto t2 = std::chrono::steady_clock::now();
auto duration = t2 - t1;
auto bad = t1 + t2; // Does not compile
std::cout << duration / 1.0ms;
The line t1 + t2 is rejected at compile time because adding two time points is semantically meaningless. This is not a runtime check. The type system enforces it. The fact that chrono limits itself to time while leaving every other physical domain to raw numerics has always felt like an arbitrary boundary.
User-defined literals make the ergonomics tractable. 500ms, 1s, and 2h are all valid C++ since C++14, and std::chrono uses them extensively. The same mechanism generalizes cleanly to physical units: you can write 100.0_q_m for metres or 9.8_q_m_per_s2 for gravitational acceleration, which reads nearly as well as the mathematics it represents.
Three Libraries, Three Eras
Before P3045 can land in the standard, developers have had to choose from existing libraries, each representing a different generation of C++ idioms.
Boost.Units is the oldest significant entry. It uses the Boost.MPL template metaprogramming framework to encode dimensions and units in types. The safety guarantees are solid and the library is comprehensive, but the compilation overhead is notorious. Boost.MPL was written for C++03, and the template instantiation patterns it relies on translate into build times that were painful a decade ago and remain painful today. For a library you include in most translation units, that matters.
nholthaus/units takes a more pragmatic approach: header-only, C++14, no external dependencies. The implementation is considerably simpler than Boost.Units and compile times are much better. It covers SI units and a reasonable set of derived units. For projects that need basic dimensional safety without committing to Boost or C++20, it fills the gap adequately.
mp-units is the current state of the art and the direct basis for P3045. It requires C++20 and uses concepts, constexpr evaluation, and CTAD throughout. The design is more expressive than its predecessors and the compile-time overhead is dramatically lower than Boost.Units, since it can lean on language features rather than MPL gymnastics. Mateusz Pusz presented it at CppCon 2023, and the library has continued to evolve alongside the standardization work.
How the Type System Works
The core abstraction in mp-units and P3045 is the quantity type, parameterized by a reference unit and a representation type:
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
quantity<si::metre / si::second, double> speed = 60.0 * (km / h);
quantity<si::metre, double> distance = 100.0 * m;
quantity<si::second, double> time = distance / speed;
quantity<si::kilogram, double> mass = 70.0 * kg;
auto broken = distance + time; // Compile error: length + time is undefined
Unit conversions happen automatically when the dimensions are compatible:
quantity<si::metre, double> d1 = 1.0 * km; // Stores 1000.0
quantity<si::metre, double> d2 = 500.0 * m;
auto total = d1 + d2; // 1500.0 metres, no explicit conversion needed
The conversion is exact where possible and uses the representation type’s arithmetic otherwise. There is no runtime dispatch, no virtual functions, no type erasure. The unit information exists only in the type and is stripped away before code generation.
The Affine Space Distinction
The most interesting design decision in P3045 is one that most introductions to unit libraries skip over: the distinction between quantity and quantity_point.
A quantity represents a difference or displacement: 5 metres, 3 seconds, 10 kelvin. A quantity_point represents an absolute position in some space: a location 5 metres from the origin, a timestamp 3 seconds after epoch, a temperature of 300 kelvin. These are mathematically different objects with different valid operations, and confusing them is a classic source of bugs.
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
quantity_point boiling{373.15 * K};
quantity_point freezing{273.15 * K};
quantity<si::kelvin, double> diff = boiling - freezing; // Valid: point minus point gives displacement
auto bad = boiling + freezing; // Compile error: adding two absolute temperatures is meaningless
This distinction comes from affine space mathematics. An affine space has points and vectors; you can subtract two points to get a vector, you can add a vector to a point to get a point, but you cannot add two points. The chrono library already models this: time_point - time_point gives a duration, but time_point + time_point does not compile. P3045 generalizes the pattern to all physical quantities.
The practical consequence is that temperature arithmetic finally works correctly. In most codebases, temperature is stored as a plain double. The Celsius to Fahrenheit conversion formula involves both scaling and offset. If you model temperatures as quantity<celsius, double> without the affine distinction, you can accidentally add two absolute temperatures and get a result that compiles but is physically incoherent. With quantity_point, the type system prevents it.
This matters beyond temperature. Absolute timestamps, geographic coordinates relative to a datum, and energy levels measured from a ground state all share this structure. Modeling them as displacements when they are actually positions is a subtle category error, and catching it at compile time costs nothing at runtime.
What P3045 Actually Proposes
P3045R7 is comprehensive. It defines the full SI base dimensions, derived SI units, a selection of common non-SI units, the quantity and quantity_point class templates, arithmetic operators with dimensional analysis, conversion functions between compatible units, integration with std::format for printing quantities with their units, and support for custom dimensions defined outside the standard library.
The proposal is targeting C++26, though standardization timelines have a way of shifting. As of the R7 revision, it is still in active review within WG21. Seven revisions over a few years suggests the committee is engaging with it seriously rather than letting it stall the way earlier unit proposals did.
For the practical question: mp-units already works today. If you are writing C++20 code and you have any physics, sensor data, or measurement processing in your codebase, there is no reason to wait for the standard. The library is available on GitHub, packaged through Conan and vcpkg, and the API tracks the P3045 proposal closely enough that migration will be straightforward once the standard ships.
The Gap Between Available and Used
The tools have existed for a long time. Boost.Units shipped in 2003. The chrono library has been in the standard for fifteen years. nholthaus/units has been on GitHub for over a decade. Adoption in production C++ outside of aerospace and automotive domains remains low.
Part of this is familiarity. When you join a codebase where everything is double, adding typed quantities requires either a partial migration, which means both styles coexist and their boundary is its own source of bugs, or a full rewrite. Neither is appealing during normal feature development.
Part of it is the Boost problem. For many projects, Boost.Units is the first library that comes up in a search, and the compile-time overhead is enough to discourage adoption before the ergonomics are even evaluated. mp-units addresses the performance problem, but it requires C++20, which remains blocked by toolchain constraints in some embedded and legacy environments.
The case for standardization is precisely that a standard library solution changes the adoption calculus. You no longer need to justify adding a dependency, negotiate with your build system, or worry about long-term maintenance. The <units> header ships with the compiler, the way <chrono> does. That normalization matters more than any individual library feature.
The Mars Climate Orbiter is the famous example, but it is far from the only one. Medical device software, financial calculation engines, and industrial control systems all have unit-related bugs in their history. The compiler cannot catch every class of error, but this one, passing a value in the wrong unit to a function that expects a different unit, is exactly the kind of mistake type systems exist to prevent. The infrastructure to do it has been available in C++ for years. P3045 removes the last remaining excuse not to use it.