Unit Conversion and Dimensional Analysis Library 3.6.1
A compile-time, header-only C++23 dimensional-analysis library
Loading...
Searching...
No Matches
kind.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//
30// ATTRIBUTION:
31// Parts of this work have been adapted from:
32// http://stackoverflow.com/questions/35069778/create-comparison-trait-for-template-classes-whose-parameters-are-in-a-different
33// http://stackoverflow.com/questions/28253399/check-traits-for-all-variadic-template-arguments/28253503
34// http://stackoverflow.com/questions/36321295/rational-approximation-of-square-root-of-stdratio-at-compile-time?noredirect=1#comment60266601_36321295
35// https://github.com/swatanabe/cppnow17-units
36//
37//--------------------------------------------------------------------------------------------------
38//
43//
44//--------------------------------------------------------------------------------------------------
45
46#pragma once
47
48#ifndef UNIT_KIND_H
49#define UNIT_KIND_H
50
51#include <units/core.h>
52
53#include <algorithm>
54#include <cstddef>
55#include <ostream>
56#include <string>
57
58namespace units
59{
60 //----------------------------------------------------------------------------------------------------------------------
61 // AFFINE POINT / DELTA WRAPPERS
62 //----------------------------------------------------------------------------------------------------------------------
63 // Opt-in wrappers that make the point-vs-amount distinction explicit in the type. `absolute<U>` is a POINT on
64 // a scale (it carries the unit's datum — 0 degC is 273.15 K); `delta<U>` is an AMOUNT (offset-free — a 10 degC
65 // delta is a 10 K delta). Reach for these where the distinction matters (temperatures, epochs vs durations,
66 // absolute vs gauge pressure, positions vs displacements); for a non-affine unit the datum is zero, so the two
67 // coincide numerically. The type algebra:
68 // absolute - absolute -> delta (the datum offsets cancel)
69 // absolute +/- delta -> absolute (move the point by a relative amount)
70 // delta +/- delta -> delta
71 // delta * / scalar -> delta
72 // absolute + absolute -> ill-formed
73 //
74 // The wrappers exist only where <units/kind.h> is included, so a user's own `absolute`/`delta` names are
75 // undisturbed otherwise. They live in `inline namespace affine`, so `units::absolute` and
76 // `units::affine::absolute` name the same type; the `affine::` qualifier disambiguates when needed.
77
78 namespace detail
79 {
83 template<UnitType U>
85 typename traits::conversion_factor_traits<typename traits::unit_traits<U>::conversion_factor>::dimension_type,
86 typename traits::conversion_factor_traits<typename traits::unit_traits<U>::conversion_factor>::pi_exponent_ratio, std::ratio<0>>>,
87 typename traits::unit_traits<U>::underlying_type, typename traits::unit_traits<U>::numerical_scale_type>;
88
107 template<UnitType From, UnitType To>
109 is_losslessly_convertible_unit<From, To> &&
110 (std::is_floating_point_v<typename traits::unit_traits<To>::underlying_type> ||
111 std::ratio_equal_v<typename traits::conversion_factor_traits<typename traits::unit_traits<From>::conversion_factor>::translation_ratio,
112 typename traits::conversion_factor_traits<typename traits::unit_traits<To>::conversion_factor>::translation_ratio>);
113
116 template<UnitType U, UnitType V>
117 using absolute_result_unit_t = std::conditional_t<is_losslessly_point_convertible_unit<V, U>, U, traits::replace_underlying_t<U, floating_point_promotion_t<typename traits::unit_traits<U>::underlying_type>>>;
118
123 template<UnitType U, UnitType V>
124 using delta_result_unit_t = std::conditional_t<is_losslessly_convertible_unit<delta_unit_t<V>, delta_unit_t<U>>, U,
125 traits::replace_underlying_t<U, floating_point_promotion_t<typename traits::unit_traits<U>::underlying_type>>>;
126 } // namespace detail
127
128 inline namespace affine
129 {
130 template<UnitType U>
131 class absolute;
132 template<UnitType U>
133 class delta;
134 template<std::size_t N>
135 struct fixed_string;
136 template<fixed_string Tag, UnitType U>
137 class basic_kind;
138 // DOXYGEN IGNORE
140 namespace wrap_detail
141 {
142 // `to<Target>()` on every wrapper follows ONE rule: the TARGET type decides the result. A plain-unit
143 // target unwraps to that plain unit (applying the wrapper's own conversion rule); a WRAPPER target keeps
144 // that wrapper. These detect whether a `to<>` target is a wrapper (so the method returns the wrapper) or
145 // a plain unit (so it unwraps). Declared before the wrappers so their `to<>` can use them.
146 template<class T>
147 inline constexpr bool is_wrapper = false;
148 template<UnitType U>
149 inline constexpr bool is_wrapper<absolute<U>> = true;
150 template<UnitType U>
151 inline constexpr bool is_wrapper<delta<U>> = true;
152 template<fixed_string Tag, UnitType U>
153 inline constexpr bool is_wrapper<basic_kind<Tag, U>> = true;
154
156 template<class Wrapper>
157 using wrapper_unit_t = typename Wrapper::unit_type;
158
163 template<class Wrapper>
164 constexpr auto unwrap(const Wrapper& w) noexcept
165 {
166 return w.template to<typename Wrapper::unit_type>();
167 }
168
171 template<fixed_string...>
172 inline constexpr bool dependent_false = false;
173 template<class...>
174 inline constexpr bool dependent_false_t = false;
175
179 template<class LhsUnit, class RhsUnit>
180 constexpr std::partial_ordering order(const LhsUnit& lhs, const RhsUnit& rhs) noexcept
181 {
182 if (lhs < rhs)
183 return std::partial_ordering::less;
184 if (rhs < lhs)
185 return std::partial_ordering::greater;
186 if (lhs == rhs)
187 return std::partial_ordering::equivalent;
188 return std::partial_ordering::unordered;
189 }
190 } // namespace wrap_detail // END DOXYGEN IGNORE
192 } // inline namespace affine (forward declarations only)
193
194 // The wrapper traits + concepts are defined here — after the forward declarations, before the class bodies — so
195 // each wrapper's `to<>` overloads can constrain on `AbsoluteType`/`DeltaType`/`KindType`.
196 namespace traits
197 { // DOXYGEN IGNORE
199 namespace detail
200 {
201 template<class T>
202 struct is_absolute_impl : std::false_type
203 {
204 };
205 template<UnitType U>
206 struct is_absolute_impl<units::affine::absolute<U>> : std::true_type
207 {
208 };
209
210 template<class T>
211 struct is_delta_impl : std::false_type
212 {
213 };
214 template<UnitType U>
215 struct is_delta_impl<units::affine::delta<U>> : std::true_type
216 {
217 };
218
219 template<class T>
220 struct is_kind_impl : std::false_type
221 {
222 };
223 template<units::affine::fixed_string Tag, UnitType U>
224 struct is_kind_impl<units::affine::basic_kind<Tag, U>> : std::true_type
225 {
226 };
227 } // namespace detail // END DOXYGEN IGNORE
229
235 template<class T>
236 struct is_absolute : detail::is_absolute_impl<std::remove_cv_t<T>>
237 {
238 };
239
240 template<class T>
241 inline constexpr bool is_absolute_v = is_absolute<T>::value;
242
248 template<class T>
249 struct is_delta : detail::is_delta_impl<std::remove_cv_t<T>>
250 {
251 };
252
253 template<class T>
254 inline constexpr bool is_delta_v = is_delta<T>::value;
255
261 template<class T>
262 struct is_kind : detail::is_kind_impl<std::remove_cv_t<T>>
263 {
264 };
265
266 template<class T>
267 inline constexpr bool is_kind_v = is_kind<T>::value;
268 } // namespace traits
269
274 template<class T>
275 concept AbsoluteType = traits::is_absolute_v<T>;
276
281 template<class T>
282 concept DeltaType = traits::is_delta_v<T>;
283
288 template<class T>
289 concept KindType = traits::is_kind_v<T>;
290
291 inline namespace affine
292 {
293 // ----------------------------------------------------------------------------
294 // CLASS absolute
295 // ----------------------------------------------------------------------------
298 // ----------------------------------------------------------------------------
299 template<UnitType U>
301 {
302 public:
303 using unit_type = U;
304 using underlying_type = typename traits::unit_traits<U>::underlying_type;
305
306 constexpr absolute() noexcept = default;
307 constexpr explicit absolute(const U& point) noexcept : m_point(point) {}
308
310 constexpr explicit absolute(underlying_type value) noexcept : m_point(value) {}
311
313 constexpr auto value() const noexcept { return m_point.value(); }
315 constexpr auto raw() const noexcept { return m_point.raw(); }
317 constexpr auto to_linearized() const noexcept { return m_point.to_linearized(); }
319 [[nodiscard]] constexpr const char* name() const noexcept { return m_point.name(); }
320 [[nodiscard]] constexpr const char* abbreviation() const noexcept { return m_point.abbreviation(); }
321
324 template<ArithmeticType Arithmetic>
325 constexpr Arithmetic to() const noexcept
326 {
327 return m_point.template to<Arithmetic>();
328 }
329
333 template<UnitType PlainTarget>
334 requires traits::is_same_dimension_unit_v<U, PlainTarget>
335 constexpr PlainTarget to() const noexcept
336 {
337 return PlainTarget(m_point); // the datum participates in the unit conversion
338 }
339
343 template<AbsoluteType WrapperTarget>
344 requires traits::is_same_dimension_unit_v<U, typename WrapperTarget::unit_type>
345 constexpr WrapperTarget to() const noexcept
346 {
347 return WrapperTarget(typename WrapperTarget::unit_type(m_point));
348 }
349
350 private:
351 U m_point{};
352 };
353
354 // ----------------------------------------------------------------------------
355 // CLASS delta
356 // ----------------------------------------------------------------------------
359 // ----------------------------------------------------------------------------
360 template<UnitType U>
361 class delta
362 {
363 public:
364 using unit_type = U;
365 using underlying_type = typename traits::unit_traits<U>::underlying_type;
366
367 constexpr delta() noexcept = default;
368 constexpr explicit delta(const U& amount) noexcept : m_amount(amount) {}
369
371 constexpr explicit delta(underlying_type value) noexcept : m_amount(value) {}
372
374 constexpr auto value() const noexcept { return m_amount.value(); }
376 constexpr auto raw() const noexcept { return m_amount.raw(); }
378 constexpr auto to_linearized() const noexcept { return m_amount.to_linearized(); }
380 [[nodiscard]] constexpr const char* name() const noexcept { return m_amount.name(); }
381 [[nodiscard]] constexpr const char* abbreviation() const noexcept { return m_amount.abbreviation(); }
382
385 template<ArithmeticType Arithmetic>
386 constexpr Arithmetic to() const noexcept
387 {
388 return m_amount.template to<Arithmetic>();
389 }
390
396 template<UnitType PlainTarget>
397 requires traits::is_same_dimension_unit_v<U, PlainTarget>
398 constexpr PlainTarget to() const noexcept
399 {
400 static_assert(units::detail::is_losslessly_convertible_unit<units::detail::delta_unit_t<U>, units::detail::delta_unit_t<PlainTarget>>,
401 "units::delta::to<PlainTarget>(): converting this delta into PlainTarget would truncate an "
402 "integer value; convert to a floating-point delta first (e.g. delta<meters<double>>) if intended.");
403 return PlainTarget(scaled_to<PlainTarget>());
404 }
405
409 template<DeltaType WrapperTarget>
410 requires traits::is_same_dimension_unit_v<U, typename WrapperTarget::unit_type>
411 constexpr WrapperTarget to() const noexcept
412 {
413 using V = typename WrapperTarget::unit_type;
414 static_assert(units::detail::is_losslessly_convertible_unit<units::detail::delta_unit_t<U>, units::detail::delta_unit_t<V>>,
415 "units::delta::to<delta<V>>(): converting this delta into V would truncate an integer value; "
416 "convert to a floating-point delta first (e.g. delta<meters<double>>) if intended.");
417 return WrapperTarget(scaled_to<V>());
418 }
419
420 private:
421 U m_amount{};
422
424 template<UnitType V>
425 constexpr V scaled_to() const noexcept
426 {
427 const units::detail::delta_unit_t<U> here(m_amount.raw());
428 const units::detail::delta_unit_t<V> there(here); // scale-only conversion (no translation on either)
429 return V(there.raw());
430 }
431 };
432
433 // ----------------------------------------------------------------------------
434 // CLASS fixed_string
435 // ----------------------------------------------------------------------------
442 // ----------------------------------------------------------------------------
443 template<std::size_t N>
444 struct fixed_string
445 {
446 char value[N]{};
447
448 constexpr fixed_string(const char (&str)[N]) noexcept { std::copy_n(str, N, value); }
449
451 template<std::size_t M>
452 constexpr bool operator==(const fixed_string<M>& rhs) const noexcept
453 {
454 if constexpr (N != M)
455 return false;
456 else
457 return std::equal(value, value + N, rhs.value);
458 }
459 };
460 template<std::size_t N>
461 fixed_string(const char (&)[N]) -> fixed_string<N>;
462
463 // ----------------------------------------------------------------------------
464 // CLASS kind
465 // ----------------------------------------------------------------------------
475 // ----------------------------------------------------------------------------
476 template<fixed_string Tag, UnitType U>
478 {
479 public:
480 using unit_type = U;
481 using underlying_type = typename traits::unit_traits<U>::underlying_type;
482
483 constexpr basic_kind() noexcept = default;
484
489 constexpr basic_kind(const U& value) noexcept : m_value(value) {}
490
493 constexpr explicit basic_kind(underlying_type value) noexcept : m_value(value) {}
494
496 constexpr basic_kind& operator=(const U& value) noexcept
497 {
498 m_value = value;
499 return *this;
500 }
501
503 static constexpr auto tag() noexcept { return Tag; }
505 constexpr auto value() const noexcept { return m_value.value(); }
507 constexpr auto raw() const noexcept { return m_value.raw(); }
509 constexpr auto to_linearized() const noexcept { return m_value.to_linearized(); }
512 [[nodiscard]] constexpr const char* abbreviation() const noexcept { return m_value.abbreviation(); }
514 [[nodiscard]] std::string name() const { return std::string(Tag.value).append(" ").append(m_value.name()); }
515
518 template<ArithmeticType Arithmetic>
519 constexpr Arithmetic to() const noexcept
520 {
521 return m_value.template to<Arithmetic>();
522 }
523
526 template<UnitType PlainTarget>
527 requires traits::is_same_dimension_unit_v<U, PlainTarget>
528 constexpr PlainTarget to() const noexcept
529 {
530 return PlainTarget(m_value);
531 }
532
536 template<KindType WrapperTarget>
537 requires(WrapperTarget::tag() == Tag && traits::is_same_dimension_unit_v<U, typename WrapperTarget::unit_type>)
538 constexpr WrapperTarget to() const noexcept
539 {
540 return WrapperTarget(typename WrapperTarget::unit_type(m_value));
541 }
542
543 private:
544 U m_value{};
545 };
546
547 //----------------------------------
548 // ABSOLUTE / DELTA OPERATORS
549 //----------------------------------
550
551 // The wrapper operators keep the LHS UNIT (the "LHS-unit tie-break") so `.value()` reads in the left
552 // operand's unit — a point difference `100 degC - 32 degF` is 100 celsius-degrees. Keeping the LHS unit
553 // would narrow when the LHS is a coarse integer that cannot hold the RHS losslessly, so in that case the
554 // underlying is promoted to floating point (an `absolute`
555 // operator uses `units::detail::absolute_result_unit_t`, datum-aware; a `delta` operator uses
556 // `units::detail::delta_result_unit_t`, scale-only); the result UNIT stays the LHS unit, only the underlying
557 // widens. This keeps `absolute<kilometers<int>> - absolute<meters<int>>` well-formed (a km delta with a
558 // promoted underlying) instead of hard-erroring in the conversion.
559
561 template<UnitType U, UnitType V>
562 requires traits::is_same_dimension_unit_v<U, V>
563 constexpr auto operator-(const absolute<U>& lhs, const absolute<V>& rhs) noexcept
564 {
565 // The result unit is the LHS unit (underlying promoted only if keeping U's underlying would narrow the
566 // RHS). The rhs point is converted into that unit AFFINELY (offset applied), then subtracted; the datum
567 // cancels, leaving an offset-free delta expressed in the LHS unit.
569 return delta<R>(R(R(wrap_detail::unwrap(lhs)).raw() - R(wrap_detail::unwrap(rhs)).raw()));
570 }
571
575 template<UnitType U, UnitType V>
576 requires traits::is_same_dimension_unit_v<U, V>
577 constexpr auto operator+(const absolute<U>& lhs, const delta<V>& rhs) noexcept
578 {
580 return absolute<R>(R(R(wrap_detail::unwrap(lhs)).raw() + rhs.template to<R>().raw()));
581 }
582
585 template<UnitType U, UnitType V>
586 requires traits::is_same_dimension_unit_v<V, U>
587 constexpr auto operator+(const delta<V>& lhs, const absolute<U>& rhs) noexcept
588 {
589 return rhs + lhs;
590 }
591
593 template<UnitType U, UnitType V>
594 requires traits::is_same_dimension_unit_v<U, V>
595 constexpr auto operator-(const absolute<U>& lhs, const delta<V>& rhs) noexcept
596 {
598 return absolute<R>(R(R(wrap_detail::unwrap(lhs)).raw() - rhs.template to<R>().raw()));
599 }
600
602 template<UnitType U, UnitType V>
603 requires traits::is_same_dimension_unit_v<U, V>
604 constexpr auto operator+(const delta<U>& lhs, const delta<V>& rhs) noexcept
605 {
607 return delta<R>(R(lhs.template to<R>().raw() + rhs.template to<R>().raw()));
608 }
609
611 template<UnitType U, UnitType V>
612 requires traits::is_same_dimension_unit_v<U, V>
613 constexpr auto operator-(const delta<U>& lhs, const delta<V>& rhs) noexcept
614 {
616 return delta<R>(R(lhs.template to<R>().raw() - rhs.template to<R>().raw()));
617 }
618
620 template<UnitType U>
621 constexpr delta<U> operator-(const delta<U>& d) noexcept
622 {
623 return delta<U>(U(-wrap_detail::unwrap(d).raw()));
624 }
625
629 template<UnitType U, ArithmeticType T>
630 constexpr auto operator*(const delta<U>& lhs, T rhs) noexcept
631 {
632 using ScaledUnit = decltype(wrap_detail::unwrap(lhs) * rhs);
633 return delta<ScaledUnit>(wrap_detail::unwrap(lhs) * rhs);
634 }
635 template<UnitType U, ArithmeticType T>
636 constexpr auto operator*(T lhs, const delta<U>& rhs) noexcept
637 {
638 return rhs * lhs;
639 }
640
642 template<UnitType U, ArithmeticType T>
643 constexpr auto operator/(const delta<U>& lhs, T rhs) noexcept
644 {
645 using ScaledUnit = decltype(wrap_detail::unwrap(lhs) / rhs);
646 return delta<ScaledUnit>(wrap_detail::unwrap(lhs) / rhs);
647 }
648
652 template<UnitType U, UnitType V>
653 requires traits::is_same_dimension_unit_v<U, V>
654 constexpr absolute<U>& operator+=(absolute<U>& lhs, const delta<V>& rhs) noexcept
655 {
656 lhs = absolute<U>(U(wrap_detail::unwrap(lhs).raw() + rhs.template to<U>().raw()));
657 return lhs;
658 }
659 template<UnitType U, UnitType V>
660 requires traits::is_same_dimension_unit_v<U, V>
661 constexpr absolute<U>& operator-=(absolute<U>& lhs, const delta<V>& rhs) noexcept
662 {
663 lhs = absolute<U>(U(wrap_detail::unwrap(lhs).raw() - rhs.template to<U>().raw()));
664 return lhs;
665 }
666
668 template<UnitType U, UnitType V>
669 requires traits::is_same_dimension_unit_v<U, V>
670 constexpr delta<U>& operator+=(delta<U>& lhs, const delta<V>& rhs) noexcept
671 {
672 lhs = delta<U>(U(wrap_detail::unwrap(lhs).raw() + rhs.template to<U>().raw()));
673 return lhs;
674 }
675 template<UnitType U, UnitType V>
676 requires traits::is_same_dimension_unit_v<U, V>
677 constexpr delta<U>& operator-=(delta<U>& lhs, const delta<V>& rhs) noexcept
678 {
679 lhs = delta<U>(U(wrap_detail::unwrap(lhs).raw() - rhs.template to<U>().raw()));
680 return lhs;
681 }
682
686 template<UnitType U, ArithmeticType T>
687 constexpr delta<U>& operator*=(delta<U>& lhs, T rhs) noexcept
688 {
689 lhs = delta<U>(U(wrap_detail::unwrap(lhs).raw() * rhs));
690 return lhs;
691 }
692 template<UnitType U, ArithmeticType T>
693 constexpr delta<U>& operator/=(delta<U>& lhs, T rhs) noexcept
694 {
695 lhs = delta<U>(U(wrap_detail::unwrap(lhs).raw() / rhs));
696 return lhs;
697 }
698
699 //----------------------------------
700 // ABSOLUTE / DELTA COMPARISONS
701 //----------------------------------
702
703 // Comparisons reconcile to the common (finer) unit — never narrowing — so a mixed integer comparison
704 // (e.g. delta<kilometers<int>> vs delta<meters<int>>) is well-formed rather than hard-erroring. A
705 // comparison has no `.value()` to keep intuitive, so the common-unit reconciliation (which cannot lose an
706 // equality) is the right choice here even though the arithmetic operators keep the LHS unit.
707
708 // Point/amount comparisons delegate to the WRAPPED plain units' own comparison operators, so they inherit
709 // the core's value-based (signedness-safe) integer comparison: a signed-rep and an unsigned-rep wrapper of
710 // the same dimension compare by mathematical value, not by unsigned wraparound.
711
713 template<UnitType U, UnitType V>
714 requires traits::is_same_dimension_unit_v<U, V>
715 constexpr bool operator==(const absolute<U>& lhs, const absolute<V>& rhs) noexcept
716 {
717 return wrap_detail::unwrap(lhs) == wrap_detail::unwrap(rhs);
718 }
719 template<UnitType U, UnitType V>
720 requires traits::is_same_dimension_unit_v<U, V>
721 constexpr std::partial_ordering operator<=>(const absolute<U>& lhs, const absolute<V>& rhs) noexcept
722 {
723 return wrap_detail::order(wrap_detail::unwrap(lhs), wrap_detail::unwrap(rhs));
724 }
725
726 // Reconcile two deltas to the common UNIT (scale only) while keeping EACH side's own underlying type — so a
727 // signed and an unsigned delta reach the plain-unit comparison in their own representations, and the core's
728 // value-based comparison orders them by mathematical value rather than wrapping the negative side.
730 template<UnitType U, UnitType V>
731 requires traits::is_same_dimension_unit_v<U, V>
732 constexpr bool operator==(const delta<U>& lhs, const delta<V>& rhs) noexcept
733 {
734 using C = std::common_type_t<U, V>;
735 using CLhs = traits::replace_underlying_t<C, typename traits::unit_traits<U>::underlying_type>;
736 using CRhs = traits::replace_underlying_t<C, typename traits::unit_traits<V>::underlying_type>;
737 return lhs.template to<CLhs>() == rhs.template to<CRhs>();
738 }
739 template<UnitType U, UnitType V>
740 requires traits::is_same_dimension_unit_v<U, V>
741 constexpr std::partial_ordering operator<=>(const delta<U>& lhs, const delta<V>& rhs) noexcept
742 {
743 using C = std::common_type_t<U, V>;
744 using CLhs = traits::replace_underlying_t<C, typename traits::unit_traits<U>::underlying_type>;
745 using CRhs = traits::replace_underlying_t<C, typename traits::unit_traits<V>::underlying_type>;
746 return wrap_detail::order(lhs.template to<CLhs>(), rhs.template to<CRhs>());
747 }
748
749 //----------------------------------
750 // ABSOLUTE / DELTA FORMATTING
751 //----------------------------------
752
753 // A wrapper prints its wrapped quantity with a leading role marker so a point and an amount are visually
754 // distinct: `delta ` (an ASCII "delta " prefix) marks a delta; a point prints bare (it is the common,
755 // unmarked case). The numeric+abbreviation body forwards to the wrapped unit's own inserter, so the
756 // wrapper never re-implements formatting.
757
759 template<UnitType U>
760 std::ostream& operator<<(std::ostream& os, const absolute<U>& obj)
761 {
762 return os << wrap_detail::unwrap(obj);
763 }
764
766 template<UnitType U>
767 std::ostream& operator<<(std::ostream& os, const delta<U>& obj)
768 {
769 return os << "delta " << wrap_detail::unwrap(obj);
770 }
771
773 template<UnitType U>
774 std::string to_string(const absolute<U>& obj)
775 {
776 return units::to_string(wrap_detail::unwrap(obj));
777 }
778
780 template<UnitType U>
781 std::string to_string(const delta<U>& obj)
782 {
783 return std::string("delta ").append(units::to_string(wrap_detail::unwrap(obj)));
784 }
785
786 //----------------------------------
787 // ABSOLUTE / DELTA MATH
788 //----------------------------------
789
790 // A delta is an AMOUNT, so magnitude/extremum functions are meaningful on it (a point has a datum, so its
791 // magnitude is not — but two points still order, so min/max of points ARE meaningful). Each forwards to
792 // the wrapped unit's own cmath analog, so the result underlying promotes exactly as the plain unit does.
793
795 template<UnitType U>
796 constexpr auto abs(const delta<U>& d) noexcept
797 {
798 using R = detail::floating_point_promotion_t<U>;
799 return delta<R>(units::abs(R(wrap_detail::unwrap(d))));
800 }
801
803 template<UnitType U, UnitType V>
804 requires traits::is_same_dimension_unit_v<U, V>
805 constexpr auto min(const delta<U>& lhs, const delta<V>& rhs) noexcept
806 {
808 const delta<R> a = lhs.template to<delta<R>>();
809 const delta<R> b = rhs.template to<delta<R>>();
810 return a < b ? a : b;
811 }
812
814 template<UnitType U, UnitType V>
815 requires traits::is_same_dimension_unit_v<U, V>
816 constexpr auto max(const delta<U>& lhs, const delta<V>& rhs) noexcept
817 {
819 const delta<R> a = lhs.template to<delta<R>>();
820 const delta<R> b = rhs.template to<delta<R>>();
821 return a > b ? a : b;
822 }
823
825 template<UnitType U, UnitType V, UnitType W>
826 requires(traits::is_same_dimension_unit_v<U, V> && traits::is_same_dimension_unit_v<U, W>)
827 constexpr auto clamp(const delta<U>& value, const delta<V>& lo, const delta<W>& hi) noexcept
828 {
829 return affine::min(affine::max(value, lo), hi);
830 }
831
833 template<UnitType U, UnitType V>
834 requires traits::is_same_dimension_unit_v<U, V>
835 constexpr auto min(const absolute<U>& lhs, const absolute<V>& rhs) noexcept
836 {
838 const absolute<R> a = lhs.template to<absolute<R>>();
839 const absolute<R> b = rhs.template to<absolute<R>>();
840 return a < b ? a : b;
841 }
842
844 template<UnitType U, UnitType V>
845 requires traits::is_same_dimension_unit_v<U, V>
846 constexpr auto max(const absolute<U>& lhs, const absolute<V>& rhs) noexcept
847 {
849 const absolute<R> a = lhs.template to<absolute<R>>();
850 const absolute<R> b = rhs.template to<absolute<R>>();
851 return a > b ? a : b;
852 }
853
855 template<UnitType U, UnitType V, UnitType W>
856 requires(traits::is_same_dimension_unit_v<U, V> && traits::is_same_dimension_unit_v<U, W>)
857 constexpr auto clamp(const absolute<U>& value, const absolute<V>& lo, const absolute<W>& hi) noexcept
858 {
859 return affine::min(affine::max(value, lo), hi);
860 }
861
862 //----------------------------------
863 // ABSOLUTE / DELTA MISUSE DIAGNOSTICS (readable, not a candidate wall)
864 //----------------------------------
865 // The common naive fumbles get a one-line library message instead of an overload-resolution wall. Each
866 // returns a real value so the compiler must instantiate the body (fires the static_assert on MSVC too).
867
869 template<UnitType U, ArithmeticType T>
870 constexpr auto operator*(const absolute<U>& lhs, T) noexcept
871 {
872 static_assert(wrap_detail::dependent_false_t<U>,
873 "units::absolute: a point cannot be scaled (a point has no magnitude). Take the difference of two "
874 "points for a delta, or scale a delta.");
875 return lhs;
876 }
877 template<UnitType U, ArithmeticType T>
878 constexpr auto operator*(T, const absolute<U>& rhs) noexcept
879 {
880 static_assert(wrap_detail::dependent_false_t<U>,
881 "units::absolute: a point cannot be scaled (a point has no magnitude). Scale a delta instead.");
882 return rhs;
883 }
884 template<UnitType U, ArithmeticType T>
885 constexpr auto operator/(const absolute<U>& lhs, T) noexcept
886 {
887 static_assert(wrap_detail::dependent_false_t<U>,
888 "units::absolute: a point cannot be divided by a number (a point has no magnitude). Divide a delta.");
889 return lhs;
890 }
892 template<UnitType U, UnitType V>
893 requires traits::is_same_dimension_unit_v<U, V>
894 constexpr auto operator/(const absolute<U>& lhs, const absolute<V>&) noexcept
895 {
896 static_assert(wrap_detail::dependent_false_t<U>,
897 "units::absolute: a point cannot be divided by a point (there is no ratio of two positions). "
898 "Take the difference of two points for a delta.");
899 return lhs;
900 }
901
904 template<UnitType U, UnitType V>
905 requires traits::is_same_dimension_unit_v<U, V>
906 constexpr auto operator+(const absolute<U>& lhs, const absolute<V>&) noexcept
907 {
908 static_assert(wrap_detail::dependent_false_t<U>,
909 "units::absolute: cannot add two points (the sum of two positions has no meaning). Subtract them "
910 "for a delta, or add a delta to move a point.");
911 return lhs;
912 }
913
916 template<UnitType U, ArithmeticType T>
917 constexpr auto operator+(const delta<U>& lhs, T) noexcept
918 {
919 static_assert(wrap_detail::dependent_false_t<U>,
920 "units::delta: cannot add a bare number to a delta. Wrap the number in a delta of the same unit "
921 "(e.g. delta<meters<double>>(3)).");
922 return lhs;
923 }
924 template<UnitType U, ArithmeticType T>
925 constexpr auto operator-(const delta<U>& lhs, T) noexcept
926 {
927 static_assert(wrap_detail::dependent_false_t<U>,
928 "units::delta: cannot subtract a bare number from a delta. Wrap the number in a delta of the same "
929 "unit.");
930 return lhs;
931 }
932 template<UnitType U, ArithmeticType T>
933 constexpr auto operator+(const absolute<U>& lhs, T) noexcept
934 {
935 static_assert(wrap_detail::dependent_false_t<U>,
936 "units::absolute: cannot add a bare number to a point. Add a delta of the same unit to move the "
937 "point (e.g. point + delta<meters<double>>(3)).");
938 return lhs;
939 }
940
941 //----------------------------------
942 // KIND (TAGGED) OPERATORS
943 //----------------------------------
944
945 // A `kind` interoperates ONLY with the SAME tag: every binary operator is constrained on `Tag1 == Tag2`, so
946 // two different kinds (radial vs. straight distance, torque vs. energy) do not add, subtract, or compare —
947 // mixing them is ill-formed. The result keeps the LHS unit (the same tie-break as the other wrappers),
948 // promoting the underlying only when the RHS would narrow it; the tag is preserved.
949
951 template<fixed_string Tag, UnitType U, UnitType V>
952 requires traits::is_same_dimension_unit_v<U, V>
953 constexpr auto operator+(const basic_kind<Tag, U>& lhs, const basic_kind<Tag, V>& rhs) noexcept
954 {
956 return basic_kind<Tag, R>(R(R(wrap_detail::unwrap(lhs)).raw() + R(wrap_detail::unwrap(rhs)).raw()));
957 }
958
960 template<fixed_string Tag, UnitType U, UnitType V>
961 requires traits::is_same_dimension_unit_v<U, V>
962 constexpr auto operator-(const basic_kind<Tag, U>& lhs, const basic_kind<Tag, V>& rhs) noexcept
963 {
965 return basic_kind<Tag, R>(R(R(wrap_detail::unwrap(lhs)).raw() - R(wrap_detail::unwrap(rhs)).raw()));
966 }
967
969 template<fixed_string Tag, UnitType U>
971 {
972 return basic_kind<Tag, U>(U(-wrap_detail::unwrap(k).raw()));
973 }
974
976 template<fixed_string Tag, UnitType U, ArithmeticType T>
977 constexpr auto operator*(const basic_kind<Tag, U>& lhs, T rhs) noexcept
978 {
979 using ScaledUnit = decltype(wrap_detail::unwrap(lhs) * rhs);
980 return basic_kind<Tag, ScaledUnit>(wrap_detail::unwrap(lhs) * rhs);
981 }
982 template<fixed_string Tag, UnitType U, ArithmeticType T>
983 constexpr auto operator*(T lhs, const basic_kind<Tag, U>& rhs) noexcept
984 {
985 return rhs * lhs;
986 }
987
989 template<fixed_string Tag, UnitType U, ArithmeticType T>
990 constexpr auto operator/(const basic_kind<Tag, U>& lhs, T rhs) noexcept
991 {
992 using ScaledUnit = decltype(wrap_detail::unwrap(lhs) / rhs);
993 return basic_kind<Tag, ScaledUnit>(wrap_detail::unwrap(lhs) / rhs);
994 }
995
998 template<fixed_string Tag, UnitType U, UnitType V>
999 requires traits::is_same_dimension_unit_v<U, V>
1000 constexpr auto operator/(const basic_kind<Tag, U>& lhs, const basic_kind<Tag, V>& rhs) noexcept
1001 {
1002 return wrap_detail::unwrap(lhs) / wrap_detail::unwrap(rhs);
1003 }
1004
1006 template<fixed_string Tag, UnitType U, ArithmeticType T>
1007 constexpr basic_kind<Tag, U>& operator*=(basic_kind<Tag, U>& lhs, T rhs) noexcept
1008 {
1009 lhs = basic_kind<Tag, U>(U(lhs.raw() * rhs));
1010 return lhs;
1011 }
1012 template<fixed_string Tag, UnitType U, ArithmeticType T>
1013 constexpr basic_kind<Tag, U>& operator/=(basic_kind<Tag, U>& lhs, T rhs) noexcept
1014 {
1015 lhs = basic_kind<Tag, U>(U(lhs.raw() / rhs));
1016 return lhs;
1017 }
1018
1020 template<fixed_string Tag, UnitType U, UnitType V>
1021 requires traits::is_same_dimension_unit_v<U, V>
1023 {
1024 lhs = basic_kind<Tag, U>(U(wrap_detail::unwrap(lhs).raw() + U(wrap_detail::unwrap(rhs)).raw()));
1025 return lhs;
1026 }
1027 template<fixed_string Tag, UnitType U, UnitType V>
1028 requires traits::is_same_dimension_unit_v<U, V>
1029 constexpr basic_kind<Tag, U>& operator-=(basic_kind<Tag, U>& lhs, const basic_kind<Tag, V>& rhs) noexcept
1030 {
1031 lhs = basic_kind<Tag, U>(U(wrap_detail::unwrap(lhs).raw() - U(wrap_detail::unwrap(rhs)).raw()));
1032 return lhs;
1033 }
1034
1037 template<fixed_string Tag, UnitType U, UnitType V>
1038 requires traits::is_same_dimension_unit_v<U, V>
1039 constexpr bool operator==(const basic_kind<Tag, U>& lhs, const basic_kind<Tag, V>& rhs) noexcept
1040 {
1041 return wrap_detail::unwrap(lhs) == wrap_detail::unwrap(rhs);
1042 }
1043 template<fixed_string Tag, UnitType U, UnitType V>
1044 requires traits::is_same_dimension_unit_v<U, V>
1045 constexpr std::partial_ordering operator<=>(const basic_kind<Tag, U>& lhs, const basic_kind<Tag, V>& rhs) noexcept
1046 {
1047 return wrap_detail::order(wrap_detail::unwrap(lhs), wrap_detail::unwrap(rhs));
1048 }
1049
1050 //----------------------------------
1051 // KIND MISMATCH DIAGNOSTICS (great errors, not overload-resolution soup)
1052 //----------------------------------
1053
1054 // Combining two DIFFERENT kinds is meaningless (a radial distance is not a straight-line distance; a torque
1055 // is not an energy). These catch-all overloads are the LEAST-preferred candidates (the same-tag overloads
1056 // above win whenever the tags match), so a mismatched combination selects one of these and stops at a single
1057 // readable message naming the mistake — instead of a wall of "no matching operator". They return a real
1058 // value (not void) so the compiler must instantiate the body to deduce the return type, which fires the
1059 // static_assert on g++, clang, AND MSVC (a void-returning body is not instantiated by MSVC's return-type
1060 // deduction, so the assert would be skipped there). To do arithmetic across kinds you must intend it:
1061 // unwrap one side with to<PlainUnit>().
1062 template<fixed_string TagL, fixed_string TagR, UnitType U, UnitType V>
1063 requires(!(TagL == TagR))
1064 constexpr auto operator+(const basic_kind<TagL, U>&, const basic_kind<TagR, V>&) noexcept
1065 {
1066 static_assert(wrap_detail::dependent_false<TagL, TagR>,
1067 "units::kind: cannot add two DIFFERENT kinds (their tags differ, e.g. \"radial\" vs \"straight\"). "
1068 "They share a unit and a dimension but are semantically distinct; unwrap one side with "
1069 "to<PlainUnit>() to operate on the plain unit if that is truly intended.");
1070 return basic_kind<TagL, U>{};
1071 }
1072 template<fixed_string TagL, fixed_string TagR, UnitType U, UnitType V>
1073 requires(!(TagL == TagR))
1074 constexpr auto operator-(const basic_kind<TagL, U>&, const basic_kind<TagR, V>&) noexcept
1075 {
1076 static_assert(wrap_detail::dependent_false<TagL, TagR>,
1077 "units::kind: cannot subtract two DIFFERENT kinds (their tags differ, e.g. \"radial\" vs "
1078 "\"straight\"). Unwrap one side with to<PlainUnit>() to operate on the plain unit if intended.");
1079 return basic_kind<TagL, U>{};
1080 }
1081 template<fixed_string TagL, fixed_string TagR, UnitType U, UnitType V>
1082 requires(!(TagL == TagR))
1083 constexpr bool operator==(const basic_kind<TagL, U>&, const basic_kind<TagR, V>&) noexcept
1084 {
1085 static_assert(wrap_detail::dependent_false<TagL, TagR>,
1086 "units::kind: cannot compare two DIFFERENT kinds (their tags differ, e.g. \"radial\" vs "
1087 "\"straight\"). Unwrap one side with to<PlainUnit>() to compare the plain units if intended.");
1088 return false;
1089 }
1090
1091 // Mixing a `kind` with a PLAIN unit is ill-formed: a plain unit carries no kind. A plain unit becomes a
1092 // kind only by construction/assignment (`kind<Tag,U> k = plain;`), never in arithmetic. These
1093 // catch-alls turn the raw "no matching operator" wall into one readable message.
1094 template<fixed_string Tag, UnitType U, UnitType Plain>
1095 constexpr auto operator+(const basic_kind<Tag, U>& lhs, const Plain&) noexcept
1096 {
1097 static_assert(wrap_detail::dependent_false_t<Plain>,
1098 "units::kind: cannot add a plain unit to a kind - a plain unit carries no kind, so mixing them in "
1099 "arithmetic is disallowed. Wrap the plain unit in the same kind first, or unwrap the kind with "
1100 "to<PlainUnit>() to work in plain units.");
1101 return lhs;
1102 }
1103 template<fixed_string Tag, UnitType U, UnitType Plain>
1104 constexpr auto operator+(const Plain&, const basic_kind<Tag, U>& rhs) noexcept
1105 {
1106 static_assert(wrap_detail::dependent_false_t<Plain>,
1107 "units::kind: cannot add a plain unit to a kind - a plain unit carries no kind. Wrap the plain unit "
1108 "in the same kind first, or unwrap the kind with to<PlainUnit>() to work in plain units.");
1109 return rhs;
1110 }
1111 template<fixed_string Tag, UnitType U, UnitType Plain>
1112 constexpr auto operator-(const basic_kind<Tag, U>& lhs, const Plain&) noexcept
1113 {
1114 static_assert(wrap_detail::dependent_false_t<Plain>,
1115 "units::kind: cannot subtract a plain unit from a kind - a plain unit carries no kind. Wrap it in the "
1116 "same kind first, or unwrap the kind with to<PlainUnit>().");
1117 return lhs;
1118 }
1119
1120 // A bare NUMBER does not add to / subtract from a kind (only kind +/- same-tag kind, and kind * / scalar
1121 // are defined). The most common naive fumble (`aKind + 3.0`) gets this message rather than a candidate wall.
1122 template<fixed_string Tag, UnitType U, ArithmeticType T>
1123 constexpr auto operator+(const basic_kind<Tag, U>& lhs, T) noexcept
1124 {
1125 static_assert(wrap_detail::dependent_false<Tag>,
1126 "units::kind: cannot add a bare number to a kind. Wrap the number in the same kind, or unwrap the "
1127 "kind with to<PlainUnit>() to work in plain units.");
1128 return lhs;
1129 }
1130 template<fixed_string Tag, UnitType U, ArithmeticType T>
1131 constexpr auto operator+(T, const basic_kind<Tag, U>& rhs) noexcept
1132 {
1133 static_assert(wrap_detail::dependent_false<Tag>,
1134 "units::kind: cannot add a bare number to a kind. Wrap the number in the same kind first.");
1135 return rhs;
1136 }
1137 template<fixed_string Tag, UnitType U, ArithmeticType T>
1138 constexpr auto operator-(const basic_kind<Tag, U>& lhs, T) noexcept
1139 {
1140 static_assert(wrap_detail::dependent_false<Tag>,
1141 "units::kind: cannot subtract a bare number from a kind. Wrap the number in the same kind first.");
1142 return lhs;
1143 }
1144
1145 //----------------------------------
1146 // KIND (TAGGED) FORMATTING
1147 //----------------------------------
1148
1150 template<fixed_string Tag, UnitType U>
1151 std::ostream& operator<<(std::ostream& os, const basic_kind<Tag, U>& obj)
1152 {
1153 return os << '[' << Tag.value << "] " << wrap_detail::unwrap(obj);
1154 }
1155
1157 template<fixed_string Tag, UnitType U>
1158 std::string to_string(const basic_kind<Tag, U>& obj)
1159 {
1160 return std::string("[").append(Tag.value).append("] ").append(units::to_string(wrap_detail::unwrap(obj)));
1161 }
1162
1163 //----------------------------------
1164 // KIND (TAGGED) MATH
1165 //----------------------------------
1166 // Parity with `delta`: magnitude and extrema keep the tag (the tag is preserved through the operation).
1167
1169 template<fixed_string Tag, UnitType U>
1170 constexpr auto abs(const basic_kind<Tag, U>& k) noexcept
1171 {
1172 using R = detail::floating_point_promotion_t<U>;
1173 return basic_kind<Tag, R>(units::abs(R(wrap_detail::unwrap(k))));
1174 }
1175
1177 template<fixed_string Tag, UnitType U, UnitType V>
1178 requires traits::is_same_dimension_unit_v<U, V>
1179 constexpr auto min(const basic_kind<Tag, U>& lhs, const basic_kind<Tag, V>& rhs) noexcept
1180 {
1182 const basic_kind<Tag, R> a = lhs.template to<basic_kind<Tag, R>>();
1183 const basic_kind<Tag, R> b = rhs.template to<basic_kind<Tag, R>>();
1184 return a < b ? a : b;
1185 }
1186
1188 template<fixed_string Tag, UnitType U, UnitType V>
1189 requires traits::is_same_dimension_unit_v<U, V>
1190 constexpr auto max(const basic_kind<Tag, U>& lhs, const basic_kind<Tag, V>& rhs) noexcept
1191 {
1193 const basic_kind<Tag, R> a = lhs.template to<basic_kind<Tag, R>>();
1194 const basic_kind<Tag, R> b = rhs.template to<basic_kind<Tag, R>>();
1195 return a > b ? a : b;
1196 }
1197
1199 template<fixed_string Tag, UnitType U, UnitType V, UnitType W>
1200 requires(traits::is_same_dimension_unit_v<U, V> && traits::is_same_dimension_unit_v<U, W>)
1201 constexpr auto clamp(const basic_kind<Tag, U>& value, const basic_kind<Tag, V>& lo, const basic_kind<Tag, W>& hi) noexcept
1202 {
1203 return affine::min(affine::max(value, lo), hi);
1204 }
1205 } // inline namespace affine
1206
1207
1208 //----------------------------------------------------------------------------------------------------------------------
1209 // TOP-LEVEL `kind` ALIAS (the preferred spelling)
1210 //----------------------------------------------------------------------------------------------------------------------
1213 template<affine::fixed_string Tag, UnitType U>
1215} // end namespace units
1216
1217//----------------------------------------------------------------------------------------------------------------------
1218// STD Namespace extensions
1219//----------------------------------------------------------------------------------------------------------------------
1220// A wrapper is hashable and has numeric limits exactly as the unit it wraps does — so a wrapper drops into an
1221// unordered container and generic `numeric_limits`-driven code the same way the plain unit does.
1222
1223//------------------------------
1224// std::hash
1225//------------------------------
1226
1228template<units::UnitType U>
1229struct std::hash<units::affine::absolute<U>>
1230{
1231 constexpr std::size_t operator()(const units::affine::absolute<U>& x) const noexcept { return std::hash<U>()(x.template to<U>()); }
1232};
1233
1235template<units::UnitType U>
1236struct std::hash<units::affine::delta<U>>
1237{
1238 constexpr std::size_t operator()(const units::affine::delta<U>& x) const noexcept { return std::hash<U>()(x.template to<U>()); }
1239};
1240
1242template<units::affine::fixed_string Tag, units::UnitType U>
1243struct std::hash<units::affine::basic_kind<Tag, U>>
1244{
1245 constexpr std::size_t operator()(const units::affine::basic_kind<Tag, U>& x) const noexcept { return std::hash<U>()(x.template to<U>()); }
1246};
1247
1248//------------------------------
1249// std::numeric_limits
1250//------------------------------
1251
1252namespace std
1253{
1255 template<units::UnitType U>
1256 struct numeric_limits<units::affine::absolute<U>>
1257 {
1258 static constexpr bool is_specialized = true;
1259 static constexpr units::affine::absolute<U> min() { return units::affine::absolute<U>(std::numeric_limits<U>::min()); }
1260 static constexpr units::affine::absolute<U> max() { return units::affine::absolute<U>(std::numeric_limits<U>::max()); }
1261 static constexpr units::affine::absolute<U> lowest() { return units::affine::absolute<U>(std::numeric_limits<U>::lowest()); }
1262 };
1263
1265 template<units::UnitType U>
1266 struct numeric_limits<units::affine::delta<U>>
1267 {
1268 static constexpr bool is_specialized = true;
1269 static constexpr units::affine::delta<U> min() { return units::affine::delta<U>(std::numeric_limits<U>::min()); }
1270 static constexpr units::affine::delta<U> max() { return units::affine::delta<U>(std::numeric_limits<U>::max()); }
1271 static constexpr units::affine::delta<U> lowest() { return units::affine::delta<U>(std::numeric_limits<U>::lowest()); }
1272 };
1273
1275 template<units::affine::fixed_string Tag, units::UnitType U>
1276 struct numeric_limits<units::affine::basic_kind<Tag, U>>
1277 {
1278 static constexpr bool is_specialized = true;
1279 static constexpr units::affine::basic_kind<Tag, U> min() { return units::affine::basic_kind<Tag, U>(std::numeric_limits<U>::min()); }
1280 static constexpr units::affine::basic_kind<Tag, U> max() { return units::affine::basic_kind<Tag, U>(std::numeric_limits<U>::max()); }
1281 static constexpr units::affine::basic_kind<Tag, U> lowest() { return units::affine::basic_kind<Tag, U>(std::numeric_limits<U>::lowest()); }
1282 };
1283} // namespace std
1284
1285#endif // UNIT_KIND_H
A point on a (possibly affine) scale — carries the unit's datum.
Definition kind.h:301
constexpr Arithmetic to() const noexcept
Express this point's value as a plain arithmetic type — the numeric value in its own unit (absolute<c...
Definition kind.h:325
constexpr auto to_linearized() const noexcept
The point's linearized value (as the wrapped unit's to_linearized()).
Definition kind.h:317
constexpr absolute() noexcept=default
< the wrapped unit's numeric type
constexpr PlainTarget to() const noexcept
Express this point as a PLAIN unit of the same dimension — unwraps, applying the datum offset (absolu...
Definition kind.h:335
constexpr const char * name() const noexcept
The wrapped unit's name/abbreviation (a point does not rename the unit).
Definition kind.h:319
constexpr WrapperTarget to() const noexcept
Express this point as another absolute<V> — stays a point (the datum is applied as it re-wraps).
Definition kind.h:345
constexpr absolute(underlying_type value) noexcept
Construct a point directly from its underlying numeric value (in U's own unit).
Definition kind.h:310
constexpr auto raw() const noexcept
The point's raw (linearized) value.
Definition kind.h:315
constexpr auto value() const noexcept
The point's numeric value in its own unit.
Definition kind.h:313
typename traits::unit_traits< U >::underlying_type underlying_type
< the wrapped unit type
Definition kind.h:304
A quantity distinguished by a string TAG — a "kind of quantity" that shares a unit and a dimension wi...
Definition kind.h:478
constexpr auto value() const noexcept
The numeric value in its own unit.
Definition kind.h:505
static constexpr auto tag() noexcept
The tag of this kind.
Definition kind.h:503
constexpr basic_kind(underlying_type value) noexcept
Construct from the underlying numeric value (in U's own unit) — explicit, since a bare number carries...
Definition kind.h:493
constexpr const char * abbreviation() const noexcept
The abbreviation is the wrapped unit's (a kind does not change the abbreviation — "m" for a kind<"rad...
Definition kind.h:512
constexpr basic_kind() noexcept=default
< the wrapped unit's numeric type
constexpr PlainTarget to() const noexcept
Express this kind as a PLAIN unit of the same dimension — unwraps (dropping the tag),...
Definition kind.h:528
constexpr WrapperTarget to() const noexcept
Express this kind as another kind<Tag, V> — the tag is KEPT (radial metres → radial feet).
Definition kind.h:538
constexpr auto raw() const noexcept
The raw (linearized) value.
Definition kind.h:507
constexpr auto to_linearized() const noexcept
The linearized value (as the wrapped unit's to_linearized()).
Definition kind.h:509
typename traits::unit_traits< U >::underlying_type underlying_type
< the wrapped unit type
Definition kind.h:481
constexpr basic_kind & operator=(const U &value) noexcept
Assign a plain unit into this kind (same clear-intent conversion as copy-initialization).
Definition kind.h:496
std::string name() const
The name is the tag followed by the wrapped unit's name — e.g. "radial meters".
Definition kind.h:514
constexpr Arithmetic to() const noexcept
Express this kind's value as a plain arithmetic type — the numeric value in its own unit,...
Definition kind.h:519
An amount of a quantity — offset-free (no datum).
Definition kind.h:362
constexpr PlainTarget to() const noexcept
Express this amount as a PLAIN unit of the same dimension — unwraps, SCALE ONLY (the datum is never a...
Definition kind.h:398
constexpr const char * name() const noexcept
The wrapped unit's name/abbreviation (an amount does not rename the unit).
Definition kind.h:380
constexpr WrapperTarget to() const noexcept
Express this amount as another delta<V> — stays an amount (scale-only).
Definition kind.h:411
constexpr Arithmetic to() const noexcept
Express this amount's value as a plain arithmetic type — the numeric value in its own unit,...
Definition kind.h:386
constexpr auto to_linearized() const noexcept
The amount's linearized value (as the wrapped unit's to_linearized()).
Definition kind.h:378
typename traits::unit_traits< U >::underlying_type underlying_type
< the wrapped unit type
Definition kind.h:365
constexpr auto raw() const noexcept
The amount's raw (linearized) value.
Definition kind.h:376
constexpr delta(underlying_type value) noexcept
Construct an amount directly from its underlying numeric value (in U's own unit).
Definition kind.h:371
constexpr auto value() const noexcept
The amount's numeric value in its own unit.
Definition kind.h:374
constexpr delta() noexcept=default
< the wrapped unit's numeric type
Definition core.h:2735
Concept satisfied by any absolute<U> point wrapper.
Definition kind.h:275
Concept satisfied by any delta<U> amount wrapper.
Definition kind.h:282
Concept satisfied by any string-tagged kind<Tag, U>.
Definition kind.h:289
unit, dimensional analysis, generic cmath functions, traits (not dimension-specific),...
unit< traits::strong_t< conversion_factor< typename traits::conversion_factor_traits< typename traits::unit_traits< U >::conversion_factor >::conversion_ratio, typename traits::conversion_factor_traits< typename traits::unit_traits< U >::conversion_factor >::dimension_type, typename traits::conversion_factor_traits< typename traits::unit_traits< U >::conversion_factor >::pi_exponent_ratio, std::ratio< 0 > > >, typename traits::unit_traits< U >::underlying_type, typename traits::unit_traits< U >::numerical_scale_type > delta_unit_t
The offset-free counterpart of a unit: same dimension, scale, and pi factor, but translation stripped...
Definition kind.h:84
constexpr bool is_losslessly_point_convertible_unit
The result unit of a wrapper operator that keeps the LHS UNIT (the "LHS-unit tie-break"): the value s...
Definition kind.h:108
std::conditional_t< is_losslessly_point_convertible_unit< V, U >, U, traits::replace_underlying_t< U, floating_point_promotion_t< typename traits::unit_traits< U >::underlying_type > > > absolute_result_unit_t
The result unit of an absolute (point) operator: keep the LHS unit, promote the underlying only when ...
Definition kind.h:117
std::conditional_t< is_losslessly_convertible_unit< delta_unit_t< V >, delta_unit_t< U > >, U, traits::replace_underlying_t< U, floating_point_promotion_t< typename traits::unit_traits< U >::underlying_type > > > delta_result_unit_t
The result unit of a delta (amount) operator: keep the LHS unit, promote the underlying only when the...
Definition kind.h:124
STL namespace.
constexpr unit< compound_conversion_factor< joules_, inverse< kelvin_ >, inverse< mols_ > > > R(8.314462618)
Gas constant.
namespace representing type traits which can access the properties of types provided by the units lib...
Definition core.h:197
Unit Conversion Library namespace.
Definition units.h:106
constexpr auto operator-(const absolute< U > &lhs, const absolute< V > &rhs) noexcept
point - point -> delta, kept in the LHS unit (the datum offsets cancel).
Definition kind.h:563
constexpr auto abs(const delta< U > &d) noexcept
Absolute magnitude of a delta (|amount|), promoting like the wrapped unit's units::abs.
Definition kind.h:796
constexpr absolute< U > & operator+=(absolute< U > &lhs, const delta< V > &rhs) noexcept
Compound move of a point by a delta.
Definition kind.h:654
constexpr delta< U > & operator*=(delta< U > &lhs, T rhs) noexcept
Compound scale of a delta by a bare number, in place (the underlying stays the lhs type,...
Definition kind.h:687
constexpr auto operator*(const delta< U > &lhs, T rhs) noexcept
delta scaled by a bare number -> delta.
Definition kind.h:630
constexpr auto operator*(const UnitTypeLhs &lhs, const UnitTypeRhs &rhs) noexcept -> detail::rewrap_to_named_t< unit< traits::strong_t< squared< typename traits::unit_traits< std::common_type_t< UnitTypeLhs, UnitTypeRhs > >::conversion_factor > >, typename std::common_type_t< UnitTypeLhs, UnitTypeRhs >::underlying_type > >
Multiplication type for convertible unit types with a linear scale.
Definition core.h:4501
constexpr bool operator==(const absolute< U > &lhs, const absolute< V > &rhs) noexcept
Compare two points (the datum is applied on each side as it unwraps to the plain unit).
Definition kind.h:715
std::string to_string(const absolute< U > &obj)
String form of a point: its wrapped quantity, unmarked.
Definition kind.h:774
constexpr auto operator/(const delta< U > &lhs, T rhs) noexcept
delta divided by a bare number -> delta (promotes like the wrapped unit's own operator/).
Definition kind.h:643
constexpr auto max(const delta< U > &lhs, const delta< V > &rhs) noexcept
The larger of two deltas, kept in the LHS unit (scale-only reconciliation of the rhs).
Definition kind.h:816
constexpr auto min(const delta< U > &lhs, const delta< V > &rhs) noexcept
The smaller of two deltas, kept in the LHS unit (scale-only reconciliation of the rhs).
Definition kind.h:805
constexpr auto clamp(const delta< U > &value, const delta< V > &lo, const delta< W > &hi) noexcept
Clamp a delta into [lo, hi], kept in the value's LHS unit.
Definition kind.h:827
constexpr auto operator+(const absolute< U > &lhs, const delta< V > &rhs) noexcept
point + delta -> point (move the point up by a relative amount), kept in the LHS point's unit.
Definition kind.h:577
std::ostream & operator<<(std::ostream &os, const absolute< U > &obj)
Stream a point: its wrapped quantity, unmarked.
Definition kind.h:760
affine::basic_kind< Tag, U > kind
The preferred user-facing spelling of a string-tagged quantity kind: units::kind<"radial",...
Definition kind.h:1214
A compile-time string usable as a non-type template parameter (the tag of a kind/of).
Definition kind.h:445
char value[N]
the stored characters (including the terminating null)
Definition kind.h:446
constexpr bool operator==(const fixed_string< M > &rhs) const noexcept
Compare two tags of possibly-different length: unequal if the lengths differ, else element-wise.
Definition kind.h:452
Trait which tests whether T is an absolute<U> point wrapper.
Definition kind.h:237
Trait which tests whether T is a delta<U> amount wrapper.
Definition kind.h:250
Trait which tests whether T is a string-tagged kind<Tag, U>.
Definition kind.h:263