# units: dimensional analysis that the compiler checks in C++

> units is a header-only C++23 library that represents physical quantities as types, so incompatible units fail to compile and conversions cost nothing at run time. It suits engineering code where unit mix-ups are expensive, and it demands a modern toolchain and attention to compile time.

**nholthaus/units** — a compile-time, header-only, dimensional analysis and unit conversion library built on c++14 with no dependencies.

- Repository: https://github.com/nholthaus/units
- Website: http://nholthaus.github.io/units/
- Stars: 1,060 · Forks: 140
- Language: C++
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/nholthaus-units

## A quantity is a type, so a wrong unit is a compile error

The central idea of units is that a physical quantity is not a number with a comment next to it, it is a type. A quantity is a value with a unit, meters, feet or seconds, that behaves like the number it wraps. Conversions between compatible units are implicit and resolved at compile time, and expressions that are dimensionally inconsistent do not compile.

The example in the README makes the ergonomics concrete. A quantity is written with a unit literal such as 5.0_m, or by multiplying a value by a unit constant such as 60.0 * km. Assignment between compatible units is implicit and lossless, and printing with the stream operator gives the value with its unit.

```cpp
#include <units/length.h>
#include <iostream>

int main()
{
    using namespace units;
    using namespace units::literals;

    meters a = 5.0_m;        // unit literal
    meters b = 60.0 * km;    // value times a unit constant (== 60000 m)
    feet   c = a;            // implicit, lossless conversion

    std::cout << a << ", " << b << ", " << c << '\n';   // prints: 5 m, 60000 m, 16.4042 ft
}
```

The library states its design goal as favouring syntax that reads as ordinary code, so that the common cases are apparent without consulting the reference. That is a deliberate choice, since the alternative in this space is class-based named types with explicit conversions, which is more explicit and much noisier to read.

The cost of that choice is compile time, and the README says so plainly: the library is heavily templated, so a translation unit's compile time scales with how much it instantiates. Every snippet in the README and in the documentation is compiled and run as part of the test suite, with the sources under examples/.

## Over 280 units, no system to select

The unit catalogue is one of the more unusual features of the library. Over 280 units across 48 dimensions ship ready to use, covering SI, imperial and US customary, ancient and esoteric cases alike, with examples such as 3.0_cwt, 10.0_fur and 1.0_rem. Each is a named type with a literal and an exact, canonically sourced conversion ratio.

The structural point is that there is no unit system to select or instantiate. Every unit is first class and used directly, and units from different systems combine in a single expression, with the README giving 1.0_m + 3.0_ft + 1.0_fur as the illustration. That design choice removes an entire class of configuration: you do not declare that you are working in SI before you can write a length.

The exactness of the ratios is worth noting, because it is the difference between a dimensional analysis library and a units-of-measurement convenience. An exact ratio means the conversion is a rational number that the compiler can fold, not a floating point approximation baked in at run time. Combined with the constexpr nature of the conversions, a conversion between equivalent representations compiles to no machine code at all, and a quantity is a trivially copyable value the size of its underlying type.

So the run-time cost of the whole scheme is zero in the conversion case and one underlying type in the storage case. The bill arrives at compile time instead.

## Compile time is the real cost, and the README tells you how to cut it

The integration story is deliberately simple. The library is header-only, has no dependencies, and needs one include. You can drop the headers in or consume the CMake package, and the repository carries a CMakeLists.txt, a cmake/ directory, packaging/ and debian/ directories for distribution, and a 3rdParty/ directory.

But the note under the getting started section is the one to internalise. If compiles are slow, include less. The umbrella header <units.h> pulls in all 48 dimensions, and since the library is heavily templated, a translation unit's compile time scales with how much it instantiates. Including only the per-dimension headers you use keeps it down. The README frames that as a best practice rather than a requirement.

The rule it suggests is a good one, and slightly counter-intuitive: include the dimension a computed result lands in. Dividing a length by a time yields a velocity, so you want <units/velocity.h> in the translation unit even if you never name a velocity directly. A velocity used only as an intermediate type is exactly the case where the omission bites.

For a large codebase, that is a real migration cost rather than a one-line change. Every file that computes across dimensions has to be given the right headers, and the headers chosen for a file that later changes shape can be wrong. The examples directory includes chrono_interop.cpp, defining_a_unit.cpp, dimensional_analysis.cpp, dimensionless_percent.cpp, hello_units.cpp, json_roundtrip.cpp and pythagoras.cpp, which is a good indication of the range of things people do with it.

## Decibels, where a scale is part of the type

Most unit libraries treat decibels as a special case bolted on afterwards. units makes the scale part of the type instead. Alongside the default linear scale, decibel_scale provides dBW, dBm and the dimensionless dB, with scale-correct arithmetic, so adding decibels multiplies the underlying linear quantities rather than adding their decibel values. That is the behaviour an engineer expects from a power calculation and the behaviour most naive implementations get wrong.

The constraint comes with it: a decibel-scale unit requires a floating-point underlying type. So the type system is also telling you that a decibel quantity cannot be an integer, which is a useful constraint rather than an arbitrary one.

The diagnostics are worth a mention too, because the library is explicit about how it reports mistakes. A dimensional error names the unit type, meters<double>, rather than the internal conversion_factor<...> template. For a library built on template metaprogramming, that is the difference between an error message a developer can act on and a wall of template instantiation noise.

There is also a dimensionless_percent example in the repository, and dimensionless quantities are the interesting edge case in any dimensional analysis system, since a percentage is a ratio with no unit and still has to combine correctly with one.

## Optional Eigen support and self-describing serialization

Two features extend the library past simple arithmetic, and both are designed to avoid a hard dependency.

The first is linear algebra with Eigen. Dimensioned quantities can be stored in Eigen vectors and matrices with the dimensions checked at compile time, for example Eigen::Matrix<meters<double>, 3, 1>. The README describes this as optional and dependency-free, activating only if Eigen is present, which is the right way to offer an integration without forcing the dependency on everyone.

The second is serialization, and the design here is unusual. Calling serialize(q) writes a quantity to a compact binary stream that carries its dimension as well as its value. A reader recovers it with no prior agreement on the type, and the format extends to any base dimension, including your own make_dimension<>, with no central table and no reflection. The absence of a central table of known types is the important part: a stream written by a program that has never heard of your custom dimension can still be read by another program, because the dimension travels with the data.

That property is what makes the format usable across version boundaries and between services that were compiled at different times. The trade is that a reader cannot know what it has received until it decodes it, and the size of the type information is paid on every write.

## C++23 is the price of admission, and 2.x is the escape hatch

The requirements are strict and the README is unambiguous about them. The library requires a C++23 compiler, and it is continuously tested on GCC 13, Clang 19, MSVC 2022 on Windows Server 2022, and MSVC 2026 on Windows Server 2025, with build badges for the Windows, GCC and Clang workflows. Older toolchains are not supported by the 3.x line, and the last release for the C++14 era is the 2.x series, with a migration guide for the 2.x to 3.x move kept in the documentation.

There is a mismatch inside the repository itself that is worth flagging. The repository description calls it a library built on c++14, and the topic tags include cpp14, while the README and the current release require C++23. The description and the tags are stale, and the README is the authority. A team that trusts the topic tags will pick the wrong toolchain.

The release history shows the same acceleration. v3.6.1 on 2026-08-18, v3.6.0 on 2026-08-17 and v3.5.1 on 2026-08-15, with the last push on 2026-09-18. Three releases in three days is a burst rather than a steady cadence, and it suggests the 3.x line has been settling after a large rework.

The licence is MIT, and the repository also carries a CITATION.cff file, which tells you the project expects to be cited in academic and technical writing, plus a CHANGELOG.md, a clang-format and clang-tidy configuration, and a natvis/ directory, which is the Visual Studio debugger visualisers that make a quantity readable in a debugger window.

## Conclusion

Adopt units when a mistake in units would be expensive, since the dimensional check is done by the type system and a conversion between equivalent units compiles to nothing, which removes a whole class of defect at the cost of some template instantiation. Do not adopt it on a C++14 codebase or a compiler older than GCC 13, Clang 19 or MSVC 2022, because the 3.x line requires C++23 and the last release for the older era is the 2.x series. Before you start, include only the per-dimension headers you need, since the umbrella header pulls in all 48 dimensions and the library is heavily templated, and plan for the migration if you are coming from 2.x.

## FAQ

### What compiler does the units library need?

A C++23 compiler. The README lists continuous testing on GCC 13, Clang 19, MSVC 2022 and MSVC 2026, and states that older toolchains are not supported by the 3.x line. The last release for the C++14 era is the 2.x series, with a migration guide in the documentation.

### How do I use units in my code?

Include the umbrella header <units.h> or one header per dimension such as <units/length.h>, then bring in the operators with `using namespace units;` and `using namespace units::literals;`. Quantities are then written as literals like 5.0_m or as a value times a unit constant like 60.0 * km.

### Does units slow down compilation?

It can. The README notes the library is heavily templated, so compile time scales with how much a translation unit instantiates, and advises including only the per-dimension headers you need, including the dimension a computed result lands in, such as <units/velocity.h> when dividing a length by a time.

### Can units handle decibels correctly?

Yes. A unit's scale is part of its type, and decibel_scale provides dBW, dBm and the dimensionless dB with scale-correct arithmetic, so adding decibels multiplies the underlying linear quantities. A decibel-scale unit requires a floating-point underlying type.

## Sources

- [License: MIT](https://github.com/nholthaus/units/blob/main/LICENSE)
- [nholthaus/units on GitHub](https://github.com/nholthaus/units)
- [Project website](http://nholthaus.github.io/units/)
- [README](https://github.com/nholthaus/units/blob/main/README.md)
- [Releases](https://github.com/nholthaus/units/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nholthaus-units
