{"id":50312438,"url":"https://github.com/aurimasniekis/cpp-dimval","last_synced_at":"2026-05-28T22:01:37.142Z","repository":{"id":360656188,"uuid":"1232144426","full_name":"aurimasniekis/cpp-dimval","owner":"aurimasniekis","description":"A header-only C++23 library for dimensional values","archived":false,"fork":false,"pushed_at":"2026-05-27T09:09:33.000Z","size":125,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-27T11:13:47.775Z","etag":null,"topics":["cpp","cpp23","dimensional","parcel","units"],"latest_commit_sha":null,"homepage":"https://aurimasniekis.github.io/cpp-dimval/","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aurimasniekis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-07T16:23:49.000Z","updated_at":"2026-05-27T09:09:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/aurimasniekis/cpp-dimval","commit_stats":null,"previous_names":["aurimasniekis/cpp-dimval"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/aurimasniekis/cpp-dimval","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurimasniekis%2Fcpp-dimval","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurimasniekis%2Fcpp-dimval/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurimasniekis%2Fcpp-dimval/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurimasniekis%2Fcpp-dimval/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aurimasniekis","download_url":"https://codeload.github.com/aurimasniekis/cpp-dimval/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aurimasniekis%2Fcpp-dimval/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33627941,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cpp23","dimensional","parcel","units"],"created_at":"2026-05-28T22:01:32.881Z","updated_at":"2026-05-28T22:01:37.135Z","avatar_url":"https://github.com/aurimasniekis.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dimval\n\n[![CI](https://github.com/aurimasniekis/cpp-dimval/actions/workflows/ci.yml/badge.svg)](https://github.com/aurimasniekis/cpp-dimval/actions/workflows/ci.yml)\n[![Docs](https://github.com/aurimasniekis/cpp-dimval/actions/workflows/docs.yml/badge.svg)](https://aurimasniekis.github.io/cpp-dimval/)\n\nA header-only C++23 library for *dimensional values* — numbers paired with\nunits (`Meter`, `Hertz`, `Decibel`, …) and optional semantic measures\n(`Distance`, `CenterFrequency`, `Snr`, …) at the type level. Conversions,\nformatting, parsing, ranges, hashing, and optional JSON serialization come in\nthe box; mixing incompatible quantities is a compile error.\n\n## Why use this library?\n\n`dimval` is most useful when you have a lot of numeric quantities flowing\nthrough a system and you want the compiler to catch accidents like adding\nmeters to kilograms or decoding a kilogram into a `MeterValue` from JSON.\n\n- **Good for** scientific, RF/SDR, GNSS, mechanics, networking, and\n  configuration-heavy code where numbers carry physical or semantic meaning.\n- **Good for** formatting/parsing \"42.5 m\" round-trips, including JSON.\n- **Avoids** silent unit coercion: `MeterValue + KilogramValue` does not\n  compile.\n- **Useful when** you need a polymorphic handle (`IUnitValue*`) for\n  heterogeneous containers but still want compile-time arithmetic.\n- **Not ideal for** full dimensional algebra (e.g. `kg·m/s²` automatically\n  becoming `Newton`). Use [mp-units](https://github.com/mpusz/mp-units) for\n  that. `dimval` keeps each unit a flat tag.\n- **Not ideal for** hard real-time code that cannot tolerate a tiny vtable\n  per polymorphic value or `std::format` allocations during rendering.\n\n## Quick example\n\n```cpp\n#include \u003cdimval/dimval.hpp\u003e\n\n#include \u003ciostream\u003e\n\nint main() {\n    namespace dv = dimval;\n\n    dv::MeterValue   height = 1.78;          // implicit from numeric\n    dv::CelsiusValue room   = 21.5;\n\n    std::cout \u003c\u003c height \u003c\u003c \"\\n\";                       // 1.78 m\n    std::cout \u003c\u003c dv::convert\u003cdv::Foot\u003e(height) \u003c\u003c \"\\n\";   // ~5.84 ft\n    std::cout \u003c\u003c dv::convert\u003cdv::Kelvin\u003e(room) \u003c\u003c \"\\n\";   // 294.65 K\n\n    if (auto parsed = dv::MeterValue::parse(\"42.5 m\")) {\n        std::cout \u003c\u003c *parsed \u003c\u003c \"\\n\";                  // 42.5 m\n    } else {\n        std::cout \u003c\u003c \"parse failed: \" \u003c\u003c parsed.error().message \u003c\u003c \"\\n\";\n    }\n}\n```\n\nWhat this shows:\n\n- `MeterValue` is the alias for `UnitValue\u003cMeter\u003e`. The macro that defines a\n  unit also publishes `\u003cTag\u003eValue`, `\u003cTag\u003eValueShared`, `\u003cTag\u003eValueUnique`,\n  and `\u003cTag\u003eRangeValue`.\n- The implicit constructor from `double` only triggers between the numeric\n  type and a *specific* tagged type — `MeterValue x = 1.5` compiles, but\n  `MeterValue x = some_kg_value` does not.\n- `dv::convert\u003cTo\u003e(value)` is a free function that handles linear and\n  affine (`Celsius` ↔ `Kelvin`) conversions and is a `static_assert` error\n  if the kinds disagree.\n- Parsing returns `std::expected\u003cUnitValue, ParseError\u003e` — no exceptions.\n\n## Installation\n\n`dimval` is a header-only INTERFACE target with optional integrations. Pick\none of the supported integrations.\n\n### CMake — FetchContent\n\n```cmake\ninclude(FetchContent)\nFetchContent_Declare(dimval\n    URL      https://github.com/aurimasniekis/cpp-dimval/archive/refs/tags/v0.2.0.tar.gz\n    URL_HASH SHA256=7ec1fa93abefc0d56d8ffbffadaecc06f9e2705e7b6aee57befa9c87f73149c1\n)\nFetchContent_MakeAvailable(dimval)\n\nadd_executable(example main.cpp)\ntarget_link_libraries(example PRIVATE dimval::dimval)\n```\n\nThe `Dependencies.cmake` file always fetches `cpp-commons` (0.1.3) — the\nsource of the `comms::Icon` / `comms::Color` types used by every descriptor —\nand fetches `nlohmann/json` (3.12.0) and `cpp-parcel` (0.2.0) as needed, via\n`FetchContent_Declare(... FIND_PACKAGE_ARGS ...)`, so an already-installed copy\nis preferred over a new download.\n\n### CMake — `find_package` after `cmake --install`\n\n```cmake\nfind_package(dimval 0.2 REQUIRED)\ntarget_link_libraries(my_app PRIVATE dimval::dimval)\n```\n\nInstall rules are auto-disabled if any dependency was fetched (`commons`,\n`nlohmann_json`, `parcel`); install those via system packages or `find_package`\nto keep `DIMVAL_INSTALL=ON`. `commons` is always required, so it must be\ninstalled for the install rules to stay enabled.\n\n### Meson\n\n```meson\ndimval_dep = dependency('dimval', version: '\u003e=0.2.0',\n    fallback: ['dimval', 'dimval_dep'])\n```\n\nMeson options mirror the CMake ones: `-Djson=true|false`,\n`-Dparcel=true|false`, `-Dtests=true|false`, `-Dexamples=true|false`.\nA `pkg-config` file is generated on install.\n\n### Header-only drop-in\n\nCopy `include/dimval` into your include path. The core requires the C++23\nstandard library and `cpp-commons` (for `comms::Icon` / `comms::Color`); the\nJSON and parcel headers are guarded by `__has_include` checks and stay inert if\nthe dependency is missing. Note that `dimval/version.hpp` is generated from\n`version.hpp.in` by the build system — a pure copy-in must run the\n`configure_file`/`configure_file()` step (or hand-write the four\n`DIMVAL_VERSION_*` macros) before including `\u003cdimval/dimval.hpp\u003e`.\n\n## Requirements\n\n- **C++ standard**: C++23 — `\u003cformat\u003e`, `\u003cexpected\u003e`, ranges, and the\n  CRTP-style metadata layout all rely on C++23 library features. CMake\n  enforces this with `target_compile_features(dimval INTERFACE cxx_std_23)`.\n- **CMake** ≥ 3.25 (or **Meson** ≥ 1.3.0).\n- **Required dependency**:\n  - [`cpp-commons`](https://github.com/aurimasniekis/cpp-commons) ≥ 0.1.3 —\n    provides `comms::Icon` / `comms::Color`, used by every unit/measure\n    descriptor. Fetched unconditionally.\n- **Optional dependencies**:\n  - [`nlohmann/json`](https://github.com/nlohmann/json) ≥ 3.12 — enables\n    `\u003cdimval/json_nlohmann.hpp\u003e` (controlled by `DIMVAL_WITH_NLOHMANN_JSON`).\n  - [`cpp-parcel`](https://github.com/aurimasniekis/cpp-parcel) ≥ 0.2.0 —\n    enables `\u003cdimval/parcel.hpp\u003e` (controlled by `DIMVAL_WITH_PARCEL`;\n    auto-disabled if JSON is OFF, since parcel depends on it).\n\n## Core concepts\n\n### Tag types\n\nEvery unit and measure is its own struct with `static constexpr` metadata,\ndeclared via two macros. The struct derives from `UnitBase\u003cSelf\u003e` /\n`MeasureBase\u003cSelf, BaseUnit\u003e`, which synthesises the runtime descriptor.\n\n```cpp\nDIMVAL_DEFINE_UNIT(Frame,                  // Tag (struct name)\n                   \"frame\",                // id\n                   \"frm\",                  // symbol\n                   \"frm\",                  // short_name\n                   \"frame\",                // long_name\n                   \"frame_count\",          // kind (compatibility group)\n                   1.0,                    // factor\n                   ::comms::Icons::mdi::movie_roll,  // icon (comms::Icon catalog constant)\n                   ::comms::Colors::mui::blue[400])  // color (comms::Color, MUI shade)\n```\n\nThe `Icon` argument is a `comms::Icon`: use a catalog constant like\n`comms::Icons::mdi::movie_roll` (from `\u003ccommons/icons.hpp\u003e`) or, for a set with\nno catalog (`ph:`, `tabler:`, …), the validated `comms::Icon::from(\"set:name\")`.\nThe `Color` argument is a `comms::Color`, e.g. a Material UI shade\n`comms::Colors::mui::blue[400]` or any `comms::Color` you construct.\n\nThe macro defines `dimval::Frame`, the aliases `dimval::FrameValue`,\n`dimval::FrameValueShared`, `dimval::FrameValueUnique`,\n`dimval::FrameRangeValue`, and registers a `UnitDescriptor` at static-init\ntime. The struct lives in `namespace dimval` regardless of where the macro\nis invoked.\n\n### `UnitValue\u003cU, T = double\u003e`\n\nA `T` value tagged with a unit type at compile time. Operators are\nrestricted to same-tag arithmetic plus scalar `*` / `/`.\n\n```cpp\ndimval::MeterValue a = 1.5;            // implicit from numeric — preferred\ndimval::MeterValue b{1.5};             // brace-init also works\nauto                c = dimval::unit_value\u003cdimval::Meter\u003e(1.5);  // factory (rarely needed)\ndimval::UnitValue\u003cdimval::Meter\u003e d{1.5};                          // explicit long form\n\n// Heap-owned aliases generated by the macro:\ndimval::MeterValueShared s = dimval::MeterValue::of(2.0);\ndimval::MeterValueUnique u = dimval::MeterValue::unique(2.0);\n\na += b;          // 3.0 m\na *= 2.0;        // 6.0 m\nauto ratio = a / b;        // double, 4.0  — same-unit division strips the tag\nauto neg   = -a;           // -6.0 m\nbool lt    = a \u003c b;        // \u003c=\u003e ordering between same-unit values\n```\n\nThe `\u003cTag\u003eValue` aliases are the intended day-to-day form;\n`unit_value\u003c\u003e` / `UnitValue\u003c\u003e` are listed for completeness but rarely\nappear in user code.\n\n`UnitValue\u003cU, T\u003e` derives from `IUnitValue` (a vtable adds 8 bytes per\ninstance). The full per-instance state is a single `T v;` member plus the\nvtable pointer. Arithmetic is `constexpr`.\n\n### `MeasureValue\u003cM, T = double\u003e`\n\nA `MeasureValue` carries both a unit *and* a semantic refinement.\n`Distance`, `Length`, `Width`, `Height`, `Depth` all use `Meter`, but each\nis its own measure tag.\n\n```cpp\ndimval::DistanceValue d = 1500.0;                          // preferred alias form\nauto raw = d.as_unit_value();                              // -\u003e dimval::MeterValue\nauto d2  = dimval::from_unit_value\u003cdimval::Distance\u003e(      // wrap a MeterValue back\n    dimval::MeterValue{7.0});\n\n// Cross-measure arithmetic is a compile error even if both wrap Meter.\n// auto bad = d + dimval::LengthValue{1.0};                // ill-formed\n```\n\n### Ranges\n\n`UnitRangeValue\u003cU, T\u003e` and `MeasureRangeValue\u003cM, T\u003e` are closed/open\nintervals. Four named factories cover the common cases; `make` validates\nthe bounds and returns `std::expected\u003c…, RangeError\u003e`.\n\n```cpp\nusing mr = dimval::MeterRangeValue;  // = UnitRangeValue\u003cMeter\u003e\n\nauto closed     = mr::closed(0.0, 10.0);  // [0, 10] — bounds construct from numerics\nauto open       = mr::open(0.0, 10.0);    // (0, 10)\nauto left_open  = mr::left_open(0.0, 10.0);   // (0, 10]\nauto right_open = mr::right_open(0.0, 10.0);  // [0, 10)\n\nclosed.contains(dimval::MeterValue{5.0});   // true\nclosed.contains(dimval::MeterValue{10.0});  // true (inclusive)\nopen.contains(dimval::MeterValue{10.0});    // false (exclusive)\nclosed.contains(open);                       // bool — range-in-range\nclosed.overlaps(other);                      // bool\nauto inter = closed.intersect(other);        // std::optional\u003cmr\u003e\n\n// Validating untrusted bounds:\nauto r = mr::make(dimval::MeterValue{10.0}, dimval::MeterValue{0.0});\nif (!r) {\n    // r.error().code == RangeErrorCode::MaxLessThanMin\n}\n```\n\n### Registries\n\n`UnitRegistry::global()` and `MeasureRegistry::global()` are thread-safe\nsingletons. Built-in tags auto-register at static-init via the\n`DIMVAL_DEFINE_*` macros. You can also register descriptors at runtime —\nuseful when units come from configuration files.\n\n```cpp\nauto\u0026 reg = dimval::UnitRegistry::global();\nreg.register_unit({\n    .id        = \"furlong\",\n    .symbol    = \"fur\",\n    .short_name= \"fur\",\n    .long_name = \"furlong\",\n    .kind      = \"length\",\n    .factor    = 201.168,\n});\n\nif (auto* d = reg.find(\"fur\")) {\n    std::cout \u003c\u003c d-\u003elong_name \u003c\u003c \"\\n\";    // \"furlong\"\n}\nfor (const auto\u0026 d : reg.by_kind(\"length\")) {\n    std::cout \u003c\u003c d.id \u003c\u003c \"\\n\";\n}\nbool ok = reg.compatible(\"m\", \"h\");        // false — different kinds\n```\n\nLookups acquire a shared lock; registrations acquire a unique lock. The\ndescriptor's `std::string_view` fields are non-owning — for runtime\nregistration, the caller must keep the underlying strings alive.\n\n### Polymorphic handles\n\n`IUnitValue` and `IMeasureValue` are pure-virtual interfaces with a small\nfixed surface (`descriptor()`, `numeric_as_double()`, `to_string()`,\n`to_formatted_string()`, `clone()`). They let you mix tags in one\ncontainer.\n\n```cpp\nstd::vector\u003cdimval::IUnitValueUnique\u003e readings;\nreadings.push_back(dimval::MeterValue::unique(1.0));\nreadings.push_back(dimval::KilogramValue::unique(80.0));\nreadings.push_back(dimval::HertzValue::unique(2.4e9));\n\nfor (const auto\u0026 r : readings) {\n    std::cout \u003c\u003c r-\u003edescriptor().kind \u003c\u003c \": \" \u003c\u003c r-\u003eto_string() \u003c\u003c \"\\n\";\n}\n```\n\n## Common usage patterns\n\n### Conversion across units of the same kind\n\n```cpp\nnamespace dv = dimval;\n\ndv::KilometerPerHourValue km_h = 36.0;\nauto m_s = dv::convert\u003cdv::MeterPerSecond\u003e(km_h);   // -\u003e MeterPerSecondValue, 10.0\nauto kn  = dv::convert\u003cdv::Knot\u003e(m_s);              // -\u003e KnotValue, ~19.4\n\n// Affine: Celsius/Fahrenheit/Kelvin — handled correctly.\ndv::CelsiusValue c0 = 0.0;\nauto k0 = dv::convert\u003cdv::Kelvin\u003e(c0);              // -\u003e KelvinValue{273.15}\n\n// static_assert blocks unrelated kinds:\n// auto bad = dv::convert\u003cdv::Kilogram\u003e(km_h);      // ill-formed\n```\n\n`convert\u003cTo\u003e(From)` reduces `From` to a canonical value\n(`canonical = factor * v + offset`) and unfolds it back into `To`. It is\n`constexpr` and `noexcept`; an identity convert returns the value\nunchanged.\n\n### Formatting with `std::format` and streams\n\nThe format spec is `[style][.precision]` where `style` ∈\n`{default, short, full, json}`.\n\n| Spec        | `MeterValue{42.5}` output     |\n|-------------|-------------------------------|\n| `{}`        | `42.5 m`                      |\n| `{:short}`  | `42.5m`                       |\n| `{:full}`   | `42.5 meter`                  |\n| `{:json}`   | `{\"unit\":\"m\",\"value\":42.5}`   |\n| `{:.2}`     | `42.50 m`                     |\n| `{:full.3}` | `42.500 meter`                |\n\nNotes:\n\n- `MeasureValue` adds the measure name in `:full` (`\"42.5 Distance (meter)\"`)\n  and `:json` (`{\"measure\":\"distance\",\"unit\":\"m\",\"value\":42.5}`).\n- Ranges render with `[...]` / `(...)` brackets matching their inclusion,\n  e.g. `(0 m, 10 m]` for `left_open`. The `:json` style emits a `min`,\n  `max`, `min_inclusive`, `max_inclusive` payload.\n- `operator\u003c\u003c` for every value type and descriptor is in\n  `\u003cdimval/ostream.hpp\u003e` (already pulled in by the umbrella) and forwards\n  to `std::format(\"{}\", v)`.\n- A bogus spec (e.g. `\"{:full.x}\"`) throws `std::format_error`.\n\n### Parsing strings\n\n```cpp\nnamespace dv = dimval;\n\n// Compile-time-typed — the `Value::parse` member is the preferred form:\nauto a = dv::MeterValue::parse(\"42.5 m\");                  // std::expected\u003cMeterValue, ParseError\u003e\nauto b = dv::DistanceValue::parse(\"100 m\");                // measure\nauto c = dv::UnitValue\u003cdv::Meter, int\u003e::parse(\"42 m\");     // non-default numeric type\nauto d = dv::parse_unit_value\u003cdv::Meter\u003e(\"42.5 m\");        // free-function equivalent\n\n// Runtime-typed (descriptor lookup via the registry):\nauto e = dv::parse_dynamic_unit_value(\"125 dBm\");\n// e-\u003edesc-\u003ekind  == \"log_power\"\n// e-\u003evalue       == 125\n```\n\nThe grammar is `\u003cnumber\u003e\u003cwhitespace?\u003e\u003ctail\u003e`, where the number accepts\nsign, decimal point, and `e`/`E` exponents. Leading/trailing whitespace is\ntrimmed. The tail must equal one of the unit's `id`, `symbol`, or\n`short_name` (or be empty for a purely dimensionless value).\n\nFailure cases (each maps to a `ParseErrorCode`):\n\n```cpp\ndv::MeterValue::parse(\"   \");                  // Empty\ndv::MeterValue::parse(\"abc m\");                // InvalidNumber\ndv::MeterValue::parse(\"1.5 kg\");               // UnitMismatch\ndv::MeterValue::parse(\"42 m foo\");             // UnitMismatch (trailing garbage tail)\ndv::parse_dynamic_unit_value(\"3.14 zorgs\");    // UnknownUnit\ndv::parse_dynamic_unit_value(\"3.14\");          // dimensionless, success\ndv::UnitValue\u003cdv::Meter, int\u003e::parse(\"42.5 m\");  // InvalidNumber (int rejects '.')\n```\n\n### Hashing, ordering, and containers\n\n```cpp\nstd::unordered_set\u003cdimval::MeterValue\u003e seen;\nstd::unordered_map\u003cdimval::DistanceValue, std::string\u003e labels;\nstd::map\u003cdimval::MeterValue, int\u003e sorted;            // ordered by \u003c=\u003e\nstd::vector\u003cdimval::MeterValue\u003e v{3.0, 1.0, 2.0};\nstd::ranges::sort(v);\n```\n\n`std::hash` specializations are defined in `\u003cdimval/hash.hpp\u003e` (in the\numbrella). The hash mixes the tag's `id` with `std::hash\u003cT\u003e{}(value)`, so\ntwo `MeterValue{1.5}` produce the same hash, and a `MeterValue{1.5}` does\n*not* collide with a `KilogramValue{1.5}` of equal numeric value.\n\n### Math helpers (opt-in)\n\n`\u003cdimval/math.hpp\u003e` is **not** in the umbrella; pull it in explicitly. It\nadds tag-preserving `abs`, `min`, `max`, `clamp`, and `midpoint(range)`.\n\n```cpp\n#include \u003cdimval/math.hpp\u003e\n\nauto a = dimval::abs(dimval::MeterValue{-3.0});                       // 3 m\nauto m = dimval::midpoint(dimval::MeterRangeValue::closed(2.0, 8.0)); // 5 m\n```\n\nWithout these helpers the natural reach is `value.v`, which strips the\ntag — that's the escape hatch you usually want to avoid.\n\n### nlohmann/json integration\n\n```cpp\n#include \u003cdimval/dimval.hpp\u003e\n\n#include \u003cnlohmann/json.hpp\u003e\n\ndimval::DistanceValue distance = 7.0;\nnlohmann::json j = distance;\n// j == {\"m\":\"distance\",\"u\":\"m\",\"v\":7.0}\n\nauto v = j.get\u003cdimval::DistanceValue\u003e();\n```\n\nWire format (compact field names — different from the `{:json}` format\nspec output, which uses the long names `\"unit\"` / `\"value\"`):\n\n```text\nUnitValue           {\"u\":\"m\",\"v\":42.5}\nMeasureValue        {\"m\":\"distance\",\"u\":\"m\",\"v\":42.5}\nUnitRangeValue      {\"u\":\"m\",\"min\":0,\"max\":10,\"mi\":true,\"xi\":true}\nMeasureRangeValue   {\"m\":\"distance\",\"u\":\"m\",\"min\":0,\"max\":10,\"mi\":true,\"xi\":true}\n```\n\nField legend: `u` = unit id, `m` = measure id, `v` = numeric value,\n`mi` = min_inclusive, `xi` = max_inclusive (default `true`/`true`).\n\n`from_json` validates the `u` / `m` fields against the destination tag's\ndescriptor; a mismatch raises `nlohmann::json::other_error`. Range\nvalidation errors (e.g. inverted bounds) are also reported via\n`nlohmann::json::other_error`.\n\n### cpp-parcel integration\n\n`\u003cdimval/parcel.hpp\u003e` wraps each value type in a parcel `Cell` so it can\nship through a parcel registry. Wire format:\n\n```text\nUnitValueCell           {\"k\":\"uv\",  \"v\":{\"u\":\"m\",\"v\":42.5}}\nMeasureValueCell        {\"k\":\"mv\",  \"v\":{\"m\":\"distance\",\"u\":\"m\",\"v\":42.5}}\nUnitRangeValueCell      {\"k\":\"urv\", \"v\":{...}}\nMeasureRangeValueCell   {\"k\":\"mrv\", \"v\":{...}}\n```\n\n```cpp\ndimval::UnitValueCell\u003cdimval::Meter\u003e cell = dimval::MeterValue{42.5};\nparcel::json_t j = cell.to_json();      // {\"k\":\"uv\",\"v\":{\"u\":\"m\",\"v\":42.5}}\n\nparcel::ParcelRegistry reg;\nauto decoded = dimval::UnitValueCell\u003cdimval::Meter\u003e::from_json(j, reg);\n```\n\nCaveat (documented in the header): all `UnitValueCell\u003cU,T\u003e` instantiations\nshare `kind_id = \"uv\"`, so a single `parcel::ParcelRegistry` cannot route\nbetween multiple unit-tag variants — the last registered wins. Decoding at\nsites that already know the C++ type works because the inner JSON adapter\nvalidates the `u`/`m` field anyway.\n\n## Error handling\n\n| Mechanism                           | Used by                                                                                                                    |\n|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------|\n| `std::expected\u003cT, ParseError\u003e`      | `parse_unit_value`, `parse_measure_value`, `parse_dynamic_unit_value`, `UnitValue::parse`, `MeasureValue::parse`           |\n| `std::expected\u003cT, RangeError\u003e`      | `UnitRangeValue::make`, `MeasureRangeValue::make`                                                                          |\n| `std::optional\u003cT\u003e`                  | `UnitRangeValue::intersect`, `MeasureRangeValue::intersect`                                                                |\n| `static_assert`                     | `convert\u003cTo\u003e(From)` when `From` and `To` have different `kind`                                                             |\n| Compile error (overload resolution) | Same-tag `+`/`-` and `MeasureValue\u003cA\u003e + MeasureValue\u003cB\u003e` mixing                                                            |\n| Exceptions                          | `std::format_error` (bad spec); `nlohmann::json::other_error` (JSON tag mismatch / invalid range); parcel adapter rethrows |\n\n`ParseError` carries `code`, a copy of the input, the byte offset where\nparsing failed, and a human-readable message. `RangeError` carries the\ncode (`MaxLessThanMin`, `EmptyOpenRange`) and a message.\n\n```cpp\nif (auto v = dimval::MeterValue::parse(\"3.14 km\"); !v) {\n    // v.error().code    == ParseErrorCode::UnitMismatch\n    // v.error().input   == \"3.14 km\"\n    // v.error().pos     == 4   (byte offset where the unit starts)\n    // v.error().message == \"expected 'm', got 'km'\"\n}\n```\n\n## Edge cases and pitfalls\n\n- **Affine conversion direction**. `Celsius` and `Fahrenheit` carry an\n  `offset`; canonical storage is `Kelvin`. Round-trips like\n  `Celsius(0) → Kelvin(273.15) → Celsius(0)` work. Never reach for\n  `value.v` to \"do your own\" temperature conversion — you'll skip the\n  offset.\n- **`{:json}` format spec ≠ nlohmann adapter shape**. Format-string output\n  uses long field names (`\"unit\"`, `\"value\"`); the `nlohmann::json`\n  adapter uses short names (`\"u\"`, `\"v\"`). Pick one and stick with it on\n  the wire.\n- **Integer value type is strict**. `UnitValue\u003cMeter, int\u003e::parse(\"42.5 m\")`\n  fails with `InvalidNumber` — `from_chars` stops at the `.` and reports\n  trailing bytes; the parser does not silently truncate. `parse(\"42 m\")`\n  succeeds.\n- **Trailing tokens are an error**, not a warning.\n  `MeterValue::parse(\"42 m foo\")` returns `UnitMismatch` because the whole\n  tail (`m foo`) does not match `id`/`symbol`/`short_name`.\n- **Empty input vs empty tail**. `\"   \"` is `Empty`. `\"3.14\"` is valid for\n  `parse_dynamic_unit_value` (resolves to `dimensionless` via the\n  registry); the same input through `MeterValue::parse` is a\n  `UnitMismatch` because `Meter` requires a non-empty symbol.\n- **Range single-point with exclusive bound**. `make(5.0, 5.0,\n  open()/left_open()/right_open())` returns `EmptyOpenRange`.\n  `make(5.0, 5.0)` (closed default) is allowed and contains exactly `5.0`.\n- **Range factories `closed`/`open`/etc. are unchecked**. They construct\n  even with inverted bounds. Use `make(...)` when bounds come from\n  untrusted sources.\n- **Same-unit division returns a scalar**, *not* a `UnitValue\u003c...,double\u003e`.\n  `MeterValue{10} / MeterValue{2}` is `double{5.0}`. Multiplication by a\n  scalar keeps the tag.\n- **No compound dimensional algebra**. `Meter / Second` does not become\n  `meters per second`. The catalog includes `MeterPerSecond` as its own\n  unit (kind `\"velocity\"`); model new compound units the same way.\n- **Polymorphic copies need `clone()`**. `IUnitValue` is non-copyable\n  through the interface. Use `v-\u003eclone()` (returns a fresh\n  `unique_ptr\u003cIUnitValue\u003e`).\n- **`std::string_view` fields in descriptors don't own**. Built-in\n  descriptors point to string literals (safe forever). For runtime\n  `register_unit({...})`, keep the strings alive at least as long as the\n  descriptor is reachable.\n- **`\u003cdimval/math.hpp\u003e` is opt-in**. The umbrella does not include it.\n  Without that header, `dimval::abs(value)` is unresolved.\n- **Format precision applies to numbers, not symbols**. `{:.2}` formats\n  `value` with two decimals; the symbol/long_name is unchanged.\n- **Stable lifetime of the registry**. The global registries are Meyers\n  singletons — fine for static-init use because every macro call only\n  touches its own descriptor, but do not store references that outlive\n  `main()`.\n- **Parcel kind ids collide across tag instantiations**. See the parcel\n  section above; this is a documented limitation rather than a bug.\n\n## API overview\n\n| API                                                                               | Purpose                                                       | Notes                                                                                                                      |\n|-----------------------------------------------------------------------------------|---------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|\n| `UnitValue\u003cU, T\u003e`                                                                 | Tagged numeric value                                          | Implicit ctor from `T`; `+`,`-`,scalar `*`/`/`,same-tag `/` returns `T`                                                    |\n| `MeasureValue\u003cM, T\u003e`                                                              | Unit value + measure tag                                      | `as_unit_value()`, `from_unit_value\u003cM\u003e()` bridge to/from `UnitValue`                                                       |\n| `UnitRangeValue\u003cU, T\u003e` / `MeasureRangeValue\u003cM, T\u003e`                                | Closed/open intervals                                         | Factories `closed/open/left_open/right_open` (unchecked); `make` returns `expected`                                        |\n| `unit_value\u003cU\u003e(T)` / `measure_value\u003cM\u003e(T)`                                        | Free factories                                                | `noexcept`, `constexpr`                                                                                                    |\n| `convert\u003cTo\u003e(value)`                                                              | Same-kind unit conversion                                     | `static_assert` blocks unrelated kinds; affine-aware                                                                       |\n| `parse_unit_value\u003cU,T\u003e` / `parse_measure_value\u003cM,T\u003e` / `parse_dynamic_unit_value` | String parsing                                                | `std::expected`; integer `T` rejects fractional input                                                                      |\n| `IUnitValue` / `IMeasureValue`                                                    | Polymorphic handle                                            | `descriptor()`, `numeric_as_double()`, `to_string()`, `clone()`                                                            |\n| `UnitDescriptor` / `MeasureDescriptor`                                            | Runtime metadata                                              | Aggregate; `string_view` fields non-owning                                                                                 |\n| `UnitRegistry::global()` / `MeasureRegistry::global()`                            | Thread-safe descriptor lookup                                 | `find`, `by_kind`, `list`, `compatible`, `register_unit`, `register_unit\u003cTag\u003e()`                                           |\n| `DIMVAL_DEFINE_UNIT(Tag, ...)`                                                    | Define and auto-register a unit tag                           | Generates aliases `\u003cTag\u003eValue`, `\u003cTag\u003eValueShared/Unique`, `\u003cTag\u003eRangeValue`                                               |\n| `DIMVAL_DEFINE_MEASURE(Tag, BaseUnit, ...)`                                       | Define and auto-register a measure tag                        | Same alias pattern; `BaseUnit::id` becomes `base_unit_id`                                                                  |\n| `\u003cdimval/math.hpp\u003e`                                                               | `abs`, `min`, `max`, `clamp`, `midpoint` (tag-preserving)     | Not in umbrella; include explicitly                                                                                        |\n| `\u003cdimval/ostream.hpp\u003e`                                                            | `operator\u003c\u003c` for every value type and descriptor              | In umbrella; delegates to `std::format`                                                                                    |\n| `\u003cdimval/json_nlohmann.hpp\u003e`                                                      | nlohmann/json `to_json` / `from_json`                         | Auto-enabled if `nlohmann/json.hpp` is on the include path                                                                 |\n| `\u003cdimval/parcel.hpp\u003e`                                                             | cpp-parcel `Cell` wrappers                                    | Auto-enabled if `parcel/parcel.h` is on the include path; depends on JSON                                                  |\n| `dimval::version` / `version_major/minor/patch`                                   | Library version (string_view + ints) in `\u003cdimval/dimval.hpp\u003e` | Backed by `DIMVAL_VERSION_*` macros in the generated `\u003cdimval/version.hpp\u003e`                                                |\n\n### Built-in catalog\n\nThe built-ins ship **103 units across 57 kinds** and **115 measures**.\nRather than freezing a list here that drifts out of date with every new\nrelease, run `examples/list_catalog.cpp` to print the current registry\ncontents:\n\n```bash\nmake examples            # builds and runs every example\n./build/examples/dimval_list_catalog\n```\n\nOr, in your own code:\n\n```cpp\n#include \u003cdimval/dimval.hpp\u003e\n\n#include \u003ciostream\u003e\n\nint main() {\n    for (const auto\u0026 u : dimval::UnitRegistry::global().list()) {\n        std::cout \u003c\u003c u.kind \u003c\u003c '\\t' \u003c\u003c u.id \u003c\u003c '\\t' \u003c\u003c u.symbol \u003c\u003c '\\n';\n    }\n    for (const auto\u0026 m : dimval::MeasureRegistry::global().list()) {\n        std::cout \u003c\u003c m.id \u003c\u003c '\\t' \u003c\u003c m.base_unit_id \u003c\u003c '\\t' \u003c\u003c m.name \u003c\u003c '\\n';\n    }\n}\n```\n\nA snapshot of what the registry currently contains, listed by C++ tag\n(use `\u003cTag\u003eValue`, `\u003cTag\u003eRangeValue`, `convert\u003cTag\u003e(...)`, etc.):\n\n**Units, grouped by `kind`**\n\n| `kind`                        | Tags                                                                                              |\n|-------------------------------|---------------------------------------------------------------------------------------------------|\n| `length`                      | `Meter`, `Foot`, `Inch`, `Mile`, `NauticalMile`, `Wavelength`                                     |\n| `mass`                        | `Kilogram`, `Pound`, `Tonne`                                                                      |\n| `time`                        | `Second`, `Minute`, `Hour`, `Day`                                                                 |\n| `temperature`                 | `Kelvin`, `Celsius`, `Fahrenheit`                                                                 |\n| `current`                     | `Ampere`, `CoulombPerSecond`                                                                      |\n| `amount_of_substance`         | `Mole`                                                                                            |\n| `luminous_intensity`          | `Candela`                                                                                         |\n| `frequency`                   | `Hertz`                                                                                           |\n| `angle`                       | `Radian`, `Degree`, `Arcminute`, `Arcsecond`, `PhaseDegree`, `PhaseRadian`                        |\n| `solid_angle`                 | `Steradian`                                                                                       |\n| `force`                       | `Newton`                                                                                          |\n| `pressure`                    | `Pascal`, `Bar`, `Atmosphere`, `PoundsPerSquareInch`                                              |\n| `energy`                      | `Joule`, `WattHour`, `KilowattHour`                                                               |\n| `power`                       | `Watt`                                                                                            |\n| `apparent_power`              | `VoltAmpere`                                                                                      |\n| `reactive_power`              | `Var`                                                                                             |\n| `voltage`                     | `Volt`, `JoulePerCoulomb`                                                                         |\n| `charge`                      | `Coulomb`, `AmpereHour`                                                                           |\n| `resistance`                  | `Ohm`, `VoltPerAmpere`                                                                            |\n| `conductance`                 | `Siemens`, `AmperePerVolt`                                                                        |\n| `capacitance`                 | `Farad`, `CoulombPerVolt`                                                                         |\n| `inductance`                  | `Henry`, `WeberPerAmpere`                                                                         |\n| `magnetic_flux`               | `Weber`, `VoltSecond`                                                                             |\n| `magnetic_flux_density`       | `Tesla`, `Gauss`                                                                                  |\n| `electric_field_strength`     | `VoltPerMeter`                                                                                    |\n| `magnetic_field_strength`     | `AmperePerMeter`                                                                                  |\n| `permittivity`                | `FaradPerMeter`                                                                                   |\n| `permeability`                | `HenryPerMeter`                                                                                   |\n| `resistivity`                 | `OhmMeter`                                                                                        |\n| `conductivity`                | `SiemensPerMeter`                                                                                 |\n| `sheet_resistance`            | `OhmSquare`                                                                                       |\n| `ionizing_radiation_exposure` | `CoulombPerKilogram`                                                                              |\n| `luminous_flux`               | `Lumen`                                                                                           |\n| `illuminance`                 | `Lux`                                                                                             |\n| `radioactivity`               | `Becquerel`                                                                                       |\n| `absorbed_dose`               | `Gray`                                                                                            |\n| `dose_equivalent`             | `Sievert`                                                                                         |\n| `catalytic_activity`          | `Katal`                                                                                           |\n| `volume`                      | `Litre`, `CubicMeter`, `Gallon`                                                                   |\n| `area`                        | `SquareMeter`, `Hectare`                                                                          |\n| `density`                     | `KilogramPerCubicMeter`, `GramPerCubicCentimeter`                                                 |\n| `velocity`                    | `MeterPerSecond`, `KilometerPerHour`, `Knot`                                                      |\n| `acceleration`                | `MeterPerSecondSquared`, `StandardGravity`                                                        |\n| `rotational_frequency`        | `RotationPerMinute`                                                                               |\n| `clock_drift`                 | `SecondsPerDay`                                                                                   |\n| `data_size`                   | `Bit`, `Byte`                                                                                     |\n| `data_rate`                   | `BitPerSecond`, `BytePerSecond`                                                                   |\n| `count`                       | `Count`                                                                                           |\n| `dimensionless`               | `Dimensionless`                                                                                   |\n| `ratio`                       | `Ratio`, `Percent`, `PartsPerMillion`, `PartsPerBillion`                                          |\n| `log_ratio`                   | `Decibel`, `Neper`                                                                                |\n| `log_power`                   | `DecibelMilliwatt`, `DecibelWatt`                                                                 |\n| `log_voltage`                 | `DecibelMicrovolt`, `DecibelMillivolt`                                                            |\n| `noise_density_db`            | `DecibelPerHertz`                                                                                 |\n| `voltage_noise_density`       | `VoltPerRootHertz`                                                                                |\n| `current_noise_density`       | `AmperePerRootHertz`                                                                              |\n| `power_spectral_density`      | `WattPerHertz`                                                                                    |\n\n**Measures, grouped by base unit tag**\n\n| Base unit Tag           | Measure tags                                                                                                                                                 |\n|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `Meter`                 | `Length`, `Width`, `Height`, `Depth`, `Distance`, `Altitude`, `GeoidSeparation`, `PositionAccuracy`, `HorizontalAccuracy`, `VerticalAccuracy`, `Pseudorange` |\n| `Kilogram`              | `Mass`, `Weight`                                                                                                                                             |\n| `Second`                | `Duration`, `ElapsedTime`, `Time`, `Latency`, `Jitter`, `OffsetTime`, `TimeAccuracy`, `PropagationDelay`                                                     |\n| `Byte`                  | `Size`, `Capacity`                                                                                                                                           |\n| `BytePerSecond`         | `Throughput`, `ByteRate`                                                                                                                                     |\n| `BitPerSecond`          | `BitRate`                                                                                                                                                    |\n| `Hertz`                 | `Frequency`, `CenterFrequency`, `SampleRate`, `SymbolRate`, `Bandwidth`, `IntermediateFrequency`, `FrameRate`, `FrequencyErrorHz`                            |\n| `Celsius`               | `Temperature`                                                                                                                                                |\n| `Kelvin`                | `TemperatureKelvin`                                                                                                                                          |\n| `Ratio`                 | `Percentage`, `RatioMeasure`                                                                                                                                 |\n| `Count`                 | `CountMeasure`                                                                                                                                               |\n| `Radian`                | `Angle`                                                                                                                                                      |\n| `Degree`                | `Latitude`, `Longitude`, `Heading`, `Azimuth`, `Elevation`, `CourseOverGround`, `PolarizationAngle`                                                          |\n| `MeterPerSecond`        | `Speed`, `GroundSpeed`, `VerticalSpeed`                                                                                                                      |\n| `MeterPerSecondSquared` | `Acceleration`                                                                                                                                               |\n| `SquareMeter`           | `Area`                                                                                                                                                       |\n| `KilogramPerCubicMeter` | `Density`                                                                                                                                                    |\n| `Litre`                 | `Volume`                                                                                                                                                     |\n| `Pascal`                | `Pressure`                                                                                                                                                   |\n| `Newton`                | `Force`                                                                                                                                                      |\n| `Volt`                  | `Voltage`                                                                                                                                                    |\n| `Ampere`                | `Current`                                                                                                                                                    |\n| `Ohm`                   | `Resistance`                                                                                                                                                 |\n| `Siemens`               | `Conductance`                                                                                                                                                |\n| `Farad`                 | `Capacitance`                                                                                                                                                |\n| `Henry`                 | `Inductance`                                                                                                                                                 |\n| `Watt`                  | `Power`, `RfPower`                                                                                                                                           |\n| `Joule`                 | `Energy`                                                                                                                                                     |\n| `Coulomb`               | `Charge`                                                                                                                                                     |\n| `VoltPerMeter`          | `ElectricFieldStrength`                                                                                                                                      |\n| `AmperePerMeter`        | `MagneticFieldStrength`                                                                                                                                      |\n| `Weber`                 | `MagneticFlux`                                                                                                                                               |\n| `Tesla`                 | `MagneticFluxDensity`                                                                                                                                        |\n| `OhmMeter`              | `Resistivity`                                                                                                                                                |\n| `SiemensPerMeter`       | `Conductivity`                                                                                                                                               |\n| `FaradPerMeter`         | `Permittivity`                                                                                                                                               |\n| `HenryPerMeter`         | `Permeability`                                                                                                                                               |\n| `OhmSquare`             | `SheetResistance`                                                                                                                                            |\n| `RotationPerMinute`     | `RotationRate`                                                                                                                                               |\n| `SecondsPerDay`         | `ClockDrift`                                                                                                                                                 |\n| `Decibel`               | `Gain`, `Loss`, `PathLoss`, `ReturnLoss`, `AntennaGain`, `Snr`, `Cnr`, `CarrierToNoiseDensity`, `EbNo`, `Mer`, `EvmDb`, `IqImbalanceGain`                    |\n| `DecibelMilliwatt`      | `RfPowerDbm`                                                                                                                                                 |\n| `DecibelWatt`           | `RfPowerDbw`                                                                                                                                                 |\n| `DecibelMicrovolt`      | `SignalLevelDbuv`                                                                                                                                            |\n| `DecibelMillivolt`      | `SignalLevelDbmv`                                                                                                                                            |\n| `Percent`               | `EvmPercent`, `Vswr`, `Ber`, `Fer`, `Per`, `PacketErrorRate`, `DilutionOfPrecision`, `Hdop`, `Vdop`, `Pdop`, `Tdop`                                          |\n| `PartsPerMillion`       | `FrequencyErrorPpm`                                                                                                                                          |\n| `PartsPerBillion`       | `FrequencyStabilityPpb`                                                                                                                                      |\n| `PhaseDegree`           | `Phase`, `IqImbalancePhase`                                                                                                                                  |\n| `PhaseRadian`           | `PhaseRadians`, `CarrierPhase`                                                                                                                               |\n| `VoltPerRootHertz`      | `NoiseDensityVoltage`                                                                                                                                        |\n| `AmperePerRootHertz`    | `NoiseDensityCurrent`                                                                                                                                        |\n| `DecibelPerHertz`       | `NoiseDensityDb`                                                                                                                                             |\n| `WattPerHertz`          | `PowerSpectralDensity`                                                                                                                                       |\n| `Wavelength`            | `WavelengthMeasure`                                                                                                                                          |\n\nIf you need binary-prefix data sizes (`KiB`, `MiB`, …) or any unit not\nin the list, define your own with `DIMVAL_DEFINE_UNIT` — that's the\nsupported extension path.\n\n## Examples\n\nThe `examples/` directory contains short, standalone programs. They are\nall built by `make examples` (or `cmake --build build`).\n\n| Example                           | Demonstrates                                                            |\n|-----------------------------------|-------------------------------------------------------------------------|\n| `examples/basic_unit.cpp`         | Stack/heap construction, `convert\u003c\u003e`, alt-unit and affine conversions   |\n| `examples/basic_measure.cpp`      | `MeasureValue`, `as_unit_value`, polymorphic `IMeasureValue` container  |\n| `examples/range.cpp`              | Range factories, `contains`, `overlaps`, `intersect`, error from `make` |\n| `examples/formatting.cpp`         | Every `std::format` style/precision combination                         |\n| `examples/sorting_hashing.cpp`    | `std::hash` + `\u003c=\u003e` in `unordered_set` and sorted `vector`              |\n| `examples/error_handling.cpp`     | The four parse failure shapes plus range validation                     |\n| `examples/validation.cpp`         | Member-form `Value::parse`, dynamic registry parse, range guard         |\n| `examples/mechanics.cpp`          | Mechanics measures and imperial/metric round-trips                      |\n| `examples/custom_unit.cpp`        | `DIMVAL_DEFINE_UNIT` + runtime `register_unit`                          |\n| `examples/custom_measure.cpp`     | `DIMVAL_DEFINE_MEASURE` reusing a built-in unit (`Byte`)                |\n| `examples/registry_inquiry.cpp`   | Walking the registry, `find`, `by_kind`, `compatible`                   |\n| `examples/list_catalog.cpp`       | Dumps every built-in unit (grouped by kind) and every measure           |\n| `examples/ostream.cpp`            | `operator\u003c\u003c` on values, ranges, and descriptors                         |\n| `examples/json_integration.cpp`   | `to_json`/`from_json` for unit, measure, and range values               |\n| `examples/parcel_integration.cpp` | `*ValueCell` wire format and round-trip                                 |\n\n## Building from source\n\nCMake (the canonical path):\n\n```bash\ncmake -S . -B build\ncmake --build build -j\nctest --test-dir build --output-on-failure\n```\n\nMeson:\n\n```bash\nmeson setup build-meson -Dtests=true -Dexamples=true\nmeson compile -C build-meson\nmeson test -C build-meson\n```\n\nThe `Makefile` is a thin wrapper around the CMake invocations the project\nuses in CI:\n\n| Target                              | What it does                                                                      |\n|-------------------------------------|-----------------------------------------------------------------------------------|\n| `make build`                        | Configure + build (`build/`, Debug)                                               |\n| `make test`                         | `ctest` in `build/`                                                               |\n| `make examples`                     | Build and run every `dimval_*` example, fail on any non-zero exit                 |\n| `make sanitize`                     | Configure + build + test in `build-san/` with ASan + UBSan                        |\n| `make tidy`                         | Configure + build in `build-tidy/` with `clang-tidy`                              |\n| `make release`                      | Release build + tests in `build-release/`                                         |\n| `make coverage`                     | Clang source-based coverage; HTML in `build-coverage/coverage-html/`              |\n| `make no-json`                      | Build + test with `DIMVAL_WITH_NLOHMANN_JSON=OFF` (parcel auto-disabled)          |\n| `make docs`                         | Doxygen output in `build-docs/`                                                   |\n| `make format` / `make format-check` | clang-format the project sources                                                  |\n| `make ci`                           | The full pre-push gate: format-check + tidy + test + sanitize + release + no-json |\n\n### Build options\n\n| CMake option                | Default   | Effect                                                                  |\n|-----------------------------|-----------|-------------------------------------------------------------------------|\n| `DIMVAL_BUILD_TESTS`        | top-level | Build the GoogleTest suite (auto-fetches GTest 1.17 if not found)       |\n| `DIMVAL_BUILD_EXAMPLES`     | top-level | Build every example target                                              |\n| `DIMVAL_BUILD_DOCS`         | OFF       | Configure the Doxygen target                                            |\n| `DIMVAL_WITH_NLOHMANN_JSON` | ON        | Link nlohmann/json and define `DIMVAL_WITH_NLOHMANN_JSON=1`             |\n| `DIMVAL_WITH_PARCEL`        | ON        | Link cpp-parcel; auto-disabled if JSON is OFF                           |\n| `DIMVAL_ENABLE_SANITIZERS`  | OFF       | ASan + UBSan flags (Debug)                                              |\n| `DIMVAL_ENABLE_CLANG_TIDY`  | OFF       | Run clang-tidy during the build (uses the `.clang-tidy` in the repo)    |\n| `DIMVAL_ENABLE_COVERAGE`    | OFF       | Clang source-based coverage flags                                       |\n| `DIMVAL_WARNINGS_AS_ERRORS` | top-level | Promote compiler warnings to errors                                     |\n| `DIMVAL_INSTALL`            | top-level | Generate install rules; auto-disabled if a fetched dependency is in use |\n\n`top-level` means \"ON when `dimval` is the top project, OFF when it's a\nsubproject\" (`PROJECT_IS_TOP_LEVEL`).\n\n## Performance notes\n\nThe library carries no runtime overhead beyond:\n\n- 8 bytes per polymorphic value (the vtable pointer; arithmetic is still\n  `constexpr`).\n- A `std::shared_mutex` on each registry, shared-locked for lookups.\n- `std::format` allocations during `to_string()` / `operator\u003c\u003c`.\n\nStorage is \"one canonical unit per kind\"; SI-prefix variants (`km`, `MHz`,\n`ms`, …) are the formatter's responsibility, not extra stored types — so\nyou don't pay for unused conversions you'll never call. There are no\nbenchmarks in the repository; the claims above are about design, not\nmeasurement.\n\n## FAQ\n\n**Do I need to link a library, or is it header-only?**\n\nIt's header-only. The CMake target is `INTERFACE`. Linking\n`dimval::dimval` only adds include paths (and propagates the\n`DIMVAL_WITH_NLOHMANN_JSON=1` / `DIMVAL_WITH_PARCEL=1` definitions when\nthose integrations are on).\n\n**What happens if input is invalid?**\n\nParse and range constructors return `std::expected`; the value side is\nfree of validation exceptions. `convert\u003c\u003e` mismatches are\n`static_assert`-compile errors. Mixing tags is an overload-resolution\ncompile error. `nlohmann::json` mismatches throw\n`nlohmann::json::other_error` because that's the JSON adapter's own\ncontract.\n\n**Can I use this in multiple threads?**\n\nReading and writing the same `UnitValue` from multiple threads is a data\nrace like any other plain struct. The *registries* are thread-safe — the\ntest suite stresses this with concurrent `find` calls.\n\n**Does this own the data or borrow it?**\n\n`UnitValue\u003cU, T\u003e` owns its `T v;` by value. Heap helpers `of()` /\n`unique()` give you `shared_ptr` / `unique_ptr` ownership. Descriptor\nfields are `string_view` and *do not* own the underlying strings — you\nmust keep them alive for as long as the descriptor is reachable.\n\n**Which compiler versions work?**\n\nThe repo doesn't pin compilers explicitly; any C++23-complete toolchain\n(`\u003cformat\u003e`, `\u003cexpected\u003e`, ranges, deducing `this` is not used) should\nbuild. Run `make ci` to verify locally.\n\n**How do I disable JSON / parcel?**\n\nConfigure with `-DDIMVAL_WITH_NLOHMANN_JSON=OFF` and/or\n`-DDIMVAL_WITH_PARCEL=OFF`. Setting JSON off auto-disables parcel.\nEquivalent Meson options are `-Djson=false -Dparcel=false`.\n\n**How do I debug build errors?**\n\nCompile errors when adding mismatched tags read as overload-resolution\nfailures (`no operator+ matches…`). Compile errors from `convert\u003c\u003e` come\nthrough the `static_assert` message. Parse and range errors are runtime\nand surface through `std::expected`. Format errors throw\n`std::format_error` and include the offending spec.\n\n## Contributing\n\nContributions to the library are welcome! If you encounter any issues or have suggestions for\nimprovements,\nplease feel free to submit a pull request or open an issue on the project's repository.\n\n## License\n\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faurimasniekis%2Fcpp-dimval","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faurimasniekis%2Fcpp-dimval","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faurimasniekis%2Fcpp-dimval/lists"}