Skip to content

Comparison of Alternatives

There are plenty of other open source C++ units libraries, many quite well established. However, the tradeoffs required to use these libraries can be so significant that many people can’t or won’t use them. For example: the compiler errors may be inscrutable or overwhelming; the compilation process may become unacceptably slow; or, the required C++ standard may simply be too new for a user.

Au is an accessible, production-tested alternative. We provide a number of rare or outright novel features, with a small compile time footprint — and we’re compatible with every C++ version back to the mature and widely available C++14 standard. Key features include:

  • Fully unit-safe APIs, on both entry and exit.
  • The “safety surface”: conversions that adapt to the overflow risk based on both conversion magnitude, and storage type.
  • Highly composable “quantity maker” APIs make it easy to both compose new units, and apply unit prefixes, on the fly.
  • Human-readable and concise compiler errors, via strong typenames for units.
  • Flexible Constant types with perfect conversion policies.
  • Abbreviated construction via both unit symbols and user-defined literals.
  • The Zero type: novel, fluent handling of construction, comparison, and sign handling for quantities.
  • Ease of migration (both to and from Au): with minimal setup, we support bidirectional implicit conversions with equivalent types from any other units library.
  • Support for single-header-file delivery, but with easy customization of units and features to include.
  • Proven track record supporting embedded applications as first class citizens, via such features as our safe handling of integer Rep, treating all Reps on equal footing, and our easy ability to exclude expensive <iostream> support.
  • Intelligent, unit-aware functions for rounding and computing inverses.
  • Minimal friction by using a single, short namespace: everything’s in au::.

Alternatives considered here

We’ll consider several of the most prominent alternatives in more detail. While there are many more libraries, the ones we consider here are included for being especially pioneering or popular (or both). Here, we list those libraries, indicate which version we considered, and say a few words about why we included it in the analysis.

  • Boost Units (version: 1.2, from Boost version 1.89.0)
    • One of the longest-standing C++ unit libraries, and the most prominent pre-C++14 option.
  • nholthaus/units (versions: 2.3.5, and 3.6.1)
    • Kicked off the revolution in modern (that is, post-C++11 watershed) units libraries.
    • Its laser-sharp focus on accessibility and low friction have made it probably the most widely used C++ units library to date.
    • We assess the 2.x and 3.x lines in separate columns, because they are effectively different libraries.
      • 3.x requires C++23 (2.x targeted C++14), renames the core vocabulary, and closes a large number of long-standing gaps.
      • 2.x has been archived as of August 2026 (branch archive/2.3.5), but we retain it because it is very commonly used in the wild.
    • If you are choosing today, the deciding question is usually simply whether you can meet the C++23 requirement. If you can’t, the 2.x column is the one that applies to you — and it now describes an unmaintained library.
  • bernedom/SI (version: 2.5.4)
    • A newer, C++17-compatible offering with a large number of GitHub stars.
  • mp-units (version: 2.5.0)
    • A library designed to take full advantage of ultra-modern (that is, post-C++20 watershed) features, such as concepts and non-template type parameters (NTTPs).
    • mp-units is leading the efforts towards a standard C++ units library, both by field testing new API designs, and by coordinating with the authors of other leading units libraries.

Detailed comparison matrices

Here’s a more detailed comparison to the alternatives listed above. We’ll use the following legend1:

Legend Lacks feature /
poor support
Fair /
basic support
Good /
solid support
Best support
(of libraries considered here)

Obtaining the library

These are the first criteria to consider. They will tell you whether you can even use the library at all, and if so, how hard it will be to obtain.

nholthaus
Boost 2.x 3.x bernedom/SI mp-units Au
C++ Version Compatibility

The minimum C++ standard required to use the library.

C++98 C++14 C++23 C++17 C++20 C++14
Ease of Acquisition

Ease of including this library in projects using a wide variety of build environments

Examples of things we look for:

  1. Can you vendor it without a package manager?
  2. Does it support your build system natively (CMake, bazel)?
  3. Is it in the package managers you already use (conan, vcpkg)?
  4. Is there a single-header option?
Part of boost
  • Single, self-contained header
  • On conan and vcpkg
  • Header-only; full CMake support
  • Debian, RPM, tarball, and Ubuntu PPA artifacts
  • Available on vcpkg
  • No more single header option
Available on conan Available on conan and vcpkg
  • Full support for bazel and CMake
  • Available on conan and vcpkg, thanks to community support
  • Supports single-header delivery, with features:
    • Easy to customize units and I/O support
    • Version-stamped for full reproducibility

Note

These ratings are written with all users and projects in mind. Keep in mind that what matters for you is your project.

For example: mp-units gets low accessibility ratings because of its steep C++20 minimum requirement, and its dependence on a package manager to make the installation easy. However, if your project is already compatible with C++20, and already uses conan, then these “low” ratings would be completely irrelevant for you.

The same goes for nholthaus 3.x, whose C++23 requirement is the steepest here: if you’re already on C++23, that rating simply doesn’t apply to you. Note, though, that this one cuts both ways. Because the 2.x line is now archived, a project that can’t move to C++23 no longer has a maintained version of this library to choose.

Generic developer experience

Next: how will this library change the generic developer experience? Leaving aside any library features, conventions, or implementation strategies, there are two main impacts to developer experience.

  1. Your program will take longer to compile, because the compiler is doing more work to produce essentially the same program.

  2. You will get more compiler errors that developers will need to understand and fix.

These costs purchase significant benefits, but we still want them to be as small as possible.

Tip

Note that Au is the only units library that provides both readable compiler errors and fast compilation times!

nholthaus
Boost 2.x 3.x bernedom/SI mp-units Au
Compilation Speed

The extra time the library adds to compiling a translation unit, compared to no units library.

  • Poor: typically adds several seconds per translation unit
  • Fair: enough that end users tend to notice
  • Good: not "subjectively noticeable"
Comparable to Au; sometimes less, sometimes more Very slow (adds multiple seconds), but can be greatly improved by removing I/O support and most units
  • Default <units.h> is the most expensive include we measured (~4x the 2.x penalty), and dropping <iostream> no longer helps
  • Per-dimension headers cut that ~4x, to roughly the 2.x penalty
Overall speed champ: roughly 2/3 the penalty of Au and Boost
  • Default config 2 to 3 times as expensive as nholthaus
  • Lean includes brings it on par with nholthaus, though lack of symbols hurts code quality
  • C++20 modules should mitigate all concerns
  • Substantial compile-time work is on main, slated for 2.6.0, including an "essential" symbols header that should blunt the tradeoff above --- worth re-measuring once it ships
  • Baseline performance is great (only bernedom/SI is faster)
  • Includes `fwd.hh` headers for even more flexibility
Compiler Error Readability

The ability to understand errors when the library catches a mistake it was designed to catch.

  • Poor: Excessively long, nested types
  • Fair: Short, but dimension names lacking
  • Good: Brief typenames with user-facing unit names
Infamously challenging Positional dimensions
  • Named unit types in errors (meters<double>), not 2.x's positional std::ratio lists
Alias for unit template Pioneered strong typedefs for units
  • Strong unit typenames appear in errors
  • Short namespace minimizes clutter
  • Detailed troubleshooting guide

Ongoing maintenance

The last thing to consider before diving into features is how the library will evolve over time. There are two extremes that a production-worthy library must avoid:

  • Changing too much, especially when a new version forces a monolithic, codebase-wide change.
  • Changing too little, especially when the library becomes unmaintained or abandoned.

These opposite extremes have the same effect: they lock you into an old and ever-aging version of the code, depriving you of bugfixes and improvements. The ideal library would be one that is actively maintained, but that respects its production users and makes upgrades as smooth and incremental as possible.

nholthaus
Boost 2.x 3.x bernedom/SI mp-units Au
Actively Maintained

Does the library respond to issues? Is it continuing to receive regular commits? Does it put out new releases, ideally at least once per year?

Long unmaintained.

  • No releases since 2010; no significant releases since March 2007
  • Most issues are open and unanswered

No longer maintained.

  • Last release September 2025, archived as of August 2026 (branch archive/2.3.5); superseded by 3.x
  • The project's default branch, with frequent releases
  • Backlog cleared: all pre-existing issues closed in August 2026
  • Still receiving commits, but no release since 2022
  • Most issues closed/addressed, but some issues have gone multiple years with no response
  • Regular commits and releases
  • Issues responded to and closed over time
  • Regular commits and releases
  • External issues always promptly responded to, usually closed
Smooth Upgrades

The ideal library...

  • enables incremental upgrades (no "megadiff" required)
  • clearly calls out each breaking change, and explains how to adapt

Not Applicable:
No new releases to judge

  • Updates within the 2.x family were usually smooth
  • Now that 2.x is archived, the only upgrade left is the monolithic move to 3.x
  • Thorough migration guide
  • 2.x to 3.x is monolithic, with no syntax that works in both versions
  • Breaking changes ship in patch releases (e.g., 3.4.4 changed the cosh/sinh/tanh signatures)
No evidence of user-reported issues with upgrades
  • Good quality release notes, with breaking changes clearly called out
  • Upgrade from 0.8 to 2.0 badly broke many users who were treating it as production software

As incremental as possible:

  • Every breaking change provides a syntax that works in both old and new versions
  • Starting from 0.5.0, future-proof releases let you tackle breaking changes one at a time

Known best practices violations

Users have a right to expect that a units library follows standard best practices: both for C++ specifically, and for programming more generally. Any violations of this expectation should be catalogued explicitly, so that users can be aware of them. This final generic category gives us a place to do that.

Note that the expected state for every library is an empty state: either grey (“N/A”) for libraries we haven’t assessed in detail, or blue (“good”) for libraries we’re more familiar with. Any library with a non-empty cell can improve their rating by fixing the issues.

Warning

A blue cell means “we don’t know of any”, not “we audited this library and found none”. We are not in a position to audit libraries in depth for best practices. Any issues we do find are generally discovered incidentally, while researching other rows.

Read an empty cell as absence of evidence, not evidence of absence.

nholthaus
Boost 2.x 3.x bernedom/SI mp-units Au
Known best practice violations

Departures from the rules C++ programs rely on: operators that don't mean what the language says they mean, undefined behavior, etc.

  • Floating point == is not transitive (#118, closed as wontfix)
  • Floating point == is not transitive (#118, closed as wontfix)
  • Per-dimension include files are prone to ODR violations and UB (#378, acknowledged as genuine UB, kept as-is for now)
  • Addition is not commutative (takes the left operand's unit instead of common unit; #381).

Library features

At this point, you’ve assessed:

  • whether you can use each library at all;
  • how hard it will be to add to your project;
  • what costs you’ll pay in developer experience if you do;
  • how you can expect it to evolve over time;
  • and, whether any of them is known to break the rules.

Now we’re ready to compare the libraries “as units libraries” — that is, in terms of their core features.

Note

The features are listed, very roughly, in order of importance. Counting up the colors in each column won’t give an accurate picture. The rows near the top matter more — sometimes, much more — than the rows further down.

Of course, what matters the most for you are your use cases and criteria!

nholthaus
Boost 2.x 3.x bernedom/SI mp-units Au
Conversion Safety

Guarding against unit conversions that are likely to produce large errors.

(For example: we can convert an integer number of feet to inches, but not vice versa.)

Integer Reps unsafe
  • Implicit only when lossless, matching std::chrono (#225)
  • consteval-checked conversions for compile-time-known values
  • Overflow protection for intermediate results
  • Still no adaptation to overflow risk in the result
Integer Reps unsafe Policy consistent with std::chrono library Meets `std::chrono` baseline, plus:
  • Automatically adapts to level of overflow risk
  • Can separately opt out of checking for overflow and truncation
  • Runtime conversion checkers
  • Constants have perfect conversion policy
Unit Safety

The ability to judge the unit-correctness of every individual line of code by inspection, in isolation.

  • Poor: can achieve indirectly, by casting to known type before retrieving value.
  • Fair: provides unit-safe interfaces.
  • Good: only provides unit-safe interfaces.
.value() documented as unsafe; the safe route is q.to<meters>().value() Only contains unit-safe interfaces Only contains unit-safe interfaces
Low Friction

How easy it is to develop with the library. Criteria include:

  • Headers: few, or easily guessable
  • Simple namespace structure
  • Reasonable, safe implicit conversions
  • Generally high learning curve
  • No (non-trivial) implicit conversions
  • Many headers; hard to guess
  • Single file is very easy
  • User-friendly API typenames (meter_t, ...)
  • Namespaces add verbosity, and friction (for example, math:: namespace prevents ADL)
  • 2.x's namespace friction is gone (inline namespaces; ADL for math)
  • User-friendly typenames, with CTAD
  • Literal suffixes still share one flat namespace, so they can collide
  • Single, short namespace
  • Implicit conversions, but not safe ones
  • Multiple headers, but easy to guess (one per dimension)
  • Implicit conversions with good basic safety
  • Multiple headers, one per system
  • Longer and more nested namespaces
  • Namespaces: just one (for non-abbreviated names), and it's short
  • Includes: either single-header, or easily-guessable header per unit
  • Implicit conversions, and they adapt to the overflow risk
Composability

The ability to fluently combine the abstractions for units and prefixes to form new units on the fly.

  • Can compose units, prefixes, dimensions, and quantity (point) makers
  • Type names clunky to compose: must write decltype
No
  • Unit constants compose: 3.0 * kg * m / (s*s) yields newtons
  • Prefixes don't apply to unit constants on the fly
No
  • Can compose units, prefixes, dimensions, and quantity types
  • C++20's Non-type template parameters (NTTPs) enable composable type names
  • Can compose units, prefixes, dimensions, and quantity (point) makers
  • Type names clunky to compose: must write decltype or use traits
Unit-aware I/O

The ability to print quantities along with information about their units. Examples:

  • <iostream>, preferrably toggleable
  • Unit labels available even without <iostream>
  • fmtlb (std::format after C++20)
  • Toggleable <iostream> support
  • Impressively configurable output (format_mode, autoprefix_mode)
  • No fmtlib support
  • Toggleable <iostream> support
  • No fmtlib or std::format support
  • Toggleable <iostream> support
  • Unit labels available even without <iostream>
  • Supports std::format, including the full numeric spec grammar
  • Toggleable <iostream> support
  • Unit labels available even without <iostream>
  • No fmtlib support
  • Supports <iostream>
  • Unit labels available even without <iostream>
  • Supports std::format
  • Many customization options
  • Toggleable <iostream> support
  • Unit labels available even without <iostream>
  • Supports std::format
Mixed-Rep Support

The ease of freely mixing different storage types ("Reps") in the same program.

Possible, but user-facing types use a global "preferred" Rep. Types name their own Rep (meters<int>); the global default applies only to meters<>
Unit-aware math

Unit-aware versions of common mathematical functions (`max`, `abs`, `sin`, `round`, and so on).

  • Wide variety of functions
  • round, ceil, and so on are not unit-safe
  • Wide variety of functions
  • round, ceil, and so on operate in whatever unit the quantity happens to hold, so they are not unit-safe
  • Wide variety of functions, found by ADL
  • round, ceil, floor, and trunc have unit-safe versions
  • No unit-aware inverse
No
  • Wide variety of functions
  • Unit-safe APIs for round, ceil, and so on
  • Smart, unit-aware inverse functions
  • Wide variety of functions
  • Unit-safe APIs for round, ceil, and so on
  • Smart, unit-aware inverse functions
Generic Dimensions

The ability to write (template) functions that operate on any dimensionally consistent inputs.

(For example, a function that takes any length and time quantities, and returns the appropriate speed quantity.)

Generic templates, constrained with traits Generic templates, constrained with traits
  • Public concept vocabulary (UnitType, same_dimension, ...)
  • A concept per dimension (units::Velocity auto v), 50 of them, one beside each is_<dimension>_unit trait
Generic templates, constrained with traits
  • Concepts name a specific quantity: QuantityOf<isq::length> auto
  • Which spelling you pick (a quantity spec, or its kind_of) changes what the function accepts
Currently clunky. Could be better by adding concepts in extra C++20-only file, without compromising C++14 support.
Extensibility

How easy it is to add new units, dimensions, or systems.

Can add new units and dimensions
  • One-line macro defines new units, with prefixes and literals
  • Can now add new base dimensions (#131), and derive compound dimensions from them
Can add new units and dimensions Can even handle, e.g., systems of "natural" units Can add new units and dimensions
Ease of Migration

Support for two migration use cases:

  • From "no units" to this library
  • Between this library and another units library (either direction)
No interop with other units libraries No interop with other units libraries
  • std::chrono::duration interop, both directions
  • No interop with other units libraries
No interop with other units libraries
Point Types

Support for "point-like" quantities, also known as "affine space types".

absolute wrapper for unit, but no point-specific conversion safety
  • Point arithmetic often produces incorrect results (see #240, closed as "won't fix")
  • Opt-in absolute/delta wrappers, for any unit: point + point is ill-formed, and a difference is offset-free
  • Plain affine units keep the old hazards: kelvin<int>(273) still converts silently to celsius<int>(0)
  • No origin-aware common unit; any integer arithmetic with offset scale silently promotes to double
None; would be hard to add, since units conflated with quantity type
  • Custom origins really easy to use and compose
  • Elegant `point` and `delta` modifiers on quantity spec
  • QuantityPoint is a first class type, with the same conversion safety surface as Quantity
  • Maximally efficient common units
Magnitudes

The features of the representation for different units' sizes. Key features include:

  • Irrational numbers (such as \(\pi\))
  • Powers (robust against overflow)
  • Roots (exact representations)
Close: lacks only irrationals, basis, and instance arithmetic. Ahead of its time! std::ratio plus a pi exponent: good angle handling, but overflows when compounding (cubed<pico<meters_>> fails to compile) `std::ratio` only, with no solution for pi Full support for Magnitudes Formerly, Au alone was best, but we shared Magnitudes with mp-units
Embedded Friendliness

Support common embedded use cases. Key examples include:

  • Flexibility in the Rep (usually a variety of integral types, and perhaps float, but rarely double).
  • The easy ability to exclude <iostream>.
Assumed to be good, based on mixed-Rep support Can trim by excluding <iostream>, but integer-Rep support is poor. Assumed to be good, based on mixed-Rep support (now safe for integer Rep) and droppable <iostream>
  • <iostream> not automatically included
  • Supports integral rep
  • Integral rep conversions unsafe
Assumed to be good, based on mixed-Rep support Best choice of all:
  • No "preferred" Rep.
  • sizeof()-friendly unit label representation
  • Safe integer operations.
Abbreviated construction

The ability to construct a Quantity using the symbol for its unit.

This is most commonly done with user-defined literals (UDLs), such as 3_m for "3 meters", but there are other alternatives.

User-defined literals (UDLs)
  • User-defined literals (UDLs)
  • "Unit symbol"-like syntax (5.0 * m), but m is just a quantity with value 1.0, and it force-routes through double
User-defined literals (UDLs) Unit symbols Unit symbols
Linear algebra

Good interoperability with matrix and vector libraries, such as Eigen

Historically, libraries could work with Eigen only if Eigen was patched: Quantity types break several of Eigen's deeply embedded assumptions.

Eigen interop released in 3.5.0: a "linalg-on-units" approach It inverts the usual nesting: the unit is the Eigen scalar (Eigen::Matrix<meters<double>, 3, 1>).
  • Dimension-preserving operations stay lazy
  • Dimension-changing ones (dot, cross, norm) use scalar loops that materialize, losing vectorization
  • "units-on-linalg" approach (most flexible)
  • Explicit Eigen, GLM, and Blaze support (on main, slated for 2.6.0)
  • Only library with Quantity Character, and with named vector components
  • No quantity-level dot or cross product: the result's quantity spec can't be named yet, so both stay on the raw rep
  • "units-on-linalg" approach (most flexible)
  • Explicit Eigen support
  • Only library that preserves full Eigen performance in all cases: unit-aware dot, cross, norm, and coefficient-wise ops, each as lazy as the Eigen member it wraps
Rep Variety

The range of different storage types ("Reps") permitted.

  • Poor: only 1 or 2 types
  • Fair: all built-in numeric types
  • Good: also support custom numeric types
Supports custom numeric types Effectively floating-point only (integer types unsafe)
  • All built-in numeric types, with integer Reps meeting std::chrono safety standards
  • Constrained to std::is_arithmetic: no custom numeric types
  • No "default" rep
  • Integer reps unsafe
Well defined Representation concept
  • Mature support for is_arithmetic Rep
  • Experimental support for custom Rep
  • No constraints yet (#52)
Zero

Quantity support for constructing from, and comparing with, 0: the only number which is meaningful for every unit. (Includes facilities for working with quantity signs.)

Guidance: use default constructor to construct, but no special facility for comparison Supports copysign(), but comparing a quantity to 0 is ill-formed No special construction or comparison
  • All six comparison operators accept a literal 0, enforced by a consteval guard (on main, for 2.6.0)
  • No construction from 0
Can use ZERO to construct or compare any quantity
Angles

First-class support for angular quantities, including degrees and radians.

Curiously imprecise pi value
  • Supports degrees and radians
  • pi represented as std::ratio
Simultaneous support for both strongly-typed and "pure SI" angles
Physical constants
  • How good is the core library support?
  • Does the library include built-in constants?
Includes built-in constants as quantities Built-in constants as quantities (2018 CODATA values) "Faster than lightspeed" constants
  • Constants as types
  • Perfect conversion policy
  • Implicit Quantity conversion
  • Includes exact constants from SI 2019
Non-linear scales (such as dB)

Support for logarithmic "units", such as decibels or nepers

  • Far more support than any library outside this family
  • Not sound: 10 dBW + 10 dBW compiles, and yields 20 m^4 kg^2 s^-6
  • The clear leader
  • Decibels now modelled as affine
  • Only power convention (10*log10); no root-power (amplitude) variant
Plan to support someday; see #41.
Quantity template parameters

The ability to use quantity values as template parameters.

  • Supports integral and floating point Reps
  • Supports quantity families via concepts
  • User must provide exact unit and rep
  • Supports all quantities
  • Supports automatic conversions
  • Supports quantity families via concepts
  • Supports integral rep
  • Only library with pre-C++20 support
  • User must provide exact unit and rep
  • No floating point support
Negative units

Correctly supporting units, constants, and magnitudes that are negative: so, larger stored values correspond to smaller quantities.

A negative conversion factor is accepted, but ordering then reflects the stored values rather than the quantities Unary minus on a magnitude, added on main for the CODATA constants, but no negative units
  • Negative constants
  • Negative units
  • Negative magnitudes
  • Comparison operators correctly account for sign
"Kind" Types

Any feature which supports robustly distinguishing between units that have the same dimension and magnitude.

For example, "hertz" and "becquerel" both have the same dimension and magnitude as "inverse seconds", but some libraries may prevent users from mixing them.

  • Opt-in string-tagged kinds
  • Built-in kinds not included (hertz vs. becquerel, torque vs. energy)
No plans at present to support.
Explicit Systems of Measurement

Support for different systems, each with their own (possibly incompatible) collection of dimensions.

Single, implicit global system Single, implicit global system. (Intentional design tradeoff: reduces learning curve, and makes compiler errors shorter.)
Abstract Units/Dimensions

  • Types that represent abstract units (clearly distinct from quantities of that unit).
  • Types that represent abstract dimensions.
  • The ability to do arithmetic with instances of these types.
  • Types exist, but conflated with quantity names
    • Units and quantities are separate types (meters_ vs. meters<double>)
    • Types for dimensions; arithmetic on both
    No separate types for units
    • Types for units
    • Types for dimensions
    • Can do arithmetic (compound units on the fly; abstract dimensional analysis)
    • Types for units
    • Types for dimensions
    • Can do arithmetic (compound units on the fly; abstract dimensional analysis)
    Macro Usage

    Avoidance of macros, especially in user-facing code.

    Common in user-facing APIs The UNIT_ADD family is the only way to define a named unit. (3.x can at least name a derived type macro-free, with decltype.) Very few, and confined to implementation helpers
    • Very few, mostly implementation helpers
    • Only one user-facing macro for C++20 backwards compatibility
    • No user-facing macros
    • Internal macros only where no viable alternative exists (CUDA/HIP support, C++ feature detection)

    1. Users may have expected a “traffic light” style, green/yellow/red color scheme. However, these traditional color schemes have poor accessibility for colorblind readers. The present color scheme was designed to be colorblind-friendly.