Skip to content

Commit d8b72fc

Browse files
committed
refactor: scaling the value by magnitude heavily refactored
1 parent 2681868 commit d8b72fc

File tree

18 files changed

+591
-418
lines changed

18 files changed

+591
-418
lines changed

docs/examples/measurement.md

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,17 @@ projects:
5353

5454
### Integration with mp-units
5555

56-
To use `measurement<T>` as a quantity representation, two things must be provided.
56+
No special customization is needed to use `measurement<T>` as a quantity representation.
57+
`measurement<T>` defines `operator*(measurement, T)` and `operator/(measurement, T)` that
58+
correctly scale the value and uncertainty linearly, satisfying
59+
[`UsesFloatingPointScaling`](../users_guide/framework_basics/representation_types.md#how-scaling-works)
60+
automatically.
5761

58-
First, a `scaling_traits` specialization that teaches the library how to scale a
59-
`measurement<T>` to a `measurement<U>` by a unit magnitude:
62+
The library verifies at compile time that `measurement<T>` satisfies the
63+
[`RepresentationOf`](../users_guide/framework_basics/concepts.md#RepresentationOf) concept:
6064

6165
```cpp title="measurement.cpp"
62-
--8<-- "example/measurement.cpp:46:53"
63-
```
64-
65-
The `scale<U>(M, value)` call recurses into the built-in `scaling_traits` for the
66-
underlying scalar type `T``U`, so uncertainty propagation is exact and type-safe.
67-
68-
Second, the library verifies at compile time that `measurement<T>` satisfies the
69-
`RepresentationOf` concept:
70-
71-
```cpp title="measurement.cpp"
72-
--8<-- "example/measurement.cpp:55:58"
66+
--8<-- "example/measurement.cpp:46:49"
7367
```
7468

7569
This allows `measurement<T>` to be used seamlessly with any **mp-units** quantity type.
@@ -81,7 +75,7 @@ This allows `measurement<T>` to be used seamlessly with any **mp-units** quantit
8175
The example demonstrates various uncertainty propagation scenarios:
8276

8377
```cpp title="measurement.cpp"
84-
--8<-- "example/measurement.cpp:62:85"
78+
--8<-- "example/measurement.cpp:51:77"
8579
```
8680

8781
This showcases:

docs/how_to_guides/integration/using_custom_representation_types.md

Lines changed: 101 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -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
130124
template<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
133127
template<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
136130
template<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

141134
For **vectors**, provide the `norm()` customization point function:
@@ -147,7 +140,7 @@ class my_vector_type {
147140
public:
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
158151
template<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`:
172165
template<typename T, typename Char>
173166
struct 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
188182
template<typename T>
189183
struct 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 {
289322
public:
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:
438471
3. **Add formatting support** (optional) via `std::formatter`
439472
4. **Add `value_type`** to help the library determine the scaling factor type
440473
5. **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 ``).
443477
7. **Specialize `implicitly_scalable`** (if needed) to control implicit vs. explicit
444478
conversion semantics
445479
8. **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

docs/users_guide/framework_basics/concepts.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,9 @@ Every representation type must satisfy a common baseline:
128128

129129
- **Weakly regular**: copyable and equality comparable (default-constructibility is not required).
130130
- **`MagnitudeScalable`**: the library must be able to apply a unit magnitude ratio to it
131-
via `mp_units::scale<T>(M, value)`. Most standard types satisfy this
131+
internally. Most standard types satisfy this
132132
automatically; see
133-
[Representation Types](representation_types.md#scaling_traits) for details.
133+
[Representation Types](representation_types.md#how-scaling-works) for details.
134134
- **Character-specific operations**: additional arithmetic operations required by the
135135
[quantity character](../../reference/glossary.md#character) (e.g. total ordering for real
136136
scalars, `real()`/`imag()`/`modulus()` CPOs for complex scalars, `norm()`/`magnitude()`

0 commit comments

Comments
 (0)