· 6 min read ·

Your Compiler Has Known About the Mars Orbiter Problem for Decades

Source: isocpp

In September 1999, NASA lost the Mars Climate Orbiter because one software component was outputting thruster data in pound-force seconds while the navigation team’s software expected newton-seconds. The spacecraft entered the Martian atmosphere at the wrong angle and burned up. The mission cost $327 million. The fix, in hindsight, would have been trivial: a type system that refuses to compile code that adds pounds to newtons.

This is not a problem that requires new research. The tools to enforce dimensional correctness at compile time have existed in C++ for years. Wu Yongwei’s recent look at the unit library landscape covers the state of the art and the proposals heading toward standardization. The interesting question is not whether it can be done, but why we are still not doing it by default, and what the path to standardization actually looks like.

What the Standard Library Already Demonstrates

The C++ standard library contains one excellent example of unit enforcement: std::chrono. When you write 500ms, you get a std::chrono::duration<long long, std::milli>. Subtracting two time_point values gives you a duration. Adding two time_point values does not compile. This is not magic; it is the same technique that unit libraries have been applying to every other physical domain for years.

auto t1 = std::chrono::steady_clock::now();
std::this_thread::sleep_for(500ms);
auto t2 = std::chrono::steady_clock::now();
auto duration = t2 - t1;  // duration<...>, fine
auto broken = t1 + t2;    // does not compile

The lesson from chrono is that this approach works, scales to real codebases, and adds no runtime overhead. The surprise is that it took until C++11 to standardize even this narrow case, and that the broader problem remains unsolved in the standard library fifteen years later.

The Library Landscape Before P3045

Several libraries have been filling the gap independently. Each represents a different point in the design space.

Boost.Units is the oldest serious attempt. It uses template metaprogramming to represent dimensions as lists of base-unit exponents, encoding that velocity is length^1 * time^-1. The approach is correct and comprehensive, but the error messages it generates when you mix units are famously hostile. A simple type mismatch can produce pages of template instantiation noise that obscures the actual problem.

nholthaus/units takes a more pragmatic approach, targeting C++14 and aiming for readable error messages and a reasonable API surface. It is header-only and widely used in embedded and robotics contexts where you want to pay nothing at runtime but catch unit errors at compile time. The library defines types like meters_per_second_t and provides conversion functions that either happen at compile time or are verified at compile time and execute as a single multiply at runtime.

#include <units.h>
using namespace units::literals;

units::velocity::meters_per_second_t v = 30.0_mps;
units::time::second_t t = 4.0_s;
units::length::meter_t d = v * t;  // 120 m

// This does not compile:
units::length::meter_t wrong = v + t;

mp-units is the most modern entry, requiring C++20, and serves as the reference implementation for the ongoing standardization proposal. It uses concepts extensively and supports a richer model that distinguishes between quantities and quantity points, a distinction that matters more than it might seem.

The Quantity vs. Quantity Point Distinction

One of the more subtle things that mp-units gets right is the affine space model. A temperature of 300 K and a temperature difference of 5 K are different things, even though both involve the unit kelvin. You can add 5 K to 300 K to get 305 K, but adding 300 K to 300 K is meaningless. The same applies to timestamps: subtracting two timestamps gives a duration, but adding two timestamps is not a physically coherent operation.

std::chrono already models this correctly via time_point versus duration. mp-units generalizes it through quantity_point versus quantity.

#include <mp-units/systems/si.h>
using namespace mp_units::si::unit_symbols;

auto temp1 = mp_units::quantity_point{300.0 * K};
auto delta = 5.0 * K;  // a quantity, not a point
auto temp2 = temp1 + delta;   // ok: point + quantity = point
auto diff  = temp2 - temp1;   // ok: point - point = quantity
auto bad   = temp1 + temp2;   // compile error

This is more than pedantry. The Fahrenheit to Celsius conversion is not just a scale factor; there is an offset. A library that only tracks units without tracking whether a value is absolute or relative will get this wrong silently.

What P3045R7 Proposes

P3045R7, authored primarily by Mateusz Pusz and Dominik Berner, proposes adding a quantities and units library to the C++ standard. The revision history alone tells a story about how seriously the committee is taking this: the paper has been through seven revisions as of 2025.

The proposal covers SI units and their derived units, handles dimensionless quantities correctly (angles, ratios), and defines the quantity point abstraction described above. It also addresses representation types, so you can have quantities backed by double, float, or integer types with predictable truncation behavior.

One design decision worth noting: the proposal does not add new language features. Everything is expressed through the existing template system, user-defined literals, and C++20 concepts. This is a deliberate choice to avoid the committee overhead of language changes, but it means the approach is fundamentally a library solution rather than a first-class language feature.

The Comparison With F#

F# took the language route. Its units of measure are a type system feature, not a library. You annotate numeric types directly:

[<Measure>] type kg
[<Measure>] type m
[<Measure>] type s

let mass: float<kg> = 70.0<kg>
let velocity: float<m/s> = 9.8<m/s>
let momentum = mass * velocity  // inferred as float<kg m/s>

The F# compiler handles the dimensional arithmetic natively. Units are erased at runtime, so there is no overhead. The error messages are concise because the compiler knows exactly what went wrong at the type level rather than inferring it from template instantiation failure.

This is the ideal. C++ cannot have it without a language extension, and language extensions require years of standardization work. The library approach is the practical path, and it is more capable than you might expect given that constraint. But it does mean that error messages will always be noisier than they should be, and the syntax will always be slightly more ceremonious than annotating a numeric literal inline.

The Zero-Overhead Argument Holds Up

The reason this conversation keeps coming back to C++ specifically is that C++ code often runs in contexts where performance matters: robotics, aerospace, automotive control systems, scientific computing. These are also the contexts where unit errors cause the most damage.

The good news is that the zero-overhead claim is genuine. When the compiler can see the full type information, unit conversions reduce to a single multiplication. Dimension checks are entirely compile-time. At runtime, a meters_per_second_t is just a double. The types are erased after verification, exactly as F# does, but through the template mechanism rather than a language annotation.

Benchmarks comparing nholthaus/units against bare double arithmetic show no measurable difference in optimized builds. mp-units reports the same. The cost is paid at compile time, and it is paid in compile time rather than runtime overhead, which is an acceptable trade for most projects.

Why This Matters for Everyday Code

The Mars Climate Orbiter is the famous example, but unit errors are common in much smaller contexts. API boundaries between teams are the most frequent source: one team’s function returns meters, another calls it expecting kilometers, and the result is wrong by a factor of 1000. The bug is reproducible, easy to fix, and completely invisible to the type system if you are passing double everywhere.

Adopting a unit library is a cultural shift as much as a technical one. Once your physics-adjacent code uses typed quantities, every function signature documents its units. Reviewers can catch unit mismatches in code review. The compiler catches the rest.

The path to standardization through P3045 matters because it lowers the adoption barrier. When unit types are in the standard library, they become the default choice rather than a dependency decision. Third-party libraries can interoperate on a shared vocabulary of units without each defining their own. The chrono integration already demonstrates this: std::chrono::duration types from different parts of a codebase interoperate correctly because they share a common type family.

Until P3045 lands, the existing libraries are production-ready. For a C++20 codebase, mp-units is the obvious choice given its direct lineage to the standard proposal. For C++14 or C++17 environments, nholthaus/units is mature and well-maintained. For code that already depends on Boost, Boost.Units covers the fundamentals even if the ergonomics are rough.

The compiler has had the tools to prevent unit disasters for a long time. Whether the codebase uses them is a choice.

Was this interesting?