@@ -44,55 +44,49 @@ public:
4444 using value_type = T; // Helps library determine scaling factor type
4545
4646 constexpr explicit my_scalar_type(T v) : value_(v) {}
47- constexpr T value() const { return value_; }
47+ [[nodiscard]] constexpr T value() const { return value_; }
4848
4949 // Required: Arithmetic operations
50- constexpr my_scalar_type operator-() const { return my_scalar_type{-value_}; }
50+ [[nodiscard]] constexpr my_scalar_type operator-() const { return my_scalar_type{-value_}; }
5151
52- friend constexpr my_scalar_type operator+(const my_scalar_type& lhs, const my_scalar_type& rhs)
52+ [[nodiscard]] friend constexpr my_scalar_type operator+(const my_scalar_type& lhs, const my_scalar_type& rhs)
5353 {
5454 return my_scalar_type{lhs.value_ + rhs.value_};
5555 }
5656
57- friend constexpr my_scalar_type operator-(const my_scalar_type& lhs, const my_scalar_type& rhs)
57+ [[nodiscard]] friend constexpr my_scalar_type operator-(const my_scalar_type& lhs, const my_scalar_type& rhs)
5858 {
5959 return my_scalar_type{lhs.value_ - rhs.value_};
6060 }
6161
62- // Required for floating-point T: scaling operations used by the built-in conversion engine.
63- // For integer T, implicit conversions to/from value_type_t<T> are used instead (see note below).
64- // Either path satisfies MagnitudeScalable; alternatively provide a scaling_traits specialisation.
65- friend constexpr my_scalar_type operator*(const my_scalar_type& v, T factor)
62+ [[nodiscard]] friend constexpr my_scalar_type operator*(const my_scalar_type& v, T factor)
6663 {
6764 return my_scalar_type{v.value_ * factor};
6865 }
6966
70- friend constexpr my_scalar_type operator*(T factor, const my_scalar_type& v)
67+ [[nodiscard]] friend constexpr my_scalar_type operator*(T factor, const my_scalar_type& v)
7168 {
7269 return my_scalar_type{factor * v.value_};
7370 }
7471
75- friend constexpr my_scalar_type operator/(const my_scalar_type& v, T factor)
72+ [[nodiscard]] friend constexpr my_scalar_type operator/(const my_scalar_type& v, T factor)
7673 {
7774 return my_scalar_type{v.value_ / factor};
7875 }
7976
8077 // Required for scalar types: Self-scaling operations (multiply/divide by same type)
81- friend constexpr my_scalar_type operator*(const my_scalar_type& lhs, const my_scalar_type& rhs)
78+ [[nodiscard]] friend constexpr my_scalar_type operator*(const my_scalar_type& lhs, const my_scalar_type& rhs)
8279 {
8380 return my_scalar_type{lhs.value_ * rhs.value_};
8481 }
8582
86- friend constexpr my_scalar_type operator/(const my_scalar_type& lhs, const my_scalar_type& rhs)
83+ [[nodiscard]] friend constexpr my_scalar_type operator/(const my_scalar_type& lhs, const my_scalar_type& rhs)
8784 {
8885 return my_scalar_type{lhs.value_ / rhs.value_};
8986 }
9087
91- // Required: Equality comparison
92- constexpr bool operator==(const my_scalar_type&) const = default;
93-
94- // Required for real scalar types: Total ordering
95- constexpr auto operator<=>(const my_scalar_type&) const = default;
88+ // Required for real scalar types: Equality & Total ordering
89+ [[nodiscard]] constexpr auto operator<=>(const my_scalar_type&) const = default;
9690};
9791```
9892
@@ -116,9 +110,9 @@ public:
116110 constexpr my_complex_type(T r, T i) : real_ (r), imag_ (i) {}
117111
118112 // Required customization point functions as member functions
119- constexpr T real() const { return real_ ; }
120- constexpr T imag() const { return imag_ ; }
121- constexpr T modulus() const { return std::hypot(real_ , imag_ ); }
113+ [[ nodiscard ]] constexpr T real() const { return real_ ; }
114+ [[ nodiscard ]] constexpr T imag() const { return imag_ ; }
115+ [[ nodiscard ]] constexpr T modulus() const { return std::hypot(real_ , imag_ ); }
122116
123117 // ... other required operations (addition, scaling, equality)
124118};
@@ -128,14 +122,13 @@ Or via free functions found through ADL:
128122
129123```cpp
130124template<typename T>
131- constexpr T real(const my_complex_type<T>& c) { return c.get_real(); }
125+ [[nodiscard]] constexpr T real(const my_complex_type<T>& c) { return c.get_real(); }
132126
133127template<typename T>
134- constexpr T imag(const my_complex_type<T>& c) { return c.get_imag(); }
128+ [[nodiscard]] constexpr T imag(const my_complex_type<T>& c) { return c.get_imag(); }
135129
136130template<typename T>
137- constexpr T modulus(const my_complex_type<T>& c) { return c.get_magnitude(); }
138- // Note: You can also provide abs() instead of modulus()
131+ [[nodiscard]] constexpr T modulus(const my_complex_type<T>& c) { return c.get_magnitude(); }
139132```
140133
141134For ** vectors** , provide the ` norm() ` customization point function:
@@ -147,7 +140,7 @@ class my_vector_type {
147140public:
148141 using value_type = T;
149142
150- constexpr T norm() const { /* compute magnitude * / }
143+ constexpr auto norm() const { /* compute magnitude * / }
151144 // ... other required operations
152145};
153146```
@@ -156,7 +149,7 @@ Or via a free function:
156149
157150```cpp
158151template<typename T>
159- constexpr T norm(const my_vector_type<T>& v) { return v.compute_norm(); }
152+ [[nodiscard]] constexpr auto norm(const my_vector_type<T>& v) { return v.compute_norm(); }
160153```
161154
162155!!! tip "Use ` norm() ` for vectors"
@@ -172,7 +165,8 @@ Enable formatting with `std::format`:
172165template <typename T, typename Char>
173166struct std ::formatter<my_scalar_type<T >, Char> : std::formatter<T, Char> {
174167 template<typename FormatContext >
175- auto format(const my_scalar_type<T >& v, FormatContext& ctx) const {
168+ auto format(const my_scalar_type<T >& v, FormatContext& ctx) const
169+ {
176170 return std::formatter<T, Char>::format(v.value(), ctx);
177171 }
178172};
@@ -187,50 +181,89 @@ documentation):
187181```cpp
188182template<typename T>
189183struct mp_units::representation_values<my_scalar_type<T>> {
190- static constexpr my_scalar_type<T> zero() noexcept {
191- return my_scalar_type<T>{T{0}};
192- }
193- static constexpr my_scalar_type<T> one() noexcept {
194- return my_scalar_type<T>{T{1}};
195- }
196- static constexpr my_scalar_type<T> min() noexcept {
184+ [[nodiscard]] static constexpr my_scalar_type<T> zero() noexcept { return my_scalar_type<T>{T{0}}; }
185+ [[nodiscard]] static constexpr my_scalar_type<T> one() noexcept { return my_scalar_type<T>{T{1}}; }
186+ [[nodiscard]] static constexpr my_scalar_type<T> min() noexcept
187+ {
197188 return my_scalar_type<T>{std::numeric_limits<T>::lowest()};
198189 }
199- static constexpr my_scalar_type<T> max() noexcept {
190+ [[nodiscard]] static constexpr my_scalar_type<T> max() noexcept
191+ {
200192 return my_scalar_type<T>{std::numeric_limits<T>::max()};
201193 }
202194};
203195```
204196
205- ### Step 5: Specialize ` scaling_traits ` (if needed) { #scaling_traits }
197+ ### Step 5: Enable scaling { #scale }
206198
207- The library provides built-in scaling for two broad categories of types (see the
208- [ ` scaling_traits ` ] ( ../../users_guide/framework_basics/representation_types.md#scaling_traits )
209- reference):
199+ The library applies a unit magnitude to a representation value internally when performing
200+ unit conversions. Three built-in paths handle this automatically — see
201+ [How Scaling Works](../../users_guide/framework_basics/representation_types.md#how-scaling-works)
202+ for the full concept definitions and algorithm.
210203
211- - ** Floating-point types** : any type where ` treat_as_floating_point ` is ` true ` and
212- ` value * value_type_t<T> ` compiles — no specialization needed.
213- - ** Integer-like types** : types whose ` value_type_t ` is a non-floating-point integer type
214- and that are convertible to/from that type — no specialization needed. The built-in
215- implementation uses exact double-width integer arithmetic for rational factors, avoiding
216- floating-point precision loss.
204+ `my_scalar_type<T>` already satisfies the floating-point or element-wise scaling path
205+ through its existing `operator *(my_scalar_type, T)` and `operator /(my_scalar_type, T)` —
206+ no further customization is needed.
217207
218- You need to specialize ` scaling_traits ` ** only** when scaling must operate on multiple
219- internal fields simultaneously and the built-in single-value approach doesn't apply. A
220- common example is a type that carries both a value and an uncertainty:
208+ If your type is not automatically recognized (e.g., a third-party floating-point type with
209+ no ` value_type ` member), expose ` value_type ` via
210+ [ ` std::indirectly_readable_traits ` ] ( ../../users_guide/framework_basics/representation_types.md#value_type-or-element_type ) —
211+ ` treat_as_floating_point ` will then default to ` true ` automatically, with no further
212+ specialization needed. The example below shows this typical case.
213+ Only specialize
214+ [ ` treat_as_floating_point ` ] ( ../../users_guide/framework_basics/representation_types.md#treat_as_floating_point )
215+ directly when there is genuinely no meaningful ` value_type ` to expose.
221216
222- ``` cpp
223- template <typename T, typename U>
224- struct mp_units ::scaling_traits<my_measurement<T >, my_measurement<U >> {
225- template<auto M >
226- [[ nodiscard]] static constexpr my_measurement<U > scale(const my_measurement<T >& v)
227- {
228- return my_measurement<U >(
229- mp_units::scale<U >(M, v.value()),
230- mp_units::scale<U >(M, v.uncertainty()));
231- }
232- };
233- ```
217+ ??? example "` MyFloat ` — integrating a third-party floating-point type"
218+
219+ Suppose a third-party library provides a high-precision floating-point type that you
220+ cannot modify:
221+
222+ ```cpp
223+ // Third-party type — you cannot modify the source.
224+ class MyFloat {
225+ long double v_;
226+ public:
227+ explicit(false) MyFloat(long double v) : v_(v) {}
228+
229+ MyFloat operator-() const { return MyFloat{-v_}; }
230+ friend MyFloat operator+(MyFloat a, MyFloat b) { return MyFloat{a.v_ + b.v_}; }
231+ friend MyFloat operator-(MyFloat a, MyFloat b) { return MyFloat{a.v_ - b.v_}; }
232+ friend MyFloat operator*(MyFloat a, MyFloat b) { return MyFloat{a.v_ * b.v_}; }
233+ friend MyFloat operator/(MyFloat a, MyFloat b) { return MyFloat{a.v_ / b.v_}; }
234+ friend auto operator<=>(MyFloat, MyFloat) = default;
235+ };
236+ ```
237+
238+ `MyFloat` is floating-point in spirit but the library cannot detect this automatically:
239+
240+ - It has no `value_type` member, so `value_type_t<MyFloat>` falls back to `MyFloat` itself.
241+ - `treat_as_floating_point<MyFloat>` defaults to `std::is_floating_point_v<MyFloat>` = `false`
242+ (it is a class, not a fundamental type), so `MagnitudeScalable<MyFloat>` is not satisfied.
243+
244+ One specialization fixes this:
245+
246+ ```cpp
247+ // Expose the element type so value_type_t<MyFloat> == long double.
248+ // treat_as_floating_point<MyFloat> then defaults to
249+ // std::is_floating_point_v<long double> == true, so no further
250+ // specialization is needed.
251+ template<>
252+ struct std::indirectly_readable_traits<MyFloat> {
253+ using value_type = long double;
254+ };
255+ ```
256+
257+ After that specialization `MyFloat` satisfies `UsesFloatingPointScaling` and
258+ integrates with the library without any further changes:
259+
260+ ```cpp
261+ static_assert(mp_units::MagnitudeScalable<MyFloat>);
262+ static_assert(mp_units::RepresentationOf<MyFloat, mp_units::quantity_character::real_scalar>);
263+
264+ const auto q = isq::length(MyFloat{1.0L} * m);
265+ const auto q_km = q.in(km); // MyFloat * long double — handled by UsesFloatingPointScaling
266+ ```
234267
235268### Step 6: Specialize ` implicitly_scalable ` (if needed) { #implicitly_scalable }
236269
@@ -289,9 +322,9 @@ class ranged_representation {
289322public:
290323 constexpr ranged_representation(T v) : value_(std::clamp(v, T{Min}, T{Max})) {}
291324
292- constexpr T value() const { return value_; }
293- constexpr operator T() const { return value_; } // Conversion to underlying type
294- constexpr ranged_representation operator-() const { return ranged_representation(-value_); }
325+ [[nodiscard]] constexpr T value() const { return value_; }
326+ [[nodiscard]] constexpr operator T() const { return value_; } // Conversion to underlying type
327+ [[nodiscard]] constexpr ranged_representation operator-() const { return ranged_representation(-value_); }
295328 // ... other required operations
296329};
297330```
@@ -406,7 +439,7 @@ namespace my_namespace {
406439
407440 // ✅ Good: in the same namespace, found by ADL
408441 template<typename T>
409- constexpr T real(const my_complex<T>& c) { return c.get_real(); }
442+ [[nodiscard]] constexpr T real(const my_complex<T>& c) { return c.get_real(); }
410443
411444} // namespace my_namespace
412445```
@@ -438,8 +471,9 @@ To create a custom representation type:
4384713 . ** Add formatting support** (optional) via ` std::formatter `
4394724 . ** Add ` value_type ` ** to help the library determine the scaling factor type
4404735 . ** Specialize ` representation_values<Rep> ` ** (if needed) for custom special values
441- 6 . ** Specialize ` scaling_traits<From, To> ` ** (if needed) when scaling must operate on
442- multiple internal fields simultaneously (e.g. value + uncertainty types)
474+ 6 . ** Implement ` operator*(T, value_type_t<T>) ` and ` operator/(T, value_type_t<T>) ` ** so
475+ that scaling correctly updates all internal fields (e.g. for ` with_variance<T> ` scale
476+ ` value ` by ` k ` and ` variance ` by ` k² ` ).
4434777 . ** Specialize ` implicitly_scalable ` ** (if needed) to control implicit vs. explicit
444478 conversion semantics
4454798 . ** Verify with concepts** using ` static_assert `
@@ -461,7 +495,7 @@ custom types.
461495** Implementation References:**
462496
463497- [ ` representation_concepts.h ` ] ( https://github.com/mpusz/mp-units/blob/1511c73d362649cca90191cdcfd3b369058c1dc1/src/core/include/mp-units/framework/representation_concepts.h ) - Concept definitions
464- - [ ` scaling.h ` ] ( https://github.com/mpusz/mp-units/blob/1511c73d362649cca90191cdcfd3b369058c1dc1/src/core/include/mp-units/framework /scaling.h ) - ` scaling_traits ` and built-in scaling implementation
498+ - [ ` scaling.h ` ] ( https://github.com/mpusz/mp-units/blob/1511c73d362649cca90191cdcfd3b369058c1dc1/src/core/include/mp-units/bits /scaling.h ) - built-in scaling implementation
465499- [ ` value_cast.h ` ] ( https://github.com/mpusz/mp-units/blob/1511c73d362649cca90191cdcfd3b369058c1dc1/src/core/include/mp-units/framework/value_cast.h ) - ` implicitly_scalable ` and ` is_integral_scaling `
466500- [ ` customization_points.h ` ] ( https://github.com/mpusz/mp-units/blob/1511c73d362649cca90191cdcfd3b369058c1dc1/src/core/include/mp-units/framework/customization_points.h ) - CPO implementations
467501- [ ` cartesian_vector.h ` ] ( https://github.com/mpusz/mp-units/blob/1511c73d362649cca90191cdcfd3b369058c1dc1/src/core/include/mp-units/cartesian_vector.h ) - Vector implementation example
0 commit comments