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.
- 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.82.0)
- One of the longest-standing C++ unit libraries, and the most prominent pre-C++14 option.
- nholthaus/units (version: 2.3.3)
- 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.
- bernedom/SI (version: 2.5.1)
- A newer, C++17-compatible offering with a large number of GitHub stars.
- mp-units (version: 2.0.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.
Boost | nholthaus | bernedom/SI | mp-units | Au | |
---|---|---|---|---|---|
C++ Version CompatibilityThe minimum C++ standard required to use the library. |
C++98 | C++14 | C++17 | C++20 | C++14 |
Ease of AcquisitionEase of including this library in projects using a wide variety of build environments |
Part of boost | Single, self-contained header | Available on conan | Available on conan and vcpkg |
Supports single-header delivery, with features:
|
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.
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.
-
Your program will take longer to compile, because the compiler is doing more work to produce essentially the same program.
-
You will get more compiler errors that developers will need to understand and fix.
These costs can bring significant benefits, but we still want them to be as small as possible.
Boost | nholthaus | bernedom/SI | mp-units | Au | |
---|---|---|---|---|---|
Compilation SpeedThe extra time the library adds to compiling a translation unit, compared to no units library.
|
Very slow, but can be greatly improved by removing I/O support and most units |
|
|||
Compiler Error ReadabilityThe ability to understand errors when the library catches a mistake it was designed to catch.
|
Infamously challenging | Positional dimensions | Alias for unit template |
|
|
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;
- and, what costs you’ll pay in developer experience if you do.
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!
Boost | nholthaus | bernedom/SI | mp-units | Au | |
---|---|---|---|---|---|
Conversion SafetyGuarding 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 | Integer Reps unsafe |
Policy consistent
with std::chrono library
|
Meets `std::chrono` baseline, plus:
|
|
Unit SafetyThe ability to judge the unit-correctness of every individual line of code by inspection, in isolation.
|
Only contains unit-safe interfaces | Only contains unit-safe interfaces | |||
Low FrictionHow easy it is to develop with the library. Criteria include:
|
|
|
|
|
|
ComposabilityThe ability to fluently combine the abstractions for units and prefixes to form new units on the fly. |
|
No | No |
|
|
Unit-aware I/OThe ability to print quantities along with information about their units. Examples:
|
|
|
|
|
|
Mixed-Rep SupportThe ease of freely mixing different storage types ("Reps") in the same program. |
Possible, but user-facing types use a global "preferred" Rep. | ||||
Unit-aware mathUnit-aware versions of common mathematical functions (`max`, `abs`, `sin`, `round`, and so on). |
|
|
No |
|
|
Generic DimensionsThe 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 | Generic templates, constrained with traits | Concepts excel here | Currently clunky. Could be better by adding concepts in extra C++20-only file, without compromising C++14 support. |
ExtensibilityHow easy it is to add new units, dimensions, or systems. |
Can add new units and dimensions |
|
Can add new units and dimensions | Can even handle, e.g., systems of "natural" units | Can add new units and dimensions |
Ease of MigrationSupport for two migration use cases:
|
No interop with other units libraries | No interop with other units libraries | No interop with other units libraries |
|
|
Point TypesSupport for "point-like" quantities, also known as "affine space types". |
absolute
wrapper for unit
|
Optional "offset" for units, but can't distinguish quantity from point. | None; would be hard to add, since units conflated with quantity type | Custom origins really easy to use and compose | |
MagnitudesThe features of the representation for different units' sizes. Key features include:
|
Close: lacks only irrationals, basis, and instance arithmetic. Ahead of its time! | Uses ratio plus "pi powers": good angle handling, but vulnerable to overflow | `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 FriendlinessSupport common embedded use cases. Key examples include:
|
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 |
Best choice of all:
|
Abbreviated constructionThe ability to construct a Quantity using the symbol for its unit.
This is most commonly done with user-defined literals (UDLs), such as
|
User-defined literals (UDLs) | User-defined literals (UDLs) | Unit symbols | Unit symbols | |
Linear algebraGood interoperability with matrix and vector libraries, such as Eigen Most libraries can work with Eigen, but only if Eigen is patched: Quantity types break several of Eigen's deeply embedded assumptions. |
Experimental support for Quantity Character; can use matrix types as Rep | Planned to add: #70 | |||
Rep VarietyThe range of different storage types ("Reps") permitted.
|
Supports custom numeric types | Effectively floating-point only (integer types unsafe) |
|
Well defined Representation concept |
|
Zero
Quantity support for constructing from, and comparing with, |
Guidance: use default constructor to construct, but no special facility for comparison | Supports copysign() , but no special construction or comparison |
No special construction or comparison |
zero() member |
Can use ZERO
to construct or compare any quantity
|
AnglesFirst-class support for angular quantities, including degrees and radians. |
Curiously imprecise pi value |
|
Simultaneous support for both strongly-typed and "pure SI" angles | ||
Physical constants
|
Includes built-in constants as quantities | Includes built-in constants as quantities | "Faster than lightspeed" constants |
|
|
Non-linear scales (such as dB)Support for logarithmic "units", such as decibels or nepers |
Plan to support someday; see #41. | ||||
Quantity template parametersThe ability to use quantity values as template parameters. |
|
|
|||
"Kind" TypesAny 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. |
No plans at present to support. | ||||
Explicit Systems of MeasurementSupport 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 exist, but conflated with quantity names | No separate types for units |
|
|
|
Macro UsageAvoidance of macros, especially in user-facing code. |
Common in user-facing APIs | Common in user-facing APIs | Very few, and confined to implementation helpers |
|
Zero user-facing macros; only two internal macros |
-
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. ↩