mirror of
https://github.com/mpusz/mp-units.git
synced 2025-08-01 03:14:29 +02:00
feat: 💥 delta
and absolute
construction helpers
This commit is contained in:
@@ -37,7 +37,7 @@ std::cout << Pressure << "\n";
|
||||
|
||||
The problem is related to the accidental usage of a `quantity` rather than `quantity_point` for
|
||||
`Temperature`. This means that after conversion to kelvins, we will get `28 K` instead of
|
||||
the expected `301.15 K`, which will corrupt all further calculations.
|
||||
the expected `301.15 K`, corrupting all further calculations.
|
||||
|
||||
A correct code should use a `quantity_point`:
|
||||
|
||||
@@ -48,51 +48,68 @@ quantity_point Temperature(28.0 * deg_C);
|
||||
This might be an obvious thing for domain experts, but new users of the library may not be aware
|
||||
of the affine space abstractions and how they influence temperature handling.
|
||||
|
||||
After a lengthy discussion on handling such scenarios, we decided to aid the `quantity` and
|
||||
`quantity_point` construction with `absolute<Reference>` and `delta<Reference>` quantity reference
|
||||
specifiers. This applies to:
|
||||
After a lengthy discussion on handling such scenarios, we decided to:
|
||||
|
||||
- the multiply syntax,
|
||||
- 2-parameter `quantity` constructor.
|
||||
- make the above code ill-formed,
|
||||
- provide an alternative way to create a `quantity` with the `delta` quantity construction helper.
|
||||
|
||||
Here are the main points of this new design:
|
||||
|
||||
1. All references/units that do not specify point origin (are not offset units) in their definition
|
||||
are considered `delta` by default. This means that `42 * m` creates a `quantity` and is
|
||||
the same as calling `42 * delta<m>`.
|
||||
2. Multiply syntax is extended to allow `quantity_point` creation with the `42 * absolute<m>`
|
||||
syntax. This will provide an implicit zeroth point origin.
|
||||
3. For units that specify a point origin (`si::kelvin`, `si::degree_Celsius`, and
|
||||
`usc::degree_Fahrenheit`), the user always needs to specify a modifier. This means that:
|
||||
- `4 * deg_C` does not compile,
|
||||
- `4 * delta<deg_C>` creates a `quantity`.
|
||||
- `4 * absolute<deg_C>` creates a `quantity_point`.
|
||||
4. The 2-parameter `quantity` constructor requires the same:
|
||||
1. All references/units that specify point origin in their definition (i.e., `si::kelvin`,
|
||||
`si::degree_Celsius`, and `usc::degree_Fahrenheit`) are excluded from the multiply syntax
|
||||
(:boom: **breaking change** :boom:).
|
||||
2. A new `delta` quantity construction helper is introduced:
|
||||
|
||||
```cpp
|
||||
quantity q1(4, m); // OK
|
||||
quantity q2(4, delta<m>); // OK
|
||||
quantity q3(4, absolute<m>); // Compile-time error
|
||||
quantity q4(4, deg_C); // Compile-time error
|
||||
quantity q5(4, delta<deg_C>); // OK
|
||||
quantity q6(4, absolute<deg_C>); // Compile-time error
|
||||
```
|
||||
- `delta<m>(42)` results with a `quantity<si::metre, int>`,
|
||||
- `delta<deg_C>(5)` results with a `quantity<si::deg_C, int>`.
|
||||
|
||||
The `delta` and `absolute` modifiers are stripped upon construction, so the resulting `quantity`
|
||||
and `quantity_point` types use the underlying unit in its type.
|
||||
3. A new `absolute` quantity point construction helper is introduced:
|
||||
|
||||
With such changes, the offending code will not compile, forcing the user to think more about what
|
||||
is written. To enable the compilation, the user has to type one of the following:
|
||||
- `absolute<m>(42)` results with a `quantity_point<si::metre, zeroth_point_origin<kind_of<isq::length>>{}, int>`,
|
||||
- `absolute<deg_C>(5)` results with a `quantity<si::metre, si::ice_point, int>`.
|
||||
|
||||
- `quantity_point Temperature(28.0 * delta<deg_C>);`
|
||||
- `quantity_point Temperature = 28.0 * absolute<deg_C>;`
|
||||
!!! info
|
||||
|
||||
If the user still insists on using `quantity` instead of a `quantity_point`, the code will
|
||||
have to be written in the following way to compile successfully:
|
||||
Please note that `si::kelvin` is also excluded from the multiply syntax to prevent the
|
||||
following surprising issues:
|
||||
|
||||
```cpp
|
||||
quantity Temperature = 28.0 * delta<deg_C>;
|
||||
```
|
||||
=== "Now"
|
||||
|
||||
This will yield an invalid result, but now, hopefully, it is clearly readable in the code what is
|
||||
wrong here.
|
||||
```cpp
|
||||
quantity q = delta<K>(300);
|
||||
quantity_point qp = absolute<K>(300);
|
||||
static_assert(q.in(deg_C) != qp.in(deg_C).quantity_from_zero());
|
||||
```
|
||||
|
||||
=== "Before"
|
||||
|
||||
```cpp
|
||||
quantity q(300 * K);
|
||||
quantity_point qp(300 * K);
|
||||
static_assert(q.in(deg_C) != qp.in(deg_C).quantity_from_zero());
|
||||
```
|
||||
|
||||
We believe that the code enforced with new utilities makes it much easier to understand what
|
||||
happens here.
|
||||
|
||||
With such changes to the interface design, the offending code will not compile as initially written.
|
||||
Users will be forced to think more about what they write. To enable the compilation, the users have
|
||||
to explicitly create a:
|
||||
|
||||
- `quantity_point` (the intended abstraction in this example) with any of the below syntaxes:
|
||||
|
||||
```cpp
|
||||
quantity_point Temperature = absolute<deg_C>(28.0);
|
||||
auto Temperature = absolute<deg_C>(28.0);
|
||||
quantity_point Temperature(delta<deg_C>(28.0));
|
||||
```
|
||||
|
||||
- `quantity` (an incorrect abstraction in this example) with:
|
||||
|
||||
```cpp
|
||||
quantity Temperature = delta<deg_C>(28.0);
|
||||
auto Temperature = delta<deg_C>(28.0);
|
||||
```
|
||||
|
||||
Thanks to the new design, we can immediately see what happens here and why the result might be
|
||||
incorrect in the second case.
|
||||
|
@@ -43,28 +43,51 @@ a number with a predefined unit:
|
||||
!!! info
|
||||
|
||||
In case someone doesn't like the multiply syntax or there is an ambiguity between `operator*`
|
||||
provided by this and other libraries, a quantity can also be created with a two-parameter
|
||||
constructor:
|
||||
provided by this and other libraries, there are two other ways to create a quantity:
|
||||
|
||||
=== "C++ modules"
|
||||
1. `delta` construction helper:
|
||||
|
||||
```cpp
|
||||
import mp_units;
|
||||
=== "C++ modules"
|
||||
|
||||
using namespace mp_units;
|
||||
```cpp
|
||||
import mp_units;
|
||||
|
||||
quantity q{42, si::metre / si::second};
|
||||
```
|
||||
using namespace mp_units;
|
||||
|
||||
=== "Header files"
|
||||
quantity q = delta<si::metre / si::second>(42);
|
||||
```
|
||||
|
||||
```cpp
|
||||
#include <mp-units/systems/si.h>
|
||||
=== "Header files"
|
||||
|
||||
using namespace mp_units;
|
||||
```cpp
|
||||
#include <mp-units/systems/si.h>
|
||||
|
||||
quantity q{42, si::metre / si::second};
|
||||
```
|
||||
using namespace mp_units;
|
||||
|
||||
quantity q = delta<si::metre / si::second>(42);
|
||||
```
|
||||
|
||||
2. A two-parameter constructor:
|
||||
|
||||
=== "C++ modules"
|
||||
|
||||
```cpp
|
||||
import mp_units;
|
||||
|
||||
using namespace mp_units;
|
||||
|
||||
quantity q{42, si::metre / si::second};
|
||||
```
|
||||
|
||||
=== "Header files"
|
||||
|
||||
```cpp
|
||||
#include <mp-units/systems/si.h>
|
||||
|
||||
using namespace mp_units;
|
||||
|
||||
quantity q{42, si::metre / si::second};
|
||||
```
|
||||
|
||||
The above creates an instance of `quantity<derived_unit<si::metre, per<si::second>>{}, int>`.
|
||||
The same can be obtained using optional unit symbols:
|
||||
@@ -238,7 +261,7 @@ This introduces an additional type-safety.
|
||||
using namespace mp_units::si::unit_symbols;
|
||||
using namespace mp_units::usc::unit_symbols;
|
||||
|
||||
quantity_point temp = 20. * absolute<deg_C>;
|
||||
quantity_point temp = absolute<deg_C>(20.);
|
||||
std::println("Temperature: {} ({})",
|
||||
temp.quantity_from_zero(),
|
||||
temp.in(deg_F).quantity_from_zero());
|
||||
@@ -259,7 +282,7 @@ This introduces an additional type-safety.
|
||||
using namespace mp_units::si::unit_symbols;
|
||||
using namespace mp_units::usc::unit_symbols;
|
||||
|
||||
quantity_point temp = 20. * absolute<deg_C>;
|
||||
quantity_point temp = absolute<deg_C>(20.);
|
||||
std::println("Temperature: {} ({})",
|
||||
temp.quantity_from_zero(),
|
||||
temp.in(deg_F).quantity_from_zero());
|
||||
|
@@ -378,5 +378,5 @@ For example:
|
||||
the previous example:
|
||||
|
||||
```cpp
|
||||
constexpr auto room_reference_temperature = ice_point + isq::Celsius_temperature(21 * delta<deg_C>);
|
||||
constexpr auto room_reference_temperature = ice_point + delta<isq::Celsius_temperature[deg_C]>(21);
|
||||
```
|
||||
|
@@ -68,51 +68,17 @@ difference between two things:
|
||||
- the difference in _speed_ (even if relative to zero).
|
||||
|
||||
As we already know, a `quantity` type provides all operations required for a _displacement vector_
|
||||
abstraction in an affine space.
|
||||
abstraction in the affine space. It can be constructed with:
|
||||
|
||||
Quantities are constructed from a delta quantity reference. Most of units are considered to be
|
||||
delta references by default. The ones that need a special qualification are the units that
|
||||
get a point origin in their definition (i.e., units of temperature).
|
||||
|
||||
We can create a `quantity` by passing a delta quantity reference to either:
|
||||
|
||||
- two-parameter constructor:
|
||||
|
||||
```cpp
|
||||
quantity q1(42, si::metre);
|
||||
// quantity q2(42, si::kelvin); // Compile-time error
|
||||
// quantity q3(42, si::degree_Celsius); // Compile-time error
|
||||
// quantity q4(42, usc::degree_Fahrenheit); // Compile-time error
|
||||
quantity q5(42, delta<si::metre>);
|
||||
quantity q6(42, delta<si::kelvin>);
|
||||
quantity q7(42, delta<si::degree_Celsius>);
|
||||
quantity q8(42, delta<usc::degree_Fahrenheit>);
|
||||
```
|
||||
|
||||
- multiply syntax:
|
||||
|
||||
```cpp
|
||||
quantity q1 = 42 * m;
|
||||
// quantity q2 = 42 * K; // Compile-time error
|
||||
// quantity q3 = 42 * deg_C; // Compile-time error
|
||||
// quantity q4 = 42 * deg_F; // Compile-time error
|
||||
quantity q5 = 42 * delta<m>;
|
||||
quantity q6 = 42 * delta<K>;
|
||||
quantity q7 = 42 * delta<deg_C>;
|
||||
quantity q8 = 42 * delta<deg_F>;
|
||||
```
|
||||
- the multiply syntax (works for most of the units),
|
||||
- `delta<Reference>` construction helper (e.g., `delta<isq::height[m]>(42)`, `delta<deg_C>(3)`),
|
||||
- two-parameter constructor taking a number and a quantity reference/unit.
|
||||
|
||||
!!! note
|
||||
|
||||
`delta` specifier is used to qualify the entire reference upon `quantity` construction.
|
||||
It does not satisfy the [`Reference`](concepts.md#Reference) concept. This means that,
|
||||
for example, the below are ill-formed:
|
||||
The multiply syntax support is disabled for units that provide a point origin in their
|
||||
definition (i.e., units of temperature like `K`, `deg_C`, and `deg_F`).
|
||||
|
||||
```cpp
|
||||
void foo(quantity<delta<si::degree_Celsius>> temp); // ill-formed
|
||||
quantity<N * m / (delta<deg_C> * mol)> specific_heat_capacity; // ill-formed
|
||||
quantity R = 8.314 * N * m / (delta<deg_C> * mol); // ill-formed
|
||||
```
|
||||
|
||||
## _Point_ is modeled by `quantity_point` and `PointOrigin`
|
||||
|
||||
@@ -152,17 +118,19 @@ scale zeroth point using the following rules:
|
||||
- otherwise, an instantiation of `zeroth_point_origin<QuantitySpec>` is being used which
|
||||
provides a well-established zeroth point for a specific quantity type.
|
||||
|
||||
Quantity points with default point origins may be constructed using multiply syntax from an
|
||||
absolute quantity reference. None of units are considered to be absolute references by default,
|
||||
so they need a special qualification:
|
||||
Quantity points with default point origins may be constructed with the `absolute` construction
|
||||
helper or forcing an explicit conversion from the `quantity`:
|
||||
|
||||
```cpp
|
||||
// quantity_point qp1 = 42 * m; // Compile-time error
|
||||
// quantity_point qp2 = 42 * K; // Compile-time error
|
||||
// quantity_point qp3 = 42 * deg_C; // Compile-time error
|
||||
quantity_point qp4 = 42 * absolute<m>;
|
||||
quantity_point qp5 = 42 * absolute<K>;
|
||||
quantity_point qp6 = 42 * absolute<deg_C>;
|
||||
// quantity_point qp1 = 42 * m; // Compile-time error
|
||||
// quantity_point qp2 = 42 * K; // Compile-time error
|
||||
// quantity_point qp3 = delta<deg_C>(42); // Compile-time error
|
||||
quantity_point qp4(42 * m);
|
||||
quantity_point qp5(42 * K);
|
||||
quantity_point qp6(delta<deg_C>(42));
|
||||
quantity_point qp7 = absolute<m>(42);
|
||||
quantity_point qp8 = absolute<K>(42);
|
||||
quantity_point qp9 = absolute<deg_C>(42);
|
||||
```
|
||||
|
||||
!!! tip
|
||||
@@ -180,8 +148,8 @@ for this domain.
|
||||
{style="width:80%;display: block;margin: 0 auto;"}
|
||||
|
||||
```cpp
|
||||
quantity_point<isq::distance[si::metre]> qp1 = 100 * absolute<m>;
|
||||
quantity_point<isq::distance[si::metre]> qp2 = 120 * absolute<m>;
|
||||
quantity_point<isq::distance[si::metre]> qp1(100 * m);
|
||||
quantity_point<isq::distance[si::metre]> qp2 = absolute<m>(120);
|
||||
|
||||
assert(qp1.quantity_from_zero() == 100 * m);
|
||||
assert(qp2.quantity_from_zero() == 120 * m);
|
||||
@@ -210,7 +178,7 @@ compatible:
|
||||
|
||||
```cpp
|
||||
quantity_point<si::metre> qp1{isq::distance(100 * m)};
|
||||
quantity_point<si::metre> qp2{isq::height(120 * m)};
|
||||
quantity_point<si::metre> qp2 = absolute<isq::height[m]>(120);
|
||||
|
||||
assert(qp2.quantity_from(qp1) == 20 * m);
|
||||
assert(qp1.quantity_from(qp2) == -20 * m);
|
||||
@@ -230,8 +198,8 @@ origin.
|
||||
```cpp
|
||||
inline constexpr struct origin final : absolute_point_origin<isq::distance> {} origin;
|
||||
|
||||
// quantity_point<si::metre, origin> qp1{100 * m}; // Compile-time error
|
||||
// quantity_point<si::metre, origin> qp2 = 120 * absolute<m>; // Compile-time error
|
||||
// quantity_point<si::metre, origin> qp1{100 * m}; // Compile-time error
|
||||
// quantity_point<si::metre, origin> qp2{delta<m>(120)}; // Compile-time error
|
||||
quantity_point<si::metre, origin> qp1 = origin + 100 * m;
|
||||
quantity_point<si::metre, origin> qp2 = 120 * m + origin;
|
||||
|
||||
@@ -443,7 +411,7 @@ namespace si {
|
||||
inline constexpr struct absolute_zero final : absolute_point_origin<isq::thermodynamic_temperature> {} absolute_zero;
|
||||
inline constexpr auto zeroth_kelvin = absolute_zero;
|
||||
|
||||
inline constexpr struct ice_point final : relative_point_origin<273'150 * absolute<milli<kelvin>>>> {} ice_point;
|
||||
inline constexpr struct ice_point final : relative_point_origin<absolute<milli<kelvin>>(273'150)}> {} ice_point;
|
||||
inline constexpr auto zeroth_degree_Celsius = ice_point;
|
||||
|
||||
}
|
||||
@@ -451,7 +419,7 @@ inline constexpr auto zeroth_degree_Celsius = ice_point;
|
||||
namespace usc {
|
||||
|
||||
inline constexpr struct zeroth_degree_Fahrenheit final :
|
||||
relative_point_origin<-32 * absolute<mag_ratio<5, 9> * si::degree_Celsius>> {} zeroth_degree_Fahrenheit;
|
||||
relative_point_origin<absolute<mag_ratio<5, 9> * si::degree_Celsius>(-32)> {} zeroth_degree_Fahrenheit;
|
||||
|
||||
}
|
||||
```
|
||||
@@ -500,28 +468,28 @@ choose from here. Depending on our needs or tastes, we can:
|
||||
- be explicit about the unit and origin:
|
||||
|
||||
```cpp
|
||||
quantity_point<si::degree_Celsius, si::zeroth_degree_Celsius> q1 = si::zeroth_degree_Celsius + 20.5 * delta<deg_C>;
|
||||
quantity_point<si::degree_Celsius, si::zeroth_degree_Celsius> q2 = {20.5 * delta<deg_C>, si::zeroth_degree_Celsius};
|
||||
quantity_point<si::degree_Celsius, si::zeroth_degree_Celsius> q3{20.5 * delta<deg_C>};
|
||||
quantity_point<si::degree_Celsius, si::zeroth_degree_Celsius> q4 = 20.5 * absolute<deg_C>;
|
||||
quantity_point<si::degree_Celsius, si::zeroth_degree_Celsius> q1 = si::zeroth_degree_Celsius + delta<deg_C>(20.5);
|
||||
quantity_point<si::degree_Celsius, si::zeroth_degree_Celsius> q2 = {delta<deg_C>(20.5), si::zeroth_degree_Celsius};
|
||||
quantity_point<si::degree_Celsius, si::zeroth_degree_Celsius> q3{delta<deg_C>(20.5)};
|
||||
quantity_point<si::degree_Celsius, si::zeroth_degree_Celsius> q4 = absolute<deg_C>(20.5);
|
||||
```
|
||||
|
||||
- specify a unit and use its zeroth point origin implicitly:
|
||||
|
||||
```cpp
|
||||
quantity_point<si::degree_Celsius> q5 = si::zeroth_degree_Celsius + 20.5 * delta<deg_C>;
|
||||
quantity_point<si::degree_Celsius> q6 = {20.5 * delta<deg_C>, si::zeroth_degree_Celsius};
|
||||
quantity_point<si::degree_Celsius> q7{20.5 * delta<deg_C>};
|
||||
quantity_point<si::degree_Celsius> q8 = 20.5 * absolute<deg_C>;
|
||||
quantity_point<si::degree_Celsius> q5 = si::zeroth_degree_Celsius + delta<deg_C>(20.5);
|
||||
quantity_point<si::degree_Celsius> q6 = {delta<deg_C>(20.5), si::zeroth_degree_Celsius};
|
||||
quantity_point<si::degree_Celsius> q7{delta<deg_C>(20.5)};
|
||||
quantity_point<si::degree_Celsius> q8 = absolute<deg_C>(20.5);
|
||||
```
|
||||
|
||||
- benefit from CTAD:
|
||||
|
||||
```cpp
|
||||
quantity_point q9 = si::zeroth_degree_Celsius + 20.5 * delta<deg_C>;
|
||||
quantity_point q10 = {20.5 * delta<deg_C>, si::zeroth_degree_Celsius};
|
||||
quantity_point q11{20.5 * delta<deg_C>};
|
||||
quantity_point q12 = 20.5 * absolute<deg_C>;
|
||||
quantity_point q9 = si::zeroth_degree_Celsius + delta<deg_C>(20.5);
|
||||
quantity_point q10 = {delta<deg_C>(20.5), si::zeroth_degree_Celsius};
|
||||
quantity_point q11{delta<deg_C>(20.5)};
|
||||
quantity_point q12 = absolute<deg_C>(20.5);
|
||||
```
|
||||
|
||||
In all of the above cases, we end up with the `quantity_point` of the same type and value.
|
||||
@@ -532,10 +500,10 @@ the following way:
|
||||
{style="width:80%;display: block;margin: 0 auto;"}
|
||||
|
||||
```cpp
|
||||
constexpr struct room_reference_temp final : relative_point_origin<21 * absolute<deg_C>> {} room_reference_temp;
|
||||
constexpr struct room_reference_temp final : relative_point_origin<absolute<deg_C>(21)> {} room_reference_temp;
|
||||
using room_temp = quantity_point<isq::Celsius_temperature[deg_C], room_reference_temp>;
|
||||
|
||||
constexpr auto step_delta = isq::Celsius_temperature(0.5 * delta<deg_C>);
|
||||
constexpr auto step_delta = delta<isq::Celsius_temperature<deg_C>>(0.5);
|
||||
constexpr int number_of_steps = 6;
|
||||
|
||||
room_temp room_ref{};
|
||||
|
Reference in New Issue
Block a user