|
Unit Conversion and Dimensional Analysis Library 3.6.1
A compile-time, header-only C++23 dimensional-analysis library
|
Add your own named unit with a single UNIT_ADD line that yields a type, its literal, and its printable name.
The library ships 47 dimensions worth of units, but a project frequently needs one more: a domain-specific length, a custom rate, a unit named for a local convention. You add it declaratively with the UNIT_ADD family of macros, inside namespace units. You never hand-write a strong-type class; the macro generates the type, the user-defined literal, the name()/abbreviation() metadata used by diagnostics and stream output, and the trivially-copyable value semantics — from one line.
Related how-to guides: math functions, chrono interop, JSON serialization.
UNIT_ADD takes four arguments:
| Argument | Meaning | Example |
|---|---|---|
| dimension | the dimension namespace the unit joins | length |
| plural_name | the type name (always plural) | smoots |
| abbreviation | the literal suffix and printed symbol | smoot → _smoot, prints smoot |
| conversion_factor | how the unit relates to an existing one | conversion_factor<std::ratio<17018, 10000>, meters<>> |
The macro must be expanded inside namespace units so the generated type lands in the right place and the literal is registered in units::literals.
The smoot is a length equal to 67 inches (1.7018 m):
The single line gives you the units::length::smoots type, the 364.4_smoot literal, implicit conversion to and from every other length unit, and unit-aware arithmetic — all checked at compile time.
Note: plural_name is the type; abbreviation is both the literal suffix (_smoot) and the symbol printed by operator<< and name(). They may differ — feet uses abbreviation ft, so the literal is _ft.
The last argument states the unit's magnitude relative to a reference. Two forms cover almost every case.
Scale an existing unit by a std::ratio:
Anchor to a dimension's base unit directly (ratio of 1); this is how each dimension's SI unit is defined — for example the built-in meters_per_second is conversion_factor<std::ratio<1>, dimension::velocity>.
Caveat: a new unit needs both a distinct plural_name/abbreviation and a distinct conversion factor within its dimension. A duplicate name is a redefinition error; a factor that equals an existing unit's (for example std::ratio<6> of feet, which already names fathoms) collides in the type-to-name reverse map. Pick a factor no built-in unit already uses.
Caveat: std::ratio is an exact rational. Express an irrational or long-decimal factor as the closest ratio you need — the smoot's 17018/10000 is exact for 1.7018 m. There is no floating-point conversion-factor argument.
To get the full femto-through-peta family (and their literals) in one line, use UNIT_ADD_WITH_METRIC_PREFIXES. It expands to UNIT_ADD for the base unit plus one for each prefix, deriving each type name and literal from your plural_name and abbreviation:
The prefix names are prepended to plural_name (milliquaffs, kiloquaffs) and the SI prefix letters to abbreviation (mqf, kqf). This is how the built-in meters gets millimeters, kilometers, and the rest.
For data-style units that also need the binary prefixes (kibi through exbi), use UNIT_ADD_WITH_METRIC_AND_BINARY_PREFIXES.
A unit built from others — a rate, a product — is spelled with compound_conversion_factor, inverse, squared, and cubed. These accept the underscore-suffixed conversion-factor names (furlongs_, seconds_), which every UNIT_ADD also generates alongside the unit type:
squared<seconds_> and cubed<meters_> express powers; inverse<hours_> expresses a reciprocal. The built-in feet_per_second_squared is compound_conversion_factor<feet_, inverse<squared<seconds_>>>, and meters_per_second is compound_conversion_factor<meters_, inverse<seconds_>> — the same tools you use.
UNIT_ADD is for a unit you want to name and reuse — it registers a literal, a printable symbol, and a type-to-name mapping for diagnostics. When you only need the type of a derived quantity — a function's return type, a member, a local using — you do not need a macro at all. Every arithmetic combination of units already is a unit type; decltype on a representative expression names it:
accel_t is the same type the library would hand you from that expression — fully dimensioned, convertible to acceleration::meters_per_second_squared, and checked at compile time. This is the idiom to reach for when a result type is a mouthful and you would rather derive it than spell it, or when a generic function must return "whatever dimension `A / B` is":
Prefer decltype for a one-off, internal, or generic type; prefer UNIT_ADD when the unit deserves a name, a literal, and a symbol in diagnostics and output. The two compose freely — a decltype-named type is an ordinary unit you can convert to or from any UNIT_ADD-defined unit of the same dimension.
To add a dimension the library does not model, pair your UNIT_ADD with UNIT_ADD_DIMENSION_TRAIT(name), which generates the traits::is_<name>_unit / is_<name>_unit_v predicates for that dimension. The built-in headers end with one such line — for example UNIT_ADD_DIMENSION_TRAIT(length).
Sometimes the point of a new dimension is incompatibility: you want a quantity that the type system keeps separate from every physical unit, so a widgets can never be silently added to a meters or passed where a seconds is expected. You build one from a base-dimension tag — a small struct carrying the dimension's name and abbreviation — fed to make_dimension:
The payoff is the compile-time wall between the new dimension and everything else. make_dimension gives the tag a distinct dimensional signature, so mixing it with a physical unit is ill-formed:
Compose derived custom dimensions with the same dimension_multiply / dimension_divide tools the built-ins use — for example a "widgets per second" rate is dimension_divide<dimension::widget, dimension::time>. This is how you get a strong, unit-checked type for a domain quantity the SI system has no name for, with the library's full arithmetic and diagnostics behind it.
You never write a strong-type specialization by hand; that hand-written form was the source of a real defect (issue #357) and is fully retired. UNIT_ADD generates the named-type class, its deduction guides, and the argument-dependent-lookup machinery that lets diagnostics print the named type. For the details of how those named types are built, see named-type internals.