Unit Conversion and Dimensional Analysis Library 3.6.1
A compile-time, header-only C++23 dimensional-analysis library
Loading...
Searching...
No Matches
serialization.h
Go to the documentation of this file.
1//--------------------------------------------------------------------------------------------------
2//
3// UnitConversion: A compile-time c++23 unit conversion library with no dependencies
4//
5//--------------------------------------------------------------------------------------------------
6//
7// The MIT License (MIT)
8//
9// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
10// and associated documentation files (the "Software"), to deal in the Software without
11// restriction, including without limitation the rights to use, copy, modify, merge, publish,
12// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
13// Software is furnished to do so, subject to the following conditions:
14//
15// The above copyright notice and this permission notice shall be included in all copies or
16// substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
19// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
21// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23//
24//--------------------------------------------------------------------------------------------------
25//
26// Copyright (c) 2016 Nic Holthaus
27//
28//--------------------------------------------------------------------------------------------------
29//
32//
33//--------------------------------------------------------------------------------------------------
34
35#pragma once
36
37#ifndef units_serialization_h_
38#define units_serialization_h_
39
40#include <algorithm>
41#include <array>
42#include <cmath>
43#include <compare>
44#include <cstddef>
45#include <cstdint>
46#include <cstdio>
47#include <cstring>
48#include <expected>
49#include <functional>
50#include <istream>
51#include <iterator>
52#include <limits>
53#include <memory>
54#include <ostream>
55#include <span>
56#include <stdexcept>
57#include <string>
58#include <string_view>
59#include <tuple>
60#include <units/core.h>
61#include <utility>
62#include <vector>
63
64namespace units
65{
66 namespace detail
67 {
74 using builtin_dimensions = std::tuple<dimension::length, dimension::mass, dimension::time, dimension::current, dimension::temperature, dimension::substance, dimension::luminous_intensity,
81
82 //------------------------------------------------------------------------------------------------------------------
83 // FUNCTION: name_hash [static]
84 //------------------------------------------------------------------------------------------------------------------
91 //------------------------------------------------------------------------------------------------------------------
92 constexpr std::uint64_t name_hash(std::string_view name) noexcept
93 {
94 std::uint64_t h = 1469598103934665603ULL;
95 for (const char c : name)
96 {
97 h ^= static_cast<std::uint8_t>(c);
98 h *= 1099511628211ULL;
99 }
100 return h;
101 }
102 } // namespace detail
103
114
120 class any_unit;
121
122 //======================================================================================================================
123 // VARINT (LEB128) — the terse integer codec
124 //======================================================================================================================
125
126 namespace detail
127 {
128 //------------------------------------------------------------------------------------------------------------------
129 // FUNCTION: put_uvarint [static]
130 //------------------------------------------------------------------------------------------------------------------
134 //------------------------------------------------------------------------------------------------------------------
135 inline void put_uvarint(std::vector<std::byte>& out, std::uint64_t value)
136 {
137 do
138 {
139 std::uint8_t byte = value & 0x7F;
140 value >>= 7;
141 if (value != 0)
142 byte |= 0x80;
143 out.push_back(std::byte{byte});
144 } while (value != 0);
145 }
146
147 //------------------------------------------------------------------------------------------------------------------
148 // FUNCTION: put_svarint [static]
149 //------------------------------------------------------------------------------------------------------------------
153 //------------------------------------------------------------------------------------------------------------------
154 inline void put_svarint(std::vector<std::byte>& out, std::int64_t value)
155 {
156 put_uvarint(out, (static_cast<std::uint64_t>(value) << 1) ^ static_cast<std::uint64_t>(value >> 63));
157 }
158
159 //------------------------------------------------------------------------------------------------------------------
160 // FUNCTION: get_uvarint [static]
161 //------------------------------------------------------------------------------------------------------------------
167 //------------------------------------------------------------------------------------------------------------------
168 inline bool get_uvarint(const std::byte*& cursor, const std::byte* end, std::uint64_t& value)
169 {
170 value = 0;
171 unsigned int shift = 0;
172 while (cursor != end)
173 {
174 const auto byte = std::to_integer<std::uint8_t>(*cursor++);
175 value |= static_cast<std::uint64_t>(byte & 0x7F) << shift;
176 if ((byte & 0x80) == 0)
177 return true;
178 shift += 7;
179 if (shift >= 64)
180 return false;
181 }
182 return false;
183 }
184
185 //------------------------------------------------------------------------------------------------------------------
186 // FUNCTION: get_svarint [static]
187 //------------------------------------------------------------------------------------------------------------------
193 //------------------------------------------------------------------------------------------------------------------
194 inline bool get_svarint(const std::byte*& cursor, const std::byte* end, std::int64_t& value)
195 {
196 std::uint64_t raw;
197 if (!get_uvarint(cursor, end, raw))
198 return false;
199 value = static_cast<std::int64_t>((raw >> 1) ^ (~(raw & 1) + 1));
200 return true;
201 }
202 } // namespace detail
203
204 //======================================================================================================================
205 // UNIT IDENTITY — the compile-time dimension signature, and its runtime form
206 //======================================================================================================================
207
212 {
213 std::uint64_t hash;
214 std::int64_t num;
215 std::int64_t den;
216 };
217
222 {
223 std::vector<dimension_term> terms;
224
225 //------------------------------------------------------------------------------------------------------------------
226 // FUNCTION: operator== [public]
227 //------------------------------------------------------------------------------------------------------------------
231 //------------------------------------------------------------------------------------------------------------------
232 bool operator==(const unit_identity& other) const noexcept
233 {
234 if (terms.size() != other.terms.size())
235 return false;
236 for (std::size_t i = 0; i < terms.size(); ++i)
237 if (terms[i].hash != other.terms[i].hash || terms[i].num != other.terms[i].num || terms[i].den != other.terms[i].den)
238 return false;
239 return true;
240 }
241 };
242
243 namespace detail
244 {
245 //------------------------------------------------------------------------------------------------------------------
246 // FUNCTION: dimension_arity [static]
247 //------------------------------------------------------------------------------------------------------------------
251 //------------------------------------------------------------------------------------------------------------------
252 template<class DimensionList>
253 consteval std::size_t dimension_arity()
254 {
255 if constexpr (DimensionList::empty)
256 return 0;
257 else
259 }
260
261 //------------------------------------------------------------------------------------------------------------------
262 // FUNCTION: fill_terms [static]
263 //------------------------------------------------------------------------------------------------------------------
271 //------------------------------------------------------------------------------------------------------------------
272 template<class DimensionList, std::size_t N>
273 consteval void fill_terms(std::array<dimension_term, N>& out, std::size_t at)
274 {
275 if constexpr (!DimensionList::empty)
276 {
277 using front_dim = typename DimensionList::front;
278 using tag = typename front_dim::dimension;
279 using exponent = typename front_dim::exponent;
280 out[at] = dimension_term{name_hash(std::string_view(tag::name)), exponent::num, exponent::den};
282 }
283 }
284
287 template<UnitType Unit>
289 {
291 static constexpr std::size_t arity = dimension_arity<Dim>();
292
293 static consteval std::array<dimension_term, arity> compute()
294 {
295 std::array<dimension_term, arity> terms{};
296 fill_terms<Dim, arity>(terms, 0);
297 // insertion sort by hash — arity is tiny (the base dimensions of one quantity)
298 for (std::size_t i = 1; i < arity; ++i)
299 {
300 dimension_term key = terms[i];
301 std::size_t j = i;
302 while (j > 0 && terms[j - 1].hash > key.hash)
303 {
304 terms[j] = terms[j - 1];
305 --j;
306 }
307 terms[j] = key;
308 }
309 return terms;
310 }
311
312 static constexpr std::array<dimension_term, arity> value = compute();
313 };
314
315 //------------------------------------------------------------------------------------------------------------------
316 // FUNCTION: identity_of [static]
317 //------------------------------------------------------------------------------------------------------------------
322 //------------------------------------------------------------------------------------------------------------------
323 template<UnitType Unit>
325 {
326 unit_identity id;
327 id.terms.assign(signature<Unit>::value.begin(), signature<Unit>::value.end());
328 return id;
329 }
330
332 template<class Dim>
334 } // namespace detail
335
336 //======================================================================================================================
337 // WIRE FORMAT
338 //======================================================================================================================
339
340 namespace detail
341 {
342 inline constexpr std::uint8_t serialization_version = 1;
343
345 enum class value_kind : std::uint8_t
346 {
348 f32 = 1,
349 f64 = 2
350 };
351
352 //------------------------------------------------------------------------------------------------------------------
353 // FUNCTION: encode [static]
354 //------------------------------------------------------------------------------------------------------------------
364 //------------------------------------------------------------------------------------------------------------------
365 inline std::vector<std::byte> encode(const unit_identity& identity, double base)
366 {
368 if (base == std::floor(base) && std::abs(base) < 9.0e15)
370 else if (static_cast<double>(static_cast<float>(base)) == base)
372 else
374
375 // any fractional exponent forces the fracExp flag
376 bool fracExp = false;
377 for (const auto& term : identity.terms)
378 if (term.den != 1)
379 fracExp = true;
380
381 std::vector<std::byte> out;
382 out.push_back(std::byte{serialization_version});
383 const std::uint8_t header = static_cast<std::uint8_t>(static_cast<std::uint8_t>(kind) | (fracExp ? 0x04 : 0x00));
384 out.push_back(std::byte{header});
385 put_uvarint(out, identity.terms.size());
386 for (const auto& term : identity.terms)
387 {
388 // base dimension keyed by an 8-byte name-hash: fixed size, no central table, any dimension round-trips
389 for (unsigned int i = 0; i < 8; ++i)
390 out.push_back(std::byte{static_cast<std::uint8_t>(term.hash >> (8 * i))});
391 put_svarint(out, term.num);
392 if (fracExp)
393 put_svarint(out, term.den);
394 }
395
396 switch (kind)
397 {
398 case value_kind::ivarint: put_svarint(out, static_cast<std::int64_t>(base)); break;
399 case value_kind::f32:
400 {
401 const float f = static_cast<float>(base);
402 std::uint32_t bits;
403 std::memcpy(&bits, &f, sizeof(bits));
404 for (unsigned int i = 0; i < 4; ++i)
405 out.push_back(std::byte{static_cast<std::uint8_t>(bits >> (8 * i))});
406 break;
407 }
408 case value_kind::f64:
409 {
410 std::uint64_t bits;
411 std::memcpy(&bits, &base, sizeof(bits));
412 for (unsigned int i = 0; i < 8; ++i)
413 out.push_back(std::byte{static_cast<std::uint8_t>(bits >> (8 * i))});
414 break;
415 }
416 }
417 return out;
418 }
419 } // namespace detail
420
421 //======================================================================================================================
422 // any_unit
423 //======================================================================================================================
424
426 {
427 public:
428 //------------------------------------------------------------------------------------------------------------------
429 // FUNCTION: any_unit [public]
430 //------------------------------------------------------------------------------------------------------------------
437 //------------------------------------------------------------------------------------------------------------------
438 any_unit(unit_identity id, double base)
439 : m_identity(std::move(id)),
440 m_base(base),
441 m_bytes(detail::encode(m_identity, base))
442 {
443 }
444
445 //------------------------------------------------------------------------------------------------------------------
446 // FUNCTION: any_unit [public]
447 //------------------------------------------------------------------------------------------------------------------
451 //------------------------------------------------------------------------------------------------------------------
453 : m_identity(),
454 m_base(0.0),
455 m_bytes(detail::encode(m_identity, 0.0))
456 {
457 }
458
459 //------------------------------------------------------------------------------------------------------------------
460 // FUNCTION: is [public]
461 //------------------------------------------------------------------------------------------------------------------
465 //------------------------------------------------------------------------------------------------------------------
466 template<class Dimension>
467 [[nodiscard]] bool is() const noexcept
468 {
470 }
471
472 //------------------------------------------------------------------------------------------------------------------
473 // FUNCTION: value_in_base [public]
474 //------------------------------------------------------------------------------------------------------------------
477 //------------------------------------------------------------------------------------------------------------------
478 [[nodiscard]] double value_in_base() const noexcept
479 {
480 return m_base;
481 }
482
483 //------------------------------------------------------------------------------------------------------------------
484 // FUNCTION: identity [public]
485 //------------------------------------------------------------------------------------------------------------------
488 //------------------------------------------------------------------------------------------------------------------
489 [[nodiscard]] const unit_identity& identity() const noexcept
490 {
491 return m_identity;
492 }
493
494 //======================================================================================================================
495 // BYTES — the owned serialized form, in both a type-safe and a C-interface view
496 //======================================================================================================================
497
498 //------------------------------------------------------------------------------------------------------------------
499 // FUNCTION: bytes [public]
500 //------------------------------------------------------------------------------------------------------------------
505 //------------------------------------------------------------------------------------------------------------------
506 [[nodiscard]] std::span<const std::byte> bytes() const noexcept
507 {
508 return m_bytes;
509 }
510
511 //------------------------------------------------------------------------------------------------------------------
512 // FUNCTION: operator std::span<const std::byte> [public]
513 //------------------------------------------------------------------------------------------------------------------
518 //------------------------------------------------------------------------------------------------------------------
519 [[nodiscard]] operator std::span<const std::byte>() const noexcept
520 {
521 return m_bytes;
522 }
523
524 //------------------------------------------------------------------------------------------------------------------
525 // FUNCTION: data [public]
526 //------------------------------------------------------------------------------------------------------------------
532 //------------------------------------------------------------------------------------------------------------------
533 [[nodiscard]] const char* data() const noexcept
534 {
535 return reinterpret_cast<const char*>(m_bytes.data());
536 }
537
538 //------------------------------------------------------------------------------------------------------------------
539 // FUNCTION: size [public]
540 //------------------------------------------------------------------------------------------------------------------
543 //------------------------------------------------------------------------------------------------------------------
544 [[nodiscard]] std::size_t size() const noexcept
545 {
546 return m_bytes.size();
547 }
548
549 //------------------------------------------------------------------------------------------------------------------
550 // FUNCTION: to_string [public]
551 //------------------------------------------------------------------------------------------------------------------
560 //------------------------------------------------------------------------------------------------------------------
561 [[nodiscard]] std::string to_string() const
562 {
563 std::string named;
564 // resolve to the canonical named unit of whichever known dimension matches, and render it exactly as a
565 // concrete unit streams (name/abbreviation + dimension form); leave `named` empty if no known dimension matched
566 try
567 {
568 visit([&named](const auto& quantity) { named = units::to_string(quantity); });
569 }
570 catch (const std::runtime_error&)
571 {
572 }
573 return named.empty() ? to_string_raw() : named;
574 }
575
576 //------------------------------------------------------------------------------------------------------------------
577 // FUNCTION: to_string_raw [public]
578 //------------------------------------------------------------------------------------------------------------------
586 //------------------------------------------------------------------------------------------------------------------
587 [[nodiscard]] std::string to_string_raw() const
588 {
589 std::string out = std::to_string(m_base);
590 if (m_identity.terms.empty())
591 {
592 out += " [dimensionless]";
593 return out;
594 }
595 out += " [";
596 for (std::size_t i = 0; i < m_identity.terms.size(); ++i)
597 {
598 if (i != 0)
599 out += ' ';
600 char hex[19];
601 std::snprintf(hex, sizeof(hex), "#%llx", static_cast<unsigned long long>(m_identity.terms[i].hash));
602 out += hex;
603 out += '^';
604 out += std::to_string(m_identity.terms[i].num);
605 if (m_identity.terms[i].den != 1)
606 {
607 out += '/';
608 out += std::to_string(m_identity.terms[i].den);
609 }
610 }
611 out += ']';
612 return out;
613 }
614
615 //======================================================================================================================
616 // COMPARISON
617 //======================================================================================================================
618
619 //------------------------------------------------------------------------------------------------------------------
620 // FUNCTION: operator== [public]
621 //------------------------------------------------------------------------------------------------------------------
631 //------------------------------------------------------------------------------------------------------------------
632 [[nodiscard]] bool operator==(const any_unit& other) const noexcept
633 {
634 if (!(m_identity == other.m_identity))
635 return false;
636 const double diff = std::abs(m_base - other.m_base);
637 return diff < std::numeric_limits<double>::epsilon() * std::abs(m_base + other.m_base) || diff < std::numeric_limits<double>::min();
638 }
639
640 //------------------------------------------------------------------------------------------------------------------
641 // FUNCTION: operator!= [public]
642 //------------------------------------------------------------------------------------------------------------------
646 //------------------------------------------------------------------------------------------------------------------
647 [[nodiscard]] bool operator!=(const any_unit& other) const noexcept
648 {
649 return !(*this == other);
650 }
651
652 //------------------------------------------------------------------------------------------------------------------
653 // FUNCTION: operator<=> [public]
654 //------------------------------------------------------------------------------------------------------------------
662 //------------------------------------------------------------------------------------------------------------------
663 [[nodiscard]] std::partial_ordering operator<=>(const any_unit& other) const noexcept
664 {
665 if (!(m_identity == other.m_identity))
666 return std::partial_ordering::unordered;
667 if (*this == other)
668 return std::partial_ordering::equivalent;
669 return m_base <=> other.m_base;
670 }
671
672 //------------------------------------------------------------------------------------------------------------------
673 // FUNCTION: to [public]
674 //------------------------------------------------------------------------------------------------------------------
678 //------------------------------------------------------------------------------------------------------------------
679 template<class Unit>
680 [[nodiscard]] std::expected<Unit, deserialize_error> to() const
681 {
682 static_assert(traits::is_unit_v<Unit>,
683 "any_unit::to<T>() collapses into a unit type (e.g. meters<double>), not a bare number. To read a plain value, collapse to a unit first, then call .value() or .to<double>() on that "
684 "unit.");
685 // gate the body so a non-unit Unit produces ONLY the friendly message above, no downstream template soup
686 if constexpr (traits::is_unit_v<Unit>)
687 {
688 if (m_identity != detail::identity_of<Unit>())
689 return std::unexpected(deserialize_error::dimension_mismatch);
690
691 using ConversionFactor = typename traits::unit_traits<Unit>::conversion_factor;
693 using UnderlyingTarget = typename traits::unit_traits<Unit>::underlying_type;
694
695 // Express the SI-base magnitude in the TARGET unit's scale, all in double so no lossy unit conversion is
696 // attempted: a double-underlying instance of the target unit converts from the canonical base cleanly.
697 // Take its point-scale value (raw), which is what the target unit's constructor expects -- for a
698 // ratio-scaled dimensionless unit (percent, parts-per-million) the point value (50) differs from the
699 // normalized value (0.5), and reconstructing from the normalized value would rescale by the unit's
700 // ratio. For a linear unit the two are identical, so this is a no-op there.
701 using TargetAsDouble = unit<traits::strong_t<ConversionFactor>, double, typename traits::unit_traits<Unit>::numerical_scale_type>;
702 const double as_raw = TargetAsDouble(detail::canonical_unit_t<Dim>(m_base)).raw();
703
704 // Narrow to the target's underlying type. An integral target that cannot represent the value exactly is
705 // a lossy_target error rather than a silent truncation.
706 if constexpr (!std::is_floating_point_v<UnderlyingTarget>)
707 {
708 if (as_raw != std::floor(as_raw) || std::abs(as_raw) > static_cast<double>(std::numeric_limits<UnderlyingTarget>::max()))
709 return std::unexpected(deserialize_error::lossy_target);
710 }
711 return Unit(static_cast<UnderlyingTarget>(as_raw));
712 }
713 else
714 {
715 return std::unexpected(deserialize_error::dimension_mismatch); // unreachable; the static_assert fired
716 }
717 }
718
719 //------------------------------------------------------------------------------------------------------------------
720 // FUNCTION: try_to [public]
721 //------------------------------------------------------------------------------------------------------------------
725 //------------------------------------------------------------------------------------------------------------------
726 template<class Unit>
727 [[nodiscard]] Unit try_to() const
728 {
729 static_assert(traits::is_unit_v<Unit>, "any_unit::try_to<T>() collapses into a unit type (e.g. meters<double>), not a bare number.");
730 auto result = to<Unit>();
731 if (!result)
732 throw std::runtime_error("units::any_unit: dimension mismatch collapsing to the requested unit");
733 return *result;
734 }
735
736 //------------------------------------------------------------------------------------------------------------------
737 // FUNCTION: assign_to [public]
738 //------------------------------------------------------------------------------------------------------------------
751 //------------------------------------------------------------------------------------------------------------------
752 template<class Unit>
753 bool assign_to(Unit& out) const
754 {
755 static_assert(traits::is_unit_v<Unit>, "any_unit::assign_to(out) assigns into a unit variable (e.g. meters<double>), not a bare number. Collapse to a unit, then read its value.");
756 if (auto result = to<Unit>())
757 {
758 out = *result;
759 return true;
760 }
761 return false;
762 }
763
764 //------------------------------------------------------------------------------------------------------------------
765 // FUNCTION: visit [public]
766 //------------------------------------------------------------------------------------------------------------------
784 //------------------------------------------------------------------------------------------------------------------
785 template<class... Dimensions, class Visitor>
786 void visit(Visitor&& visitor) const
787 {
788 // bind to a named lvalue so the traversal passes it through by reference (never moving it), and the
789 // single invocation site (try_dispatch_one) is the only place it is used as the caller's value category
790 bool matched;
791 if constexpr (sizeof...(Dimensions) == 0)
792 {
793 matched = dispatch_tuple<detail::builtin_dimensions>(std::forward<Visitor>(visitor));
794 // The empty (dimensionless) signature is deliberately not a listed candidate — it is the sink every
795 // dimensionless quantity erases to, so listing it would silently claim ratio-scaled quantities. Under
796 // the default set it is still resolved directly, so a serialized scalar or same-dimension ratio never
797 // hard-throws.
798 if (!matched)
799 matched = try_dispatch_one<dimension::dimensionless>(std::forward<Visitor>(visitor));
800 }
801 else
802 matched = dispatch_list<Dimensions...>(std::forward<Visitor>(visitor));
803 if (!matched)
804 throw std::runtime_error("units::any_unit: no candidate dimension matched the stream");
805 }
806
807 private:
808 //------------------------------------------------------------------------------------------------------------------
809 // FUNCTION: try_dispatch_one [private]
810 //------------------------------------------------------------------------------------------------------------------
816 //------------------------------------------------------------------------------------------------------------------
817 template<class Dimension, class Visitor>
818 bool try_dispatch_one(Visitor&& visitor) const
819 {
821 if (m_identity == detail::identity_of<Base>())
822 {
823 std::forward<Visitor>(visitor)(Base(m_base));
824 return true;
825 }
826 return false;
827 }
828
832 template<class... Dimensions, class Visitor>
833 bool dispatch_list(Visitor&& visitor) const
834 {
835 bool matched = false;
836 // fold in order; stop invoking once matched
837 ((matched = matched || try_dispatch_one<Dimensions>(visitor)), ...);
838 return matched;
839 }
840
843 template<class DimTuple, std::size_t I = 0, class Visitor>
844 bool dispatch_tuple(Visitor&& visitor) const
845 {
846 if constexpr (I < std::tuple_size_v<DimTuple>)
847 {
848 if (try_dispatch_one<std::tuple_element_t<I, DimTuple>>(visitor))
849 return true;
850 return dispatch_tuple<DimTuple, I + 1>(visitor);
851 }
852 return false;
853 }
854
855 unit_identity m_identity;
856 double m_base;
857 std::vector<std::byte> m_bytes;
858 };
859
860 //----------------------------------------------------------------------------------------------------------------------
861 // FUNCTION: operator<< [public]
862 //----------------------------------------------------------------------------------------------------------------------
870 //----------------------------------------------------------------------------------------------------------------------
871 inline std::ostream& operator<<(std::ostream& os, const any_unit& value)
872 {
873 os.write(value.data(), static_cast<std::streamsize>(value.size()));
874 return os;
875 }
876
877 //======================================================================================================================
878 // unit_cast — reclaimed: the explicit throwing collapse from any_unit to a concrete unit
879 //======================================================================================================================
880
881 //----------------------------------------------------------------------------------------------------------------------
882 // FUNCTION: unit_cast [public]
883 //----------------------------------------------------------------------------------------------------------------------
890 //----------------------------------------------------------------------------------------------------------------------
891 template<class Target>
892 [[nodiscard]] Target unit_cast(const any_unit& value)
893 {
894 static_assert(traits::is_unit_v<Target>, "units::unit_cast<T>(any_unit) casts to a unit type (e.g. meters<double>), not a bare number. Collapse to a unit, then read its value.");
895 return value.try_to<Target>();
896 }
897
898 //======================================================================================================================
899 // serialize / deserialize
900 //======================================================================================================================
901
902 //----------------------------------------------------------------------------------------------------------------------
903 // FUNCTION: serialize [public]
904 //----------------------------------------------------------------------------------------------------------------------
913 //----------------------------------------------------------------------------------------------------------------------
914 template<class Unit>
915 [[nodiscard]] any_unit serialize(const Unit& quantity)
916 {
917 static_assert(traits::is_unit_v<Unit>, "units::serialize requires a units quantity (e.g. meters<double>). Its argument is not a unit type; wrap the value in a unit before serializing.");
918 // gate the body so a non-unit argument produces ONLY the friendly message above, no downstream template soup
919 if constexpr (!traits::is_unit_v<Unit>)
920 return any_unit{unit_identity{}, 0.0};
921 else
922 {
923 constexpr auto& sig = detail::signature<Unit>::value; // fixed-array compile-time signature (sorted by hash)
924
925 // value in SI canonical base
928 const double base = Base(quantity).value();
929
930 // lift the compile-time signature into the runtime identity, then let any_unit own its encoded form
931 unit_identity id;
932 id.terms.assign(sig.begin(), sig.end());
933
934 return any_unit{std::move(id), base};
935 }
936 }
937
938 //----------------------------------------------------------------------------------------------------------------------
939 // FUNCTION: deserialize [public]
940 //----------------------------------------------------------------------------------------------------------------------
947 //----------------------------------------------------------------------------------------------------------------------
948 [[nodiscard]] inline std::expected<any_unit, deserialize_error> deserialize(std::span<const std::byte> bytes)
949 {
950 const std::byte* cursor = bytes.data();
951 const std::byte* end = bytes.data() + bytes.size();
952
953 if (cursor == end)
954 return std::unexpected(deserialize_error::truncated);
955 const std::uint8_t version = std::to_integer<std::uint8_t>(*cursor++);
956 if (version != detail::serialization_version)
957 return std::unexpected(deserialize_error::bad_version);
958
959 if (cursor == end)
960 return std::unexpected(deserialize_error::truncated);
961 const std::uint8_t header = std::to_integer<std::uint8_t>(*cursor++);
962 const auto kind = static_cast<detail::value_kind>(header & 0x03);
963 const bool fracExp = (header & 0x04) != 0;
964
965 std::uint64_t count = 0;
966 if (!detail::get_uvarint(cursor, end, count))
967 return std::unexpected(deserialize_error::truncated);
968
969 // Reserve only up to what the remaining bytes could actually hold, never the raw wire count: every term needs
970 // at least nine bytes (eight hash bytes plus a one-byte exponent numerator), so no more than (end - cursor)
971 // terms can follow. This keeps a huge count off untrusted input from throwing out of reserve() while leaving
972 // the classification to the loop below -- a count that overruns the buffer is reported as `truncated` there,
973 // exactly as a short buffer with an honest count is. A zero count is the valid dimensionless case.
974 unit_identity id;
975 id.terms.reserve(std::min<std::uint64_t>(count, static_cast<std::uint64_t>(end - cursor)));
976 bool havePrevHash = false;
977 std::uint64_t prevHash = 0;
978 for (std::uint64_t i = 0; i < count; ++i)
979 {
980 if (end - cursor < 8)
981 return std::unexpected(deserialize_error::truncated);
982 std::uint64_t hash = 0;
983 for (unsigned int byteIndex = 0; byteIndex < 8; ++byteIndex)
984 hash |= static_cast<std::uint64_t>(std::to_integer<std::uint8_t>(*cursor++)) << (8 * byteIndex);
985 std::int64_t num = 0;
986 std::int64_t den = 1;
987 if (!detail::get_svarint(cursor, end, num))
988 return std::unexpected(deserialize_error::truncated);
989 if (fracExp && !detail::get_svarint(cursor, end, den))
990 return std::unexpected(deserialize_error::truncated);
991 // Each term must uphold the invariants the serializer always writes: a nonzero rational exponent (a zero
992 // denominator is the undefined num/0; a zero numerator is a phantom base the reducer never emits), and a
993 // strictly ascending hash (terms are stored sorted and unique so equality is order-independent). A
994 // complete stream that violates any of these is malformed input, not a valid quantity.
995 if (den == 0 || num == 0)
996 return std::unexpected(deserialize_error::malformed);
997 if (havePrevHash && hash <= prevHash)
998 return std::unexpected(deserialize_error::malformed);
999 prevHash = hash;
1000 havePrevHash = true;
1001 id.terms.push_back(dimension_term{hash, num, den});
1002 }
1003
1004 double base = 0.0;
1005 switch (kind)
1006 {
1008 {
1009 std::int64_t v;
1010 if (!detail::get_svarint(cursor, end, v))
1011 return std::unexpected(deserialize_error::truncated);
1012 base = static_cast<double>(v);
1013 break;
1014 }
1016 {
1017 if (end - cursor < 4)
1018 return std::unexpected(deserialize_error::truncated);
1019 std::uint32_t bits = 0;
1020 for (unsigned int i = 0; i < 4; ++i)
1021 bits |= static_cast<std::uint32_t>(std::to_integer<std::uint8_t>(*cursor++)) << (8 * i);
1022 float f;
1023 std::memcpy(&f, &bits, sizeof(f));
1024 base = static_cast<double>(f);
1025 break;
1026 }
1028 {
1029 if (end - cursor < 8)
1030 return std::unexpected(deserialize_error::truncated);
1031 std::uint64_t bits = 0;
1032 for (unsigned int i = 0; i < 8; ++i)
1033 bits |= static_cast<std::uint64_t>(std::to_integer<std::uint8_t>(*cursor++)) << (8 * i);
1034 std::memcpy(&base, &bits, sizeof(base));
1035 break;
1036 }
1037 default: return std::unexpected(deserialize_error::bad_version);
1038 }
1039
1040 return any_unit(std::move(id), base);
1041 }
1042
1043 //----------------------------------------------------------------------------------------------------------------------
1044 // FUNCTION: deserialize [public]
1045 //----------------------------------------------------------------------------------------------------------------------
1050 //----------------------------------------------------------------------------------------------------------------------
1051 template<class Unit>
1052 [[nodiscard]] std::expected<Unit, deserialize_error> deserialize(std::span<const std::byte> bytes)
1053 {
1054 static_assert(traits::is_unit_v<Unit>,
1055 "units::deserialize<T>(bytes) decodes into a unit type (e.g. deserialize<meters<double>>). The requested type is not a unit; use deserialize(bytes) for an erased any_unit.");
1056 auto erased = deserialize(bytes);
1057 if (!erased)
1058 return std::unexpected(erased.error());
1059 return erased->template to<Unit>();
1060 }
1061
1062 //----------------------------------------------------------------------------------------------------------------------
1063 // FUNCTION: deserialize [public]
1064 //----------------------------------------------------------------------------------------------------------------------
1074 //----------------------------------------------------------------------------------------------------------------------
1075 [[nodiscard]] inline std::expected<any_unit, deserialize_error> deserialize(std::istream& is)
1076 {
1077 const std::istream::pos_type start = is.tellg();
1078 if (start == std::istream::pos_type(-1))
1079 return std::unexpected(deserialize_error::truncated); // not seekable: records can't be self-delimited here
1080
1081 // Read one self-delimiting record incrementally instead of draining the whole remaining stream: grow a small
1082 // buffer a chunk at a time and retry the span decoder until it either succeeds or fails for a reason other
1083 // than running short of bytes. A record is small (tens of bytes) and has no ceiling, so grow-and-retry reads
1084 // only about one record's worth per call -- reading N records is linear, not quadratic (which draining the
1085 // remainder each call made it). On success the stream is left just past this record (the unused tail read
1086 // while growing is seeked back), so the next read gets the following record.
1087 std::vector<char> buffer;
1088 std::size_t chunk = 64; // covers essentially every record in the first read
1089 std::expected<any_unit, deserialize_error> decoded{std::unexpected(deserialize_error::truncated)};
1090 while (true)
1091 {
1092 const std::size_t had = buffer.size();
1093 buffer.resize(had + chunk);
1094 is.read(buffer.data() + had, static_cast<std::streamsize>(chunk));
1095 const std::streamsize got = is.gcount();
1096 buffer.resize(had + static_cast<std::size_t>(got));
1097
1098 decoded = deserialize(std::span<const std::byte>(reinterpret_cast<const std::byte*>(buffer.data()), buffer.size()));
1099 // A successful decode, or a genuine (non-truncation) error, is final. `truncated` with more bytes still
1100 // available in the stream means the record spans past what has been read so far -- grow and retry.
1101 if (decoded || decoded.error() != deserialize_error::truncated || got < static_cast<std::streamsize>(chunk))
1102 break;
1103 chunk *= 2; // the record is unusually large; widen the next read
1104 }
1105
1106 is.clear(); // a short read set eofbit/failbit; clear so a good decode leaves the stream usable
1107 if (decoded)
1108 is.seekg(start + static_cast<std::istream::off_type>(decoded->size())); // leave the stream just past this record
1109 return decoded;
1110 }
1111
1112 //----------------------------------------------------------------------------------------------------------------------
1113 // FUNCTION: operator>> [public]
1114 //----------------------------------------------------------------------------------------------------------------------
1123 //----------------------------------------------------------------------------------------------------------------------
1124 inline std::istream& operator>>(std::istream& is, any_unit& value)
1125 {
1126 if (auto decoded = deserialize(is))
1127 value = std::move(*decoded);
1128 else
1129 is.setstate(std::ios::failbit);
1130 return is;
1131 }
1132
1133 //----------------------------------------------------------------------------------------------------------------------
1134 // FUNCTION: deserialize [public]
1135 //----------------------------------------------------------------------------------------------------------------------
1144 //----------------------------------------------------------------------------------------------------------------------
1145 template<class Unit>
1146 [[nodiscard]] std::expected<Unit, deserialize_error> deserialize(std::istream& is)
1147 {
1148 static_assert(traits::is_unit_v<Unit>,
1149 "units::deserialize<T>(stream) decodes into a unit type (e.g. deserialize<meters<double>>). The requested type is not a unit; use deserialize(stream) for an erased any_unit.");
1150 auto erased = deserialize(is);
1151 if (!erased)
1152 return std::unexpected(erased.error());
1153 return erased->template to<Unit>();
1154 }
1155} // namespace units
1156
1157//----------------------------------------------------------------------------------------------------------------------
1158// std::hash<units::any_unit>
1159//----------------------------------------------------------------------------------------------------------------------
1163template<>
1164struct std::hash<units::any_unit>
1165{
1166 std::size_t operator()(const units::any_unit& value) const noexcept
1167 {
1168 // FNV-1a-style fold over the term signature, mixed with the base-value hash
1169 std::size_t seed = std::hash<double>()(value.value_in_base());
1170 const auto mix = [&seed](std::size_t h) noexcept { seed ^= h + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); };
1171 for (const auto& term : value.identity().terms)
1172 {
1173 mix(std::hash<std::uint64_t>()(term.hash));
1174 mix(std::hash<std::int64_t>()(term.num));
1175 mix(std::hash<std::int64_t>()(term.den));
1176 }
1177 return seed;
1178 }
1179};
1180
1181#endif // units_serialization_h_
Definition serialization.h:426
bool assign_to(Unit &out) const
collapses into an existing unit variable, leaving it untouched on a dimension mismatch
Definition serialization.h:753
double value_in_base() const noexcept
the magnitude in SI canonical base units, for logging or routing
Definition serialization.h:478
std::expected< Unit, deserialize_error > to() const
collapses into a concrete unit, checked (the safe default)
Definition serialization.h:680
any_unit()
constructs an empty erased quantity (dimensionless, zero) — the target for stream extraction
Definition serialization.h:452
bool is() const noexcept
whether this erased quantity is of the requested dimension
Definition serialization.h:467
bool operator!=(const any_unit &other) const noexcept
whether two erased quantities differ in dimension or magnitude
Definition serialization.h:647
Unit try_to() const
collapses into a concrete unit, throwing on a dimension mismatch
Definition serialization.h:727
bool operator==(const any_unit &other) const noexcept
whether two erased quantities are the same dimension and magnitude
Definition serialization.h:632
any_unit(unit_identity id, double base)
constructs an erased quantity from a decoded identity and SI-base magnitude
Definition serialization.h:438
std::span< const std::byte > bytes() const noexcept
the serialized byte stream, as a type-safe view
Definition serialization.h:506
std::string to_string_raw() const
the dimension-agnostic text rendering, keyed by name-hash — always available, never resolves a name
Definition serialization.h:587
std::string to_string() const
a human-readable text rendering of the erased quantity, for logging and diagnostics
Definition serialization.h:561
std::partial_ordering operator<=>(const any_unit &other) const noexcept
orders two erased quantities of the same dimension by magnitude
Definition serialization.h:663
const char * data() const noexcept
a pointer to the serialized bytes as const char*, for byte-oriented interfaces
Definition serialization.h:533
const unit_identity & identity() const noexcept
the decoded dimension signature
Definition serialization.h:489
std::size_t size() const noexcept
the number of serialized bytes
Definition serialization.h:544
void visit(Visitor &&visitor) const
invokes a visitor with the canonical quantity for the decoded dimension
Definition serialization.h:786
Definition core.h:2741
constexpr underlying_type raw() const noexcept
scaled unit value
Definition core.h:2983
unit, dimensional analysis, generic cmath functions, traits (not dimension-specific),...
@ base
the SI base-dimension list (" m s^-1"); pairs with a base-converted value.
Definition core.h:3454
@ name
the unit's own full name ("meters", "feet"); base-dimension list if unnamed.
Definition core.h:3453
constexpr T unit_cast(const Unit &value) noexcept
Casts an unit to an arithmetic type.
Definition core.h:3789
STL namespace.
constexpr meters_per_second c(299792458.0)
Speed of light in vacuum.
constexpr unit< compound_conversion_factor< joules_, seconds_ > > h(6.62607015e-34)
Planck constant.
make_dimension< length, std::ratio< 2 >, time, std::ratio<-2 > > radioactivity
< Represents an SI derived unit of luminance
Definition core.h:1361
dimension_multiply< pressure, time > dynamic_viscosity
< Represents an SI derived unit of density
Definition core.h:1377
dimension_divide< mass, volume > density
< Represents an SI derived unit of torque
Definition core.h:1376
make_dimension< luminous_intensity, std::ratio< 1 >, length, std::ratio<-2 > > luminance
< Represents an SI derived unit of illuminance
Definition core.h:1360
dimension_pow< angle, std::ratio< 2 > > solid_angle
< Represents a quantity of angle
Definition core.h:1337
dimension_divide< current, voltage > conductance
< Represents an SI derived unit of impedance
Definition core.h:1355
make_dimension< power, std::ratio< 1 >, length, std::ratio<-1 > > spectral_flux
< Represents an SI derived unit of spectral intensity
Definition core.h:1369
dimension_divide< mass, substance > substance_mass
< Represents an SI derived unit of radioactivity
Definition core.h:1362
make_dimension< radiant_intensity, std::ratio< 1 >, area, std::ratio<-1 > > radiance
< Represents an SI derived unit of radiant intensity
Definition core.h:1366
dimension_divide< voltage, current > impedance
< Represents an SI derived unit of capacitance
Definition core.h:1354
dimension_divide< substance, mass > substance_concentration
< Represents an SI derived unit of substance mass
Definition core.h:1363
dimension_divide< energy, time > power
< Represents an SI derived unit of energy
Definition core.h:1351
dimension_multiply< impedance, time > inductance
< Represents an SI derived unit of magnetic flux
Definition core.h:1357
dimension_pow< length, std::ratio< 3 > > volume
< Represents an SI derived unit of area
Definition core.h:1346
dimension_divide< area, time > kinematic_viscosity
< Represents an SI derived unit of dynamic (absolute) viscosity
Definition core.h:1378
make_dimension< angle_tag > angle
< Represents a quantity with no dimension.
Definition core.h:1334
dimension_divide< velocity, time > acceleration
< Represents an SI derived unit of angular velocity
Definition core.h:1341
dimension_multiply< mass, acceleration > force
< Represents an SI derived unit of angular jerk
Definition core.h:1344
make_dimension< power, std::ratio< 1 >, solid_angle, std::ratio<-1 > > radiant_intensity
< Represents an SI derived unit of magnetic field strength
Definition core.h:1365
dimension_divide< angular_velocity, time > angular_acceleration
< Represents an SI derived unit of acceleration
Definition core.h:1342
dimension_divide< force, area > pressure
< Represents an SI derived unit of volumetric flow rate
Definition core.h:1348
make_dimension< radiant_intensity, std::ratio< 1 >, length, std::ratio<-1 > > spectral_intensity
< Represents an SI derived unit of irradiance
Definition core.h:1368
dimension_divide< power, current > voltage
< Represents an SI derived unit of power
Definition core.h:1352
dimension_divide< charge, voltage > capacitance
< Represents an SI derived unit of voltage
Definition core.h:1353
dimension_multiply< time, current > charge
< Represents an SI derived unit of pressure
Definition core.h:1349
make_dimension< mass, std::ratio< 1 >, time, std::ratio<-2 >, current, std::ratio<-1 > > magnetic_field_strength
< Represents an SI derived unit of substance concentration
Definition core.h:1364
dimension_divide< energy, current > magnetic_flux
< Represents an SI derived unit of conductance
Definition core.h:1356
dimension_multiply< force, length > energy
< Represents an SI derived unit of charge
Definition core.h:1350
make_dimension< power, std::ratio< 1 >, volume, std::ratio<-1 > > spectral_irradiance
< Represents an SI derived unit of spectral intensity
Definition core.h:1371
make_dimension< volume, std::ratio<-1 > > concentration
< Represents an SI derived unit of energy density
Definition core.h:1380
make_dimension< data_tag > data
< Represents a unit of concentration
Definition core.h:1381
dimension_multiply< solid_angle, luminous_intensity > luminous_flux
< Represents an SI derived unit of inductance
Definition core.h:1358
dimension_divide< length, time > velocity
< Represents an SI derived unit of frequency
Definition core.h:1339
dimension_divide< data, time > data_transfer_rate
< Represents a unit of data size
Definition core.h:1382
dimension_pow< length, std::ratio< 2 > > area
< Represents an SI derived unit of force
Definition core.h:1345
make_dimension< radiant_intensity, std::ratio< 1 >, volume, std::ratio<-1 > > spectral_radiance
< Represents an SI derived unit of spectral flux
Definition core.h:1370
make_dimension< luminous_flux, std::ratio< 1 >, length, std::ratio<-2 > > illuminance
< Represents an SI derived unit of luminous flux
Definition core.h:1359
make_dimension< time, std::ratio<-1 > > frequency
< Represents an SI derived unit of solid angle
Definition core.h:1338
make_dimension< power, std::ratio< 1 >, area, std::ratio<-1 > > irradiance
< Represents an SI derived unit of radiance
Definition core.h:1367
dimension_divide< angle, time > angular_velocity
< Represents an SI derived unit of velocity
Definition core.h:1340
make_dimension< energy, std::ratio< 1 >, volume, std::ratio<-1 > > energy_density
< Represents an SI derived unit of kinematic viscosity
Definition core.h:1379
make_dimension< length, std::ratio< 1 >, time, std::ratio<-3 > > jerk
< Represents an SI derived unit of spectral irradiance
Definition core.h:1374
dimension_divide< volume, time > volume_flow_rate
< Represents an SI derived unit of volume
Definition core.h:1347
dimension_divide< angular_acceleration, time > angular_jerk
< Represents an SI derived unit of angular acceleration
Definition core.h:1343
dimension_multiply< force, length > torque
< Represents an SI derived unit of jerk
Definition core.h:1375
typename units::detail::dimension_of_impl< U >::type dimension_of_t
Names the dimension_t of a conversion_factor.
Definition core.h:1457
Unit Conversion Library namespace.
Definition units.h:108
std::expected< any_unit, deserialize_error > deserialize(std::span< const std::byte > bytes)
decodes a self-describing byte stream into an erased quantity
Definition serialization.h:948
std::istream & operator>>(std::istream &is, any_unit &value)
reads one self-describing erased quantity from a binary stream (classic stream-extraction form)
Definition serialization.h:1124
any_unit serialize(const Unit &quantity)
serializes a quantity to a self-describing, erased any_unit
Definition serialization.h:915
deserialize_error
the reasons a deserialize can fail
Definition serialization.h:106
@ bad_version
the stream's format version is not understood
Definition serialization.h:108
@ malformed
the bytes are complete but encode an invalid record (e.g. a zero exponent denominator)
Definition serialization.h:112
@ truncated
the byte range ended before a complete quantity was read
Definition serialization.h:107
@ lossy_target
the value cannot be represented in the requested underlying type without loss
Definition serialization.h:111
@ unknown_base_dimension
the stream names a base-dimension code this build does not know
Definition serialization.h:110
@ dimension_mismatch
the stream's dimension does not match the requested target
Definition serialization.h:109
affine::basic_kind< Tag, U > kind
The preferred user-facing spelling of a string-tagged quantity kind: units::kind<"radial",...
Definition kind.h:1214
std::vector< std::byte > encode(const unit_identity &identity, double base)
encodes a dimension identity and SI-base magnitude to the self-describing byte stream
Definition serialization.h:365
void put_uvarint(std::vector< std::byte > &out, std::uint64_t value)
appends an unsigned integer to a byte buffer as an LEB128 varint
Definition serialization.h:135
consteval void fill_terms(std::array< dimension_term, N > &out, std::size_t at)
writes a term per base dimension of a dimension_t<...> list into a fixed span
Definition serialization.h:273
unit< conversion_factor< std::ratio< 1 >, Dim >, double > canonical_unit_t
the canonical SI base unit of a dimension (ratio 1, no pi, no translation)
Definition serialization.h:333
unit_identity identity_of()
the dimension signature of a unit type, as the runtime (vector-backed) identity
Definition serialization.h:324
std::tuple< dimension::length, dimension::mass, dimension::time, dimension::current, dimension::temperature, dimension::substance, dimension::luminous_intensity, dimension::angle, dimension::data, dimension::solid_angle, dimension::frequency, dimension::velocity, dimension::angular_velocity, dimension::angular_acceleration, dimension::angular_jerk, dimension::acceleration, dimension::force, dimension::area, dimension::volume, dimension::volume_flow_rate, dimension::pressure, dimension::charge, dimension::energy, dimension::power, dimension::voltage, dimension::capacitance, dimension::impedance, dimension::conductance, dimension::magnetic_flux, dimension::inductance, dimension::luminous_flux, dimension::illuminance, dimension::luminance, dimension::radioactivity, dimension::substance_mass, dimension::substance_concentration, dimension::magnetic_field_strength, dimension::radiant_intensity, dimension::radiance, dimension::irradiance, dimension::spectral_intensity, dimension::spectral_flux, dimension::spectral_radiance, dimension::spectral_irradiance, dimension::jerk, dimension::torque, dimension::density, dimension::energy_density, dimension::concentration, dimension::data_transfer_rate, dimension::dynamic_viscosity, dimension::kinematic_viscosity > builtin_dimensions
The library's known dimensions, offered to visit as the default candidate set so a stream of any buil...
Definition serialization.h:74
void put_svarint(std::vector< std::byte > &out, std::int64_t value)
appends a signed integer to a byte buffer as a zig-zag LEB128 varint
Definition serialization.h:154
constexpr std::uint64_t name_hash(std::string_view name) noexcept
FNV-1a 64-bit hash of a base dimension's name.
Definition serialization.h:92
value_kind
header byte layout: [ valueKind:2 | fracExp:1 | reserved:5 ]
Definition serialization.h:346
@ ivarint
value is an integer in SI base, zig-zag varint
Definition serialization.h:347
@ f32
value is an exact 32-bit float
Definition serialization.h:348
@ f64
value is a 64-bit double
Definition serialization.h:349
bool get_uvarint(const std::byte *&cursor, const std::byte *end, std::uint64_t &value)
reads an LEB128 unsigned varint from a byte cursor
Definition serialization.h:168
bool get_svarint(const std::byte *&cursor, const std::byte *end, std::int64_t &value)
reads a zig-zag LEB128 signed varint from a byte cursor
Definition serialization.h:194
consteval std::size_t dimension_arity()
the number of base-dimension terms in a dimension_t<...> list
Definition serialization.h:253
the compile-time signature of a unit as a fixed-size, sorted array of terms
Definition serialization.h:289
one base-dimension term of a signature: which base dimension (by name-hash), and its rational exponen...
Definition serialization.h:212
std::uint64_t hash
FNV-1a hash of the base dimension's name; the wire identity.
Definition serialization.h:213
std::int64_t den
exponent denominator (1 for the common integer-exponent case)
Definition serialization.h:215
std::int64_t num
exponent numerator
Definition serialization.h:214
the runtime identity of a quantity's dimension — the set of nonzero base-dimension terms
Definition serialization.h:222
bool operator==(const unit_identity &other) const noexcept
dimension-signature equality
Definition serialization.h:232