2023-07-30 18:29:35 +02:00
# The Affine Space
The affine space has two types of entities:
2023-12-26 12:13:14 +01:00
- **_Point_** - a position specified with coordinate values (e.g., location, address, etc.)
2024-03-23 22:21:33 +09:00
- **_Displacement vectors_** - the difference between two points (e.g., shift, offset,
displacement, duration, etc.)
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
In the following subchapters, we will often refer to _displacement vectors_ simply as _vectors_ for
brevity.
2023-07-30 18:29:35 +02:00
!!! note
2024-03-23 22:21:33 +09:00
The _displacement vector_ described here is specific to the affine space theory and is not the same
thing as the quantity of a vector character that we discussed in the
2023-08-03 21:23:34 +02:00
["Scalars, vectors, and tensors" chapter ](character_of_a_quantity.md#scalars-vectors-and-tensors )
2023-07-30 18:29:35 +02:00
(although, in some cases, those terms may overlap).
## Operations in the affine space
Here are the primary operations one can do in the affine space:
2024-03-23 22:21:33 +09:00
- _vector_ + _vector_ -> _vector_
- _vector_ - _vector_ -> _vector_
- -_vector_ -> _vector_
- _vector_ * scalar -> _vector_
- scalar * _vector_ -> _vector_
- _vector_ / scalar -> _vector_
- _point_ - _point_ -> _vector_
- _point_ + _vector_ -> _point_
- _vector_ + _point_ -> _point_
- _point_ - _vector_ -> _point_
2023-07-30 18:29:35 +02:00
2023-08-30 11:33:30 +02:00
!!! important
2023-07-30 18:29:35 +02:00
It is not possible to:
2024-03-23 22:21:33 +09:00
- add two _points_ ,
- subtract a _point_ from a _vector_ ,
- multiply nor divide _points_ with anything else.
2023-07-30 18:29:35 +02:00
2023-12-08 12:57:08 +01:00
## _Points_ are more common than most of us imagine
_Point_ abstractions should be used more often in the C++ software.
2023-12-26 12:13:14 +01:00
They are not only about _temperature_ or _time_ . _Points_ are everywhere around us and should become
2023-12-08 12:57:08 +01:00
more popular in the products we implement. They can be used to implement:
2023-12-26 12:13:14 +01:00
- _temperature_ points,
2023-12-08 12:57:08 +01:00
- timestamps,
2023-12-26 12:13:14 +01:00
- daily _mass_ readouts from the scale,
- _altitudes_ of mountain peaks on a map,
2024-03-24 07:16:23 +01:00
- current _path length_ measured by the car's odometer,
2023-12-26 12:13:14 +01:00
- today's _price_ of instruments on the market,
2023-12-08 12:57:08 +01:00
- and many more.
2023-12-26 12:13:14 +01:00
Improving the affine space's _Points_ intuition will allow us to write better and safer software.
2023-12-08 12:57:08 +01:00
2024-03-23 22:21:33 +09:00
## _Displacement vector_ is modeled by `quantity`
2023-07-30 18:29:35 +02:00
2023-12-08 12:57:08 +01:00
Up until now, each time we used a `quantity` in our code, we were modeling some kind of a
2023-07-30 18:29:35 +02:00
difference between two things:
2023-12-26 12:13:14 +01:00
- the _distance_ between two points,
- _duration_ between two time points,
- the difference in _speed_ (even if relative to zero).
2023-07-30 18:29:35 +02:00
2024-03-24 07:16:23 +01:00
As we already know, a `quantity` type provides all operations required for a _displacement vector_
2024-03-23 22:21:33 +09:00
abstraction in an affine space.
2023-07-30 18:29:35 +02:00
2023-12-21 12:25:09 +01:00
## _Point_ is modeled by `quantity_point` and `PointOrigin`
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
In the **mp-units** library, the _Point_ abstraction is modelled by:
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
- [`PointOrigin` concept ](concepts.md#PointOrigin ) that specifies measurement origin, and
2023-12-26 12:13:14 +01:00
- `quantity_point` class template that specifies a _Point_ relative to a specific predefined origin.
2023-07-30 18:29:35 +02:00
2023-12-03 16:15:38 +01:00
2023-08-23 15:43:19 +02:00
### `quantity_point`
2023-07-30 18:29:35 +02:00
2023-12-21 12:25:09 +01:00
The `quantity_point` class template specifies an absolute quantity measured from a predefined
origin:
2023-07-30 18:29:35 +02:00
```cpp
2023-08-23 15:43:19 +02:00
template< Reference auto R ,
2023-12-22 17:29:02 +01:00
PointOriginFor< get_quantity_spec ( R ) > auto PO = default_point_origin(R),
2023-08-23 15:43:19 +02:00
RepresentationOf< get_quantity_spec ( R ) . character > Rep = double>
class quantity_point;
2023-07-30 18:29:35 +02:00
```
2023-08-23 15:43:19 +02:00
As we can see above, the `quantity_point` class template exposes one additional parameter compared
2023-10-31 09:45:42 +01:00
to `quantity` . The `PO` parameter satisfies a [`PointOriginFor` concept ](concepts.md#PointOriginFor )
2024-03-23 22:21:33 +09:00
and specifies the origin of our measurement scale.
Each `quantity_point` internally stores a `quantity` object, which represents a _displacement vector_
from the predefined origin. Thanks to this, an instantiation of a `quantity_point` can be considered
as a model of a vector space from such an origin.
Forcing the user to manually predefine an origin for every domain may be cumbersome and discourage
users from using such abstractions at all. This is why, by default, the `PO` template
parameter is initialized with the `default_point_origin(R)` that provides the quantity points'
scale zeroth point using the following rules:
2023-12-21 12:25:09 +01:00
- if the measurement unit of a quantity specifies its point origin in its definition
2024-03-24 07:16:23 +01:00
(e.g., degree Celsius), then this origin is being used,
2023-12-22 17:29:02 +01:00
- otherwise, an instantiation of `zeroth_point_origin<QuantitySpec>` is being used which
2024-03-23 22:21:33 +09:00
provides a well-established zeroth point for a specific quantity type.
2023-07-30 18:29:35 +02:00
2023-08-31 18:56:43 +02:00
!!! tip
2023-12-21 12:25:09 +01:00
The `quantity_point` definition can be found in the `mp-units/quantity_point.h` header file.
2024-03-23 22:21:33 +09:00
#### `zeroth_point_origin<QuantitySpec>`
2023-08-31 18:56:43 +02:00
2024-03-23 22:21:33 +09:00
`zeroth_point_origin<QuantitySpec>` is meant to be used in cases where the specific domain has
2024-03-24 07:16:23 +01:00
a well-established, non-controversial, and unique zeroth point on the measurement scale.
This saves the user from the need to write a boilerplate code that would predefine such a type
for this domain.
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
{style="width:80%;display: block;margin: 0 auto;"}
2023-07-30 18:29:35 +02:00
```cpp
2024-03-23 22:21:33 +09:00
quantity_point< isq::distance [ si::metre ] > qp1{100 * m};
quantity_point< isq::distance [ si::metre ] > qp2{120 * m};
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
assert(qp1.quantity_from_zero() == 100 * m);
assert(qp2.quantity_from_zero() == 120 * m);
2024-03-24 07:16:23 +01:00
assert(qp2.quantity_from(qp1) == 20 * m);
assert(qp1.quantity_from(qp2) == -20 * m);
2023-08-03 13:08:09 +02:00
2024-03-23 22:21:33 +09:00
assert(qp2 - qp1 == 20 * m);
assert(qp1 - qp2 == -20 * m);
2023-08-03 13:08:09 +02:00
2024-03-23 22:21:33 +09:00
// auto res = qp1 + qp2; // Compile-time error
2023-12-21 12:25:09 +01:00
```
2024-03-23 22:21:33 +09:00
In the above code `100 * m` and `120 * m` still create two quantities that serve as _displacement
vectors_ here. Quantity point objects can be explicitly constructed from such quantities only when
their origin is an instantiation of the `zeroth_point_origin<QuantitySpec>` .
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
It is really important to understand that even though we can use `.quantity_from_zero()` to obtain
the _displacement vector_ of a point from the origin, the point by itself does not represent or have
any associated physical value. It is just a point in some space. The same point can be expressed
with different _displacement vectors_ from different origins.
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
It is also worth mentioning that simplicity comes with a safety cost here. For some users, it
might be surprising that the usage of `zeroth_point_origin<QuantitySpec>` makes various quantity
point objects compatible as long as quantity types used in the origin and reference are
compatible:
2023-08-03 13:08:09 +02:00
2023-08-23 15:43:19 +02:00
```cpp
2024-03-23 22:21:33 +09:00
quantity_point< si::metre > qp1{isq::distance(100 * m)};
quantity_point< si::metre > qp2{isq::height(120 * m)};
2023-07-30 18:29:35 +02:00
2024-03-24 07:16:23 +01:00
assert(qp2.quantity_from(qp1) == 20 * m);
assert(qp1.quantity_from(qp2) == -20 * m);
2024-03-23 22:21:33 +09:00
assert(qp2 - qp1 == 20 * m);
assert(qp1 - qp2 == -20 * m);
2023-08-23 15:43:19 +02:00
```
2023-08-03 13:08:09 +02:00
2024-03-23 22:21:33 +09:00
### Absolute _point_ origin
2023-08-03 13:08:09 +02:00
2024-03-23 22:21:33 +09:00
In cases where we want to implement an isolated independent space in which points are not compatible
with other spaces, even of the same quantity type, we should manually predefine an absolute point
origin.
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
{style="width:80%;display: block;margin: 0 auto;"}
2023-08-03 13:08:09 +02:00
```cpp
2024-03-23 22:21:33 +09:00
inline constexpr struct origin : absolute_point_origin< origin , isq::distance > {} origin;
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
// quantity_point< si::metre , origin > qp1{100 * m}; // Compile-time error
// quantity_point< si::metre , origin > qp2{120 * m}; // Compile-time error
quantity_point< si::metre , origin > qp1 = origin + 100 * m;
quantity_point< si::metre , origin > qp2 = 120 * m + origin;
2023-12-21 12:25:09 +01:00
2024-03-23 23:55:32 +09:00
// assert(qp1.quantity_from_zero() == 100 * m); // Compile-time error
2024-03-23 22:21:33 +09:00
// assert(qp2.quantity_from_zero() == 120 * m); // Compile-time error
assert(qp1.quantity_from(origin) == 100 * m);
assert(qp2.quantity_from(origin) == 120 * m);
2024-03-24 07:16:23 +01:00
assert(qp2.quantity_from(qp1) == 20 * m);
assert(qp1.quantity_from(qp2) == -20 * m);
2023-08-03 13:08:09 +02:00
2024-03-23 22:21:33 +09:00
assert(qp1 - origin == 100 * m);
assert(qp2 - origin == 120 * m);
2024-03-24 07:16:23 +01:00
assert(qp2 - qp1 == 20 * m);
assert(qp1 - qp2 == -20 * m);
2024-03-23 22:21:33 +09:00
assert(origin - qp1 == -100 * m);
assert(origin - qp2 == -120 * m);
2023-12-21 12:25:09 +01:00
2024-03-24 07:16:23 +01:00
// assert(origin - origin == 0 * m); // Compile-time error
2023-12-21 12:25:09 +01:00
```
2023-08-03 13:08:09 +02:00
2024-03-24 07:16:23 +01:00
!!! info
The `absolute_point_origin` class template uses the CRTP idiom to enforce the uniqueness of
such a type. You should pass the type of a derived class as the first argument of the template
instantiation.
*[CRTP]: Curiously Recurring Template Parameter
We can't construct a quantity point directly from the quantity anymore when a custom, named origin
is used. To prevent potential safety and maintenance issues, we always need to
explicitly provide both a compatible origin and a quantity measured from it to construct a quantity
point.
Said otherwise, a quantity point defined in terms of a specific origin is the result of adding
the origin and the _displacement vector_ measured from it to the point we create.
2023-08-03 13:08:09 +02:00
2023-12-21 12:25:09 +01:00
!!! info
2023-07-30 18:29:35 +02:00
2023-12-21 12:25:09 +01:00
A rationale for this longer construction syntax can be found in the
[Why can't I create a quantity by passing a number to a constructor? ](../../getting_started/faq.md#why-cant-i-create-a-quantity-by-passing-a-number-to-a-constructor )
chapter.
2023-07-30 18:29:35 +02:00
2023-12-21 12:25:09 +01:00
Similarly to [creation of a quantity ](../../getting_started/quick_start.md#creating-a-quantity ),
if someone does not like the operator-based syntax to create a `quantity_point` , the same results
can be achieved with a two-parameter constructor:
2023-08-03 13:08:09 +02:00
2023-12-21 12:25:09 +01:00
```cpp
2024-03-23 22:21:33 +09:00
quantity_point qp1{100 * m, origin};
2023-12-21 12:25:09 +01:00
```
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
Again, CTAD always helps to use precisely the type we need in a current case.
2023-08-03 13:17:51 +02:00
2024-03-24 07:16:23 +01:00
Additionally, if a quantity point is defined in terms of a custom, named origin, then we can't use
a `quantity_from_zero()` member function anymore. This is to prevent surprises, as our origin may
not necessarily be perceived as an absolute zero in the domain we model. Also, as we will learn soon,
we can define several related origins in one space, and then it gets harder to understand which
one is the "zero" one. This is why, to be specific and always correct about the points we use,
a `quantity_from(QP)` member function can be used (where `QP` can either be an origin or another
quantity point).
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
Finally, please note that it is not allowed to subtract two point origins defined in terms of
`absolute_point_origin` (e.g., `origin - origin` ) as those do not contain information about the
unit, so we cannot determine a resulting `quantity` type.
2023-07-30 18:29:35 +02:00
2024-03-24 07:16:23 +01:00
#### Modeling independent spaces in one domain
2024-03-23 22:21:33 +09:00
Absolute point origins are also perfect for establishing independent spaces even if the same quantity
type and unit is being used:
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
{style="width:80%;display: block;margin: 0 auto;"}
2023-07-30 18:29:35 +02:00
```cpp
2024-03-23 22:21:33 +09:00
inline constexpr struct origin1 : absolute_point_origin< origin1 , isq::distance > {} origin1;
inline constexpr struct origin2 : absolute_point_origin< origin2 , isq::distance > {} origin2;
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
quantity_point qp1 = origin1 + 100 * m;
quantity_point qp2 = origin2 + 120 * m;
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
assert(qp1.quantity_from(origin1) == 100 * m);
assert(qp2.quantity_from(origin2) == 120 * m);
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
assert(qp1 - origin1 == 100 * m);
assert(qp2 - origin2 == 120 * m);
assert(origin1 - qp1 == -100 * m);
assert(origin2 - qp2 == -120 * m);
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
// assert(qp2 - qp1 == 20 * m); // Compile-time error
// assert(qp1 - origin2 == 100 * m); // Compile-time error
// assert(qp2 - origin1 == 120 * m); // Compile-time error
2024-03-24 07:16:23 +01:00
// assert(qp2.quantity_from(qp1) == 20 * m); // Compile-time error
2024-03-23 22:21:33 +09:00
// assert(qp1.quantity_from(origin2) == 100 * m); // Compile-time error
// assert(qp2.quantity_from(origin1) == 120 * m); // Compile-time error
2023-07-30 18:29:35 +02:00
```
2023-12-26 12:13:14 +01:00
### Relative _Point_ origin
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
We often do not have only one ultimate "zero" point when we measure things. Often, we have one
common scale, but we measure various quantities relative to different points and expect
those points to be compatible. There are many examples here, but probably the most common are
temperatures, timestamps, and altitudes.
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
For such cases, relative point origins should be used:
2023-08-23 15:43:19 +02:00
2024-03-23 22:21:33 +09:00
{style="width:80%;display: block;margin: 0 auto;"}
2023-12-21 12:25:09 +01:00
```cpp
2024-03-23 22:21:33 +09:00
inline constexpr struct A : absolute_point_origin< A , isq::distance > {} A;
inline constexpr struct B : relative_point_origin< A + 10 * m > {} B;
inline constexpr struct C : relative_point_origin< B + 10 * m > {} C;
inline constexpr struct D : relative_point_origin< A + 30 * m > {} D;
quantity_point qp1 = C + 100 * m;
quantity_point qp2 = D + 120 * m;
assert(qp1.quantity_ref_from(qp1.point_origin) == 100 * m);
assert(qp2.quantity_ref_from(qp2.point_origin) == 120 * m);
2024-03-24 07:16:23 +01:00
assert(qp2.quantity_from(qp1) == 30 * m);
assert(qp1.quantity_from(qp2) == -30 * m);
assert(qp2 - qp1 == 30 * m);
assert(qp1 - qp2 == -30 * m);
2024-03-23 22:21:33 +09:00
assert(qp1.quantity_from(A) == 120 * m);
assert(qp1.quantity_from(B) == 110 * m);
assert(qp1.quantity_from(C) == 100 * m);
assert(qp1.quantity_from(D) == 90 * m);
2024-03-24 07:16:23 +01:00
assert(qp1 - A == 120 * m);
assert(qp1 - B == 110 * m);
assert(qp1 - C == 100 * m);
assert(qp1 - D == 90 * m);
2024-03-23 22:21:33 +09:00
assert(qp2.quantity_from(A) == 150 * m);
assert(qp2.quantity_from(B) == 140 * m);
assert(qp2.quantity_from(C) == 130 * m);
assert(qp2.quantity_from(D) == 120 * m);
2024-03-24 07:16:23 +01:00
assert(qp2 - A == 150 * m);
assert(qp2 - B == 140 * m);
assert(qp2 - C == 130 * m);
assert(qp2 - D == 120 * m);
2024-03-23 22:21:33 +09:00
assert(B - A == 10 * m);
assert(C - A == 20 * m);
assert(D - A == 30 * m);
assert(D - C == 10 * m);
2024-03-24 07:16:23 +01:00
assert(B - B == 0 * m);
// assert(A - A == 0 * m); // Compile-time error
2023-08-23 15:43:19 +02:00
```
2024-03-24 07:16:23 +01:00
!!! note
2023-07-30 18:29:35 +02:00
2024-03-24 07:16:23 +01:00
Even though we can't subtract two absolute point origins from each other, it is possible to
subtract relative ones or relative and absolute ones.
2023-07-30 18:29:35 +02:00
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
### Converting between different representations of the same _point_
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
As we might represent the same _point_ with _displacement vectors_ from various origins, the
library provides facilities to convert the same _point_ to the `quantity_point` class templates
expressed in terms of different origins.
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
{style="width:80%;display: block;margin: 0 auto;"}
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
For this purpose, we can use either:
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
- A converting constructor:
2023-12-21 12:25:09 +01:00
```cpp
2024-03-23 22:21:33 +09:00
quantity_point< si::metre , C > qp2C = qp2;
assert(qp2C.quantity_ref_from(qp2C.point_origin) == 130 * m);
2023-12-21 12:25:09 +01:00
```
2024-03-23 22:21:33 +09:00
- A dedicated conversion interface:
2023-12-21 12:25:09 +01:00
```cpp
2024-03-23 22:21:33 +09:00
quantity_point qp2B = qp2.point_for(B);
quantity_point qp2A = qp2.point_for(A);
assert(qp2B.quantity_ref_from(qp2B.point_origin) == 140 * m);
assert(qp2A.quantity_ref_from(qp2A.point_origin) == 150 * m);
2023-12-21 12:25:09 +01:00
```
2024-03-24 07:16:23 +01:00
It is important to understand that all such translations still describe exactly the same point
(e.g., all of them compare equal):
2024-03-23 22:21:33 +09:00
```cpp
assert(qp2 == qp2C);
assert(qp2 == qp2B);
assert(qp2 == qp2A);
```
2024-03-24 07:16:23 +01:00
!!! important
2023-12-21 12:25:09 +01:00
It is only allowed to convert between various origins defined in terms of the same
2024-03-23 22:21:33 +09:00
`absolute_point_origin` . Even if it is possible to express the same _point_ as a
_displacement vector_ from another `absolute_point_origin` , the library will not provide such
2024-03-24 07:16:23 +01:00
a conversion. A custom user-defined conversion function will be needed to add such a
functionality.
2023-12-21 12:25:09 +01:00
2024-03-23 22:21:33 +09:00
Said another way, in the library, there is no way to spell how two distinct `absolute_point_origin`
types relate to each other.
2023-12-21 12:25:09 +01:00
### Temperature support
2024-03-23 22:21:33 +09:00
Support for temperature quantity points is probably one of the most common examples of relative
point origins in action that we use in daily life.
2024-03-24 07:16:23 +01:00
The [SI ](../../appendix/references.md#SIBrochure ) definition in the library provides a few predefined
point origins for this purpose:
2023-12-21 12:25:09 +01:00
```cpp
namespace si {
inline constexpr struct absolute_zero : absolute_point_origin< absolute_zero , isq::thermodynamic_temperature > {} absolute_zero;
inline constexpr struct zeroth_kelvin : decltype(absolute_zero) {} zeroth_kelvin;
2024-01-05 11:56:55 +01:00
inline constexpr struct ice_point : relative_point_origin< quantity_point { 273 ' 150 * milli < kelvin > }> {} ice_point;
2023-12-21 12:25:09 +01:00
inline constexpr struct zeroth_degree_Celsius : decltype(ice_point) {} zeroth_degree_Celsius;
}
namespace usc {
inline constexpr struct zeroth_degree_Fahrenheit :
2024-04-19 15:29:00 +01:00
relative_point_origin< si::zeroth_degree_Celsius - 32 * (mag_ratio<5, 9> * si::degree_Celsius ) > {} zeroth_degree_Fahrenheit;
2023-12-21 12:25:09 +01:00
}
```
The above is a great example of how point origins can be stacked on top of each other:
- `usc::zeroth_degree_Fahrenheit` is defined relative to `si::zeroth_degree_Celsius`
- `si::zeroth_degree_Celsius` is defined relative to `si::zeroth_kelvin` .
2023-07-30 18:29:35 +02:00
!!! note
2024-03-24 07:16:23 +01:00
Notice that while stacking point origins, we can use different representation types and units
for origins and a _point_ . In the above example, the relative point origin for degree Celsius
is defined in terms of `si::kelvin` , while the quantity point for it will use
`si::degree_Celsius` as a unit.
2023-12-21 12:25:09 +01:00
The temperature point origins defined above are provided explicitly in the respective units'
definitions:
```cpp
namespace si {
inline constexpr struct kelvin :
named_unit< "K", kind_of< isq::thermodynamic_temperature > , zeroth_kelvin> {} kelvin;
inline constexpr struct degree_Celsius :
2024-02-16 22:13:13 +01:00
named_unit< {u8"°C", "`C"}, kelvin, zeroth_degree_Celsius> {} degree_Celsius;
2023-07-30 18:29:35 +02:00
2023-12-21 12:25:09 +01:00
}
namespace usc {
inline constexpr struct degree_Fahrenheit :
2024-04-19 15:29:00 +01:00
named_unit< {u8"°F", "`F"}, mag_ratio< 5 , 9 > * si::degree_Celsius,
2024-03-23 22:21:33 +09:00
zeroth_degree_Fahrenheit> {} degree_Fahrenheit;
2023-12-21 12:25:09 +01:00
}
```
2024-03-24 07:16:23 +01:00
As it was described above, `default_point_origin(R)` returns a `zeroth_point_origin<QuantitySpec>`
when a unit does not provide any origin in its definition. As of today, the units of temperature
are the only ones in the entire **mp-units** library that provide such origins.
2024-03-23 22:21:33 +09:00
Now, let's see how we can benefit from the above definitions. We have quite a few alternatives to
choose from here. Depending on our needs or tastes, we can:
2023-12-21 12:25:09 +01:00
- be explicit about the unit and origin:
```cpp
quantity_point< si::degree_Celsius , si::zeroth_degree_Celsius > q1 = si::zeroth_degree_Celsius + 20.5 * deg_C;
quantity_point< si::degree_Celsius , si::zeroth_degree_Celsius > q2 = {20.5 * deg_C, si::zeroth_degree_Celsius};
quantity_point< si::degree_Celsius , si::zeroth_degree_Celsius > q3{20.5 * deg_C};
```
- specify a unit and use its zeroth point origin implicitly:
```cpp
quantity_point< si::degree_Celsius > q4 = si::zeroth_degree_Celsius + 20.5 * deg_C;
quantity_point< si::degree_Celsius > q5 = {20.5 * deg_C, si::zeroth_degree_Celsius};
quantity_point< si::degree_Celsius > q6{20.5 * deg_C};
```
- benefit from CTAD:
```cpp
quantity_point q7 = si::zeroth_degree_Celsius + 20.5 * deg_C;
quantity_point q8 = {20.5 * deg_C, si::zeroth_degree_Celsius};
quantity_point q9{20.5 * deg_C};
```
In all of the above cases, we end up with the `quantity_point` of the same type and value.
To play a bit more with temperatures, we can implement a simple room AC temperature controller in
2023-08-23 15:43:19 +02:00
the following way:
2023-07-30 18:29:35 +02:00
2024-03-23 22:21:33 +09:00
{style="width:80%;display: block;margin: 0 auto;"}
2023-07-30 18:29:35 +02:00
```cpp
2023-12-21 12:25:09 +01:00
constexpr struct room_reference_temp : relative_point_origin< quantity_point { 21 * deg_C } > {} room_reference_temp;
2023-08-23 15:43:19 +02:00
using room_temp = quantity_point< isq::Celsius_temperature [ deg_C ] , room_reference_temp > ;
2023-07-30 18:29:35 +02:00
2023-08-23 15:43:19 +02:00
constexpr auto step_delta = isq::Celsius_temperature(0.5 * deg_C);
2023-07-30 18:29:35 +02:00
constexpr int number_of_steps = 6;
2023-12-21 12:25:09 +01:00
room_temp room_ref{};
room_temp room_low = room_ref - number_of_steps * step_delta;
room_temp room_high = room_ref + number_of_steps * step_delta;
2023-08-23 15:43:19 +02:00
2024-04-18 22:37:24 +01:00
std::println("Room reference temperature: {} ({}, {::N[.2f]})\n",
2023-12-21 12:25:09 +01:00
room_ref.quantity_from_zero(),
room_ref.in(usc::degree_Fahrenheit).quantity_from_zero(),
room_ref.in(si::kelvin).quantity_from_zero());
2024-03-24 07:16:23 +01:00
std::println("| {:< 18 } | { : ^ 18 } | { : ^ 18 } | { : ^ 18 } | " ,
"Temperature delta", "Room reference", "Ice point", "Absolute zero");
std::println("|{0:=^20}|{0:=^20}|{0:=^20}|{0:=^20}|", "");
2023-08-23 15:43:19 +02:00
2024-03-24 07:16:23 +01:00
auto print_temp = [& ](std::string_view label, auto v ) {
2024-04-17 14:20:15 +01:00
std::println("| {:< 14 } | { : ^ 18 } | { : ^ 18 } | { : ^ 18:N [ . 2f ] } | " , label ,
2024-01-27 22:47:33 +01:00
v - room_reference_temp, (v - si::ice_point).in(deg_C), (v - si::absolute_zero).in(deg_C));
2023-08-23 15:43:19 +02:00
};
2023-07-30 18:29:35 +02:00
2024-03-24 07:16:23 +01:00
print_temp("Lowest", room_low);
print_temp("Default", room_ref);
print_temp("Highest", room_high);
2023-07-30 18:29:35 +02:00
```
The above prints:
```text
2023-12-21 12:25:09 +01:00
Room reference temperature: 21 °C (69.8 °F, 294.15 K)
2024-03-24 07:16:23 +01:00
| Temperature delta | Room reference | Ice point | Absolute zero |
|====================|====================|====================|====================|
| Lowest | -3 °C | 18 °C | 291.15 °C |
| Default | 0 °C | 21 °C | 294.15 °C |
| Highest | 3 °C | 24 °C | 297.15 °C |
2023-07-30 18:29:35 +02:00
```
2023-12-26 12:13:14 +01:00
### No text output for _Points_
2023-08-04 14:35:20 +02:00
2024-03-23 22:21:33 +09:00
The library does not provide a text output for quantity points. The quantity stored inside
is just an implementation detail of this type. It is a vector from a specific origin.
Without the knowledge of the origin, the vector by itself is useless as we can't determine
which point it describes.
In the current library design, point origin does not provide any text in its definition.
Even if we could add such information to the point's definition, we would not
know how to output it in the text. There may be many ways to do it. For example, should we
prepend or append the origin part to the quantity text?
2023-08-04 14:35:20 +02:00
2024-03-23 22:21:33 +09:00
For example, the text output of `42 m` for a quantity point may mean many things. It may be
an offset from the mountain top, sea level, or maybe the center of Mars.
Printing `42 m AMSL` for altitudes above mean sea level is a much better solution, but the
library does not have enough information to print it that way by itself.
2023-08-04 14:35:20 +02:00
2023-07-30 18:29:35 +02:00
## The affine space is about type-safety
The following operations are not allowed in the affine space:
2023-08-23 15:43:19 +02:00
- **adding** two `quantity_point` objects
- It is physically impossible to add positions of home and Denver airports.
- **subtracting** a `quantity_point` from a `quantity`
2023-12-08 12:57:08 +01:00
- What would it mean to subtract the DEN airport location from the distance to it?
2023-08-23 15:43:19 +02:00
- **multiplying/dividing** a `quantity_point` with a scalar
- What is the position of `2 *` DEN airport location?
- **multiplying/dividing** a `quantity_point` with a quantity
- What would multiplying the distance with the DEN airport location mean?
- **multiplying/dividing** two `quantity_point` objects
- What would multiplying home and DEN airport location mean?
- **mixing** `quantity_points` of different quantity kinds
- It is physically impossible to subtract time from length.
- **mixing** `quantity_points` of inconvertible quantities
2023-12-08 12:57:08 +01:00
- What does subtracting a distance point to DEN airport from the Mount Everest base camp
altitude mean?
2023-08-23 15:43:19 +02:00
- **mixing** `quantity_points` of convertible quantities but with unrelated origins
2023-12-08 12:57:08 +01:00
- How do we subtract a point on our trip to CppCon measured relatively to our home location from
2023-08-23 15:43:19 +02:00
a point measured relative to the center of the Solar System?
2023-07-30 18:29:35 +02:00
2023-10-25 14:14:26 +02:00
!!! important "Important: The affine space improves safety"
2023-07-30 18:29:35 +02:00
2023-12-08 12:57:08 +01:00
The usage of `quantity_point` and affine space types, in general, improves expressiveness and
2023-07-30 18:29:35 +02:00
type-safety of the code we write.