feat: 💥 delta and absolute construction helpers

This commit is contained in:
Mateusz Pusz
2024-06-30 12:03:24 +02:00
parent ad3035eafe
commit ba8681f90b
24 changed files with 309 additions and 330 deletions

View File

@ -4,7 +4,7 @@
### 2.3.0 <small>WIP</small> { id="2.3.0" } ### 2.3.0 <small>WIP</small> { id="2.3.0" }
- (!) feat: Reference specifiers [#585](https://github.com/mpusz/mp-units/pull/585) - (!) feat: `delta` and `absolute` construction helpers
### 2.2.0 <small>June 14, 2024</small> { id="2.2.0" } ### 2.2.0 <small>June 14, 2024</small> { id="2.2.0" }

View File

@ -37,7 +37,7 @@ std::cout << Pressure << "\n";
The problem is related to the accidental usage of a `quantity` rather than `quantity_point` for 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 `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`: 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 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. 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 After a lengthy discussion on handling such scenarios, we decided to:
`quantity_point` construction with `absolute<Reference>` and `delta<Reference>` quantity reference
specifiers. This applies to:
- the multiply syntax, - make the above code ill-formed,
- 2-parameter `quantity` constructor. - provide an alternative way to create a `quantity` with the `delta` quantity construction helper.
Here are the main points of this new design: 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 1. All references/units that specify point origin in their definition (i.e., `si::kelvin`,
are considered `delta` by default. This means that `42 * m` creates a `quantity` and is    `si::degree_Celsius`, and `usc::degree_Fahrenheit`) are excluded from the multiply syntax
the same as calling `42 * delta<m>`. (:boom: **breaking change** :boom:).
2. Multiply syntax is extended to allow `quantity_point` creation with the `42 * absolute<m>` 2. A new `delta` quantity construction helper is introduced:
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:
```cpp - `delta<m>(42)` results with a `quantity<si::metre, int>`,
quantity q1(4, m); // OK - `delta<deg_C>(5)` results with a `quantity<si::deg_C, int>`.
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
```
The `delta` and `absolute` modifiers are stripped upon construction, so the resulting `quantity` 3. A new `absolute` quantity point construction helper is introduced:
and `quantity_point` types use the underlying unit in its type.
With such changes, the offending code will not compile, forcing the user to think more about what - `absolute<m>(42)` results with a `quantity_point<si::metre, zeroth_point_origin<kind_of<isq::length>>{}, int>`,
is written. To enable the compilation, the user has to type one of the following: - `absolute<deg_C>(5)` results with a `quantity<si::metre, si::ice_point, int>`.
- `quantity_point Temperature(28.0 * delta<deg_C>);` !!! info
- `quantity_point Temperature = 28.0 * absolute<deg_C>;`
If the user still insists on using `quantity` instead of a `quantity_point`, the code will Please note that `si::kelvin` is also excluded from the multiply syntax to prevent the
have to be written in the following way to compile successfully: following surprising issues:
```cpp === "Now"
quantity Temperature = 28.0 * delta<deg_C>;
```
This will yield an invalid result, but now, hopefully, it is clearly readable in the code what is ```cpp
wrong here. 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.

View File

@ -43,28 +43,51 @@ a number with a predefined unit:
!!! info !!! info
In case someone doesn't like the multiply syntax or there is an ambiguity between `operator*` 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 provided by this and other libraries, there are two other ways to create a quantity:
constructor:
=== "C++ modules" 1. `delta` construction helper:
```cpp === "C++ modules"
import mp_units;
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 === "Header files"
#include <mp-units/systems/si.h>
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 above creates an instance of `quantity<derived_unit<si::metre, per<si::second>>{}, int>`.
The same can be obtained using optional unit symbols: 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::si::unit_symbols;
using namespace mp_units::usc::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: {} ({})", std::println("Temperature: {} ({})",
temp.quantity_from_zero(), temp.quantity_from_zero(),
temp.in(deg_F).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::si::unit_symbols;
using namespace mp_units::usc::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: {} ({})", std::println("Temperature: {} ({})",
temp.quantity_from_zero(), temp.quantity_from_zero(),
temp.in(deg_F).quantity_from_zero()); temp.in(deg_F).quantity_from_zero());

View File

@ -378,5 +378,5 @@ For example:
the previous example: the previous example:
```cpp ```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);
``` ```

View File

@ -68,51 +68,17 @@ difference between two things:
- the difference in _speed_ (even if relative to zero). - the difference in _speed_ (even if relative to zero).
As we already know, a `quantity` type provides all operations required for a _displacement vector_ 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 - the multiply syntax (works for most of the units),
delta references by default. The ones that need a special qualification are the units that - `delta<Reference>` construction helper (e.g., `delta<isq::height[m]>(42)`, `delta<deg_C>(3)`),
get a point origin in their definition (i.e., units of temperature). - two-parameter constructor taking a number and a quantity reference/unit.
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>;
```
!!! note !!! note
`delta` specifier is used to qualify the entire reference upon `quantity` construction. The multiply syntax support is disabled for units that provide a point origin in their
It does not satisfy the [`Reference`](concepts.md#Reference) concept. This means that, definition (i.e., units of temperature like `K`, `deg_C`, and `deg_F`).
for example, the below are ill-formed:
```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` ## _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 - otherwise, an instantiation of `zeroth_point_origin<QuantitySpec>` is being used which
provides a well-established zeroth point for a specific quantity type. 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 Quantity points with default point origins may be constructed with the `absolute` construction
absolute quantity reference. None of units are considered to be absolute references by default, helper or forcing an explicit conversion from the `quantity`:
so they need a special qualification:
```cpp ```cpp
// quantity_point qp1 = 42 * m; // Compile-time error // quantity_point qp1 = 42 * m; // Compile-time error
// quantity_point qp2 = 42 * K; // Compile-time error // quantity_point qp2 = 42 * K; // Compile-time error
// quantity_point qp3 = 42 * deg_C; // Compile-time error // quantity_point qp3 = delta<deg_C>(42); // Compile-time error
quantity_point qp4 = 42 * absolute<m>; quantity_point qp4(42 * m);
quantity_point qp5 = 42 * absolute<K>; quantity_point qp5(42 * K);
quantity_point qp6 = 42 * absolute<deg_C>; 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 !!! tip
@ -180,8 +148,8 @@ for this domain.
![affine_space_1](affine_space_1.svg){style="width:80%;display: block;margin: 0 auto;"} ![affine_space_1](affine_space_1.svg){style="width:80%;display: block;margin: 0 auto;"}
```cpp ```cpp
quantity_point<isq::distance[si::metre]> qp1 = 100 * absolute<m>; quantity_point<isq::distance[si::metre]> qp1(100 * m);
quantity_point<isq::distance[si::metre]> qp2 = 120 * absolute<m>; quantity_point<isq::distance[si::metre]> qp2 = absolute<m>(120);
assert(qp1.quantity_from_zero() == 100 * m); assert(qp1.quantity_from_zero() == 100 * m);
assert(qp2.quantity_from_zero() == 120 * m); assert(qp2.quantity_from_zero() == 120 * m);
@ -210,7 +178,7 @@ compatible:
```cpp ```cpp
quantity_point<si::metre> qp1{isq::distance(100 * m)}; 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(qp2.quantity_from(qp1) == 20 * m);
assert(qp1.quantity_from(qp2) == -20 * m); assert(qp1.quantity_from(qp2) == -20 * m);
@ -230,8 +198,8 @@ origin.
```cpp ```cpp
inline constexpr struct origin final : absolute_point_origin<isq::distance> {} origin; 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> qp1{100 * m}; // Compile-time error
// quantity_point<si::metre, origin> qp2 = 120 * absolute<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> qp1 = origin + 100 * m;
quantity_point<si::metre, origin> qp2 = 120 * m + origin; 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 struct absolute_zero final : absolute_point_origin<isq::thermodynamic_temperature> {} absolute_zero;
inline constexpr auto zeroth_kelvin = 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; inline constexpr auto zeroth_degree_Celsius = ice_point;
} }
@ -451,7 +419,7 @@ inline constexpr auto zeroth_degree_Celsius = ice_point;
namespace usc { namespace usc {
inline constexpr struct zeroth_degree_Fahrenheit final : 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: - be explicit about the unit and origin:
```cpp ```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> q1 = si::zeroth_degree_Celsius + delta<deg_C>(20.5);
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> q2 = {delta<deg_C>(20.5), 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> q3{delta<deg_C>(20.5)};
quantity_point<si::degree_Celsius, si::zeroth_degree_Celsius> q4 = 20.5 * absolute<deg_C>; 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: - specify a unit and use its zeroth point origin implicitly:
```cpp ```cpp
quantity_point<si::degree_Celsius> q5 = si::zeroth_degree_Celsius + 20.5 * delta<deg_C>; quantity_point<si::degree_Celsius> q5 = si::zeroth_degree_Celsius + delta<deg_C>(20.5);
quantity_point<si::degree_Celsius> q6 = {20.5 * delta<deg_C>, si::zeroth_degree_Celsius}; quantity_point<si::degree_Celsius> q6 = {delta<deg_C>(20.5), si::zeroth_degree_Celsius};
quantity_point<si::degree_Celsius> q7{20.5 * delta<deg_C>}; quantity_point<si::degree_Celsius> q7{delta<deg_C>(20.5)};
quantity_point<si::degree_Celsius> q8 = 20.5 * absolute<deg_C>; quantity_point<si::degree_Celsius> q8 = absolute<deg_C>(20.5);
``` ```
- benefit from CTAD: - benefit from CTAD:
```cpp ```cpp
quantity_point q9 = si::zeroth_degree_Celsius + 20.5 * delta<deg_C>; quantity_point q9 = si::zeroth_degree_Celsius + delta<deg_C>(20.5);
quantity_point q10 = {20.5 * delta<deg_C>, si::zeroth_degree_Celsius}; quantity_point q10 = {delta<deg_C>(20.5), si::zeroth_degree_Celsius};
quantity_point q11{20.5 * delta<deg_C>}; quantity_point q11{delta<deg_C>(20.5)};
quantity_point q12 = 20.5 * absolute<deg_C>; 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. 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:
![affine_space_6](affine_space_6.svg){style="width:80%;display: block;margin: 0 auto;"} ![affine_space_6](affine_space_6.svg){style="width:80%;display: block;margin: 0 auto;"}
```cpp ```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>; 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; constexpr int number_of_steps = 6;
room_temp room_ref{}; room_temp room_ref{};

View File

@ -62,13 +62,13 @@ int main()
using estimate = kalman::system_state_estimate<qp>; using estimate = kalman::system_state_estimate<qp>;
using state = estimate::state_type; using state = estimate::state_type;
const quantity process_noise_variance = 0.0001 * delta<pow<2>(deg_C)>; const quantity process_noise_variance = delta<pow<2>(deg_C)>(0.0001);
const estimate initial{state{qp{60. * delta<deg_C>}}, 100. * delta<deg_C>}; const estimate initial{state{qp{delta<deg_C>(60.)}}, delta<deg_C>(100.)};
const std::array measurements = {qp{49.986 * delta<deg_C>}, qp{49.963 * delta<deg_C>}, qp{50.097 * delta<deg_C>}, const std::array measurements = {qp{delta<deg_C>(49.986)}, qp{delta<deg_C>(49.963)}, qp{delta<deg_C>(50.097)},
qp{50.001 * delta<deg_C>}, qp{50.018 * delta<deg_C>}, qp{50.05 * delta<deg_C>}, qp{delta<deg_C>(50.001)}, qp{delta<deg_C>(50.018)}, qp{delta<deg_C>(50.05)},
qp{49.938 * delta<deg_C>}, qp{49.858 * delta<deg_C>}, qp{49.965 * delta<deg_C>}, qp{delta<deg_C>(49.938)}, qp{delta<deg_C>(49.858)}, qp{delta<deg_C>(49.965)},
qp{50.114 * delta<deg_C>}}; qp{delta<deg_C>(50.114)}};
const quantity measurement_error = 0.1 * delta<deg_C>; const quantity measurement_error = delta<deg_C>(0.1);
const quantity measurement_variance = pow<2>(measurement_error); const quantity measurement_variance = pow<2>(measurement_error);
auto predict = [=](const estimate& current) { auto predict = [=](const estimate& current) {

View File

@ -62,13 +62,13 @@ int main()
using estimate = kalman::system_state_estimate<qp>; using estimate = kalman::system_state_estimate<qp>;
using state = estimate::state_type; using state = estimate::state_type;
const quantity process_noise_variance = 0.0001 * delta<pow<2>(deg_C)>; const quantity process_noise_variance = delta<pow<2>(deg_C)>(0.0001);
const estimate initial{state{qp{10. * delta<deg_C>}}, 100. * delta<deg_C>}; const estimate initial{state{qp{delta<deg_C>(10.)}}, delta<deg_C>(100.)};
const std::array measurements = {qp{50.486 * delta<deg_C>}, qp{50.963 * delta<deg_C>}, qp{51.597 * delta<deg_C>}, const std::array measurements = {qp{delta<deg_C>(50.486)}, qp{delta<deg_C>(50.963)}, qp{delta<deg_C>(51.597)},
qp{52.001 * delta<deg_C>}, qp{52.518 * delta<deg_C>}, qp{53.05 * delta<deg_C>}, qp{delta<deg_C>(52.001)}, qp{delta<deg_C>(52.518)}, qp{delta<deg_C>(53.05)},
qp{53.438 * delta<deg_C>}, qp{53.858 * delta<deg_C>}, qp{54.465 * delta<deg_C>}, qp{delta<deg_C>(53.438)}, qp{delta<deg_C>(53.858)}, qp{delta<deg_C>(54.465)},
qp{55.114 * delta<deg_C>}}; qp{delta<deg_C>(55.114)}};
const quantity measurement_error = 0.1 * delta<deg_C>; const quantity measurement_error = delta<deg_C>(0.1);
const quantity measurement_variance = pow<2>(measurement_error); const quantity measurement_variance = pow<2>(measurement_error);
auto predict = [=](const estimate& current) { auto predict = [=](const estimate& current) {

View File

@ -62,13 +62,13 @@ int main()
using estimate = kalman::system_state_estimate<qp>; using estimate = kalman::system_state_estimate<qp>;
using state = estimate::state_type; using state = estimate::state_type;
const quantity process_noise_variance = 0.15 * delta<pow<2>(deg_C)>; const quantity process_noise_variance = delta<pow<2>(deg_C)>(0.15);
const estimate initial{state{qp{10. * delta<deg_C>}}, 100. * delta<deg_C>}; const estimate initial{state{qp{delta<deg_C>(10.)}}, delta<deg_C>(100.)};
const std::array measurements = {qp{50.486 * delta<deg_C>}, qp{50.963 * delta<deg_C>}, qp{51.597 * delta<deg_C>}, const std::array measurements = {qp{delta<deg_C>(50.486)}, qp{delta<deg_C>(50.963)}, qp{delta<deg_C>(51.597)},
qp{52.001 * delta<deg_C>}, qp{52.518 * delta<deg_C>}, qp{53.05 * delta<deg_C>}, qp{delta<deg_C>(52.001)}, qp{delta<deg_C>(52.518)}, qp{delta<deg_C>(53.05)},
qp{53.438 * delta<deg_C>}, qp{53.858 * delta<deg_C>}, qp{54.465 * delta<deg_C>}, qp{delta<deg_C>(53.438)}, qp{delta<deg_C>(53.858)}, qp{delta<deg_C>(54.465)},
qp{55.114 * delta<deg_C>}}; qp{delta<deg_C>(55.114)}};
const quantity measurement_error = 0.1 * delta<deg_C>; const quantity measurement_error = delta<deg_C>(0.1);
const quantity measurement_variance = pow<2>(measurement_error); const quantity measurement_variance = pow<2>(measurement_error);
auto predict = [=](const estimate& current) { auto predict = [=](const estimate& current) {

View File

@ -84,7 +84,7 @@ int main()
const auto t3 = std::make_tuple(isq::energy(q3 * h), isq::wavenumber(q3 / c), q3, const auto t3 = std::make_tuple(isq::energy(q3 * h), isq::wavenumber(q3 / c), q3,
isq::thermodynamic_temperature(q3 * h / kb), isq::wavelength(c / q3)); isq::thermodynamic_temperature(q3 * h / kb), isq::wavelength(c / q3));
const auto q4 = isq::thermodynamic_temperature(1. * delta<K>); const auto q4 = delta<isq::thermodynamic_temperature[K]>(1.);
const auto t4 = std::make_tuple(isq::energy(q4 * kb), isq::wavenumber(q4 * kb / (h * c)), isq::frequency(q4 * kb / h), const auto t4 = std::make_tuple(isq::energy(q4 * kb), isq::wavenumber(q4 * kb / (h * c)), isq::frequency(q4 * kb / h),
q4, isq::wavelength(h * c / (q4 * kb))); q4, isq::wavelength(h * c / (q4 * kb)));

View File

@ -50,6 +50,7 @@ add_mp_units_module(
include/mp-units/ext/type_name.h include/mp-units/ext/type_name.h
include/mp-units/ext/type_traits.h include/mp-units/ext/type_traits.h
include/mp-units/framework/compare.h include/mp-units/framework/compare.h
include/mp-units/framework/construction_helpers.h
include/mp-units/framework/customization_points.h include/mp-units/framework/customization_points.h
include/mp-units/framework/dimension.h include/mp-units/framework/dimension.h
include/mp-units/framework/dimension_concepts.h include/mp-units/framework/dimension_concepts.h

View File

@ -97,10 +97,8 @@ template<Quantity To, typename From>
if constexpr (q_unit == To::unit) { if constexpr (q_unit == To::unit) {
// no scaling of the number needed // no scaling of the number needed
return {static_cast<MP_UNITS_TYPENAME To::rep>(std::forward<From>(q).numerical_value_is_an_implementation_detail_), return {static_cast<MP_UNITS_TYPENAME To::rep>(std::forward<From>(q).numerical_value_is_an_implementation_detail_),
make_delta(To::reference)}; // this is the only (and recommended) way to do To::reference}; // this is the only (and recommended) way to do a truncating conversion on a number, so we
// a truncating conversion on a number, so we are // are using static_cast to suppress all the compiler warnings on conversions
// using static_cast to suppress all the compiler
// warnings on conversions
} else { } else {
// scale the number // scale the number
using traits = magnitude_conversion_traits<To, std::remove_reference_t<From>>; using traits = magnitude_conversion_traits<To, std::remove_reference_t<From>>;
@ -108,13 +106,13 @@ template<Quantity To, typename From>
// this results in great assembly // this results in great assembly
auto res = static_cast<MP_UNITS_TYPENAME To::rep>( auto res = static_cast<MP_UNITS_TYPENAME To::rep>(
static_cast<traits::c_type>(q.numerical_value_is_an_implementation_detail_) * traits::ratio); static_cast<traits::c_type>(q.numerical_value_is_an_implementation_detail_) * traits::ratio);
return {res, make_delta(To::reference)}; return {res, To::reference};
} else { } else {
// this is slower but allows conversions like 2000 m -> 2 km without loosing data // this is slower but allows conversions like 2000 m -> 2 km without loosing data
auto res = static_cast<MP_UNITS_TYPENAME To::rep>( auto res = static_cast<MP_UNITS_TYPENAME To::rep>(
static_cast<traits::c_type>(q.numerical_value_is_an_implementation_detail_) * traits::num_mult / static_cast<traits::c_type>(q.numerical_value_is_an_implementation_detail_) * traits::num_mult /
traits::den_mult * traits::irr_mult); traits::den_mult * traits::irr_mult);
return {res, make_delta(To::reference)}; return {res, To::reference};
} }
} }
} }

View File

@ -24,6 +24,7 @@
// IWYU pragma: begin_exports // IWYU pragma: begin_exports
#include <mp-units/framework/compare.h> #include <mp-units/framework/compare.h>
#include <mp-units/framework/construction_helpers.h>
#include <mp-units/framework/customization_points.h> #include <mp-units/framework/customization_points.h>
#include <mp-units/framework/dimension.h> #include <mp-units/framework/dimension.h>
#include <mp-units/framework/dimension_concepts.h> #include <mp-units/framework/dimension_concepts.h>

View File

@ -0,0 +1,67 @@
// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include <mp-units/framework/quantity.h>
#include <mp-units/framework/quantity_point.h>
#include <mp-units/framework/reference_concepts.h>
#include <mp-units/framework/representation_concepts.h>
#ifndef MP_UNITS_IN_MODULE_INTERFACE
#include <type_traits>
#endif
namespace mp_units {
template<Reference R>
struct delta_ {
template<typename Rep>
requires RepresentationOf<std::remove_cvref_t<Rep>, get_quantity_spec(R{}).character>
[[nodiscard]] constexpr quantity<R{}, std::remove_cvref_t<Rep>> operator()(Rep&& lhs) const
{
return quantity{std::forward<Rep>(lhs), R{}};
}
};
template<Reference R>
struct absolute_ {
template<typename Rep>
requires RepresentationOf<std::remove_cvref_t<Rep>, get_quantity_spec(R{}).character>
[[nodiscard]] constexpr quantity_point<R{}, default_point_origin(R{}), std::remove_cvref_t<Rep>> operator()(
Rep&& lhs) const
{
return quantity_point{quantity{std::forward<Rep>(lhs), R{}}};
}
};
MP_UNITS_EXPORT_BEGIN
template<Reference auto R>
inline constexpr delta_<MP_UNITS_REMOVE_CONST(decltype(R))> delta{};
template<Reference auto R>
inline constexpr absolute_<MP_UNITS_REMOVE_CONST(decltype(R))> absolute{};
MP_UNITS_EXPORT_END
} // namespace mp_units

View File

@ -96,13 +96,8 @@ template<typename Func, Quantity Q1, Quantity Q2>
using common_quantity_for = quantity<common_reference(Q1::reference, Q2::reference), using common_quantity_for = quantity<common_reference(Q1::reference, Q2::reference),
std::invoke_result_t<Func, typename Q1::rep, typename Q2::rep>>; std::invoke_result_t<Func, typename Q1::rep, typename Q2::rep>>;
template<auto T, auto R>
concept SameOriginalReferenceAs =
DeltaReference<MP_UNITS_REMOVE_CONST(decltype(T))> && Reference<MP_UNITS_REMOVE_CONST(decltype(R))> &&
detail::SameReference<remove_reference_specifier(T), R>;
template<auto R1, auto R2, typename Rep1, typename Rep2> template<auto R1, auto R2, typename Rep1, typename Rep2>
concept SameValueAs = detail::SameOriginalReferenceAs<R1, R2> && std::same_as<Rep1, Rep2>; concept SameValueAs = detail::SameReference<R1, R2> && std::same_as<Rep1, Rep2>;
} // namespace detail } // namespace detail
@ -133,25 +128,25 @@ public:
[[nodiscard]] static constexpr quantity zero() noexcept [[nodiscard]] static constexpr quantity zero() noexcept
requires requires { quantity_values<rep>::zero(); } requires requires { quantity_values<rep>::zero(); }
{ {
return {quantity_values<rep>::zero(), detail::make_delta(R)}; return {quantity_values<rep>::zero(), R};
} }
[[nodiscard]] static constexpr quantity one() noexcept [[nodiscard]] static constexpr quantity one() noexcept
requires requires { quantity_values<rep>::one(); } requires requires { quantity_values<rep>::one(); }
{ {
return {quantity_values<rep>::one(), detail::make_delta(R)}; return {quantity_values<rep>::one(), R};
} }
[[nodiscard]] static constexpr quantity min() noexcept [[nodiscard]] static constexpr quantity min() noexcept
requires requires { quantity_values<rep>::min(); } requires requires { quantity_values<rep>::min(); }
{ {
return {quantity_values<rep>::min(), detail::make_delta(R)}; return {quantity_values<rep>::min(), R};
} }
[[nodiscard]] static constexpr quantity max() noexcept [[nodiscard]] static constexpr quantity max() noexcept
requires requires { quantity_values<rep>::max(); } requires requires { quantity_values<rep>::max(); }
{ {
return {quantity_values<rep>::max(), detail::make_delta(R)}; return {quantity_values<rep>::max(), R};
} }
// construction, assignment, destruction // construction, assignment, destruction
@ -160,7 +155,7 @@ public:
quantity(quantity&&) = default; quantity(quantity&&) = default;
~quantity() = default; ~quantity() = default;
template<typename Value, DeltaReference R2> template<typename Value, Reference R2>
requires detail::SameValueAs<R2{}, R, std::remove_cvref_t<Value>, Rep> requires detail::SameValueAs<R2{}, R, std::remove_cvref_t<Value>, Rep>
constexpr quantity(Value&& v, R2) : numerical_value_is_an_implementation_detail_(std::forward<Value>(v)) constexpr quantity(Value&& v, R2) : numerical_value_is_an_implementation_detail_(std::forward<Value>(v))
{ {
@ -173,24 +168,6 @@ public:
{ {
} }
template<typename Value, DeltaReference R2>
requires(!Reference<R2>) && (!detail::SameValueAs<R2{}, R, std::remove_cvref_t<Value>, Rep>) &&
detail::QuantityConvertibleTo<
quantity<detail::remove_reference_specifier(R2{}), std::remove_cvref_t<Value>>, quantity>
constexpr quantity(Value&& v, R2) :
quantity(
quantity<detail::remove_reference_specifier(R2{}), std::remove_cvref_t<Value>>{std::forward<Value>(v), R2{}})
{
}
template<typename Value, Reference R2>
requires(!DeltaReference<R2>)
constexpr quantity(Value&&, R2)
{
static_assert(DeltaReference<R2>,
"References using offset units (e.g., temperatures) must be explicitly qualified with `delta`");
}
template<detail::QuantityConvertibleTo<quantity> Q> template<detail::QuantityConvertibleTo<quantity> Q>
// NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
constexpr explicit(!std::convertible_to<typename Q::rep, Rep>) quantity(const Q& q) : constexpr explicit(!std::convertible_to<typename Q::rep, Rep>) quantity(const Q& q) :
@ -206,8 +183,8 @@ public:
convert_explicitly> || convert_explicitly> ||
// NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
!std::convertible_to<typename quantity_like_traits<Q>::rep, Rep>) quantity(const Q& q) : !std::convertible_to<typename quantity_like_traits<Q>::rep, Rep>) quantity(const Q& q) :
quantity(::mp_units::quantity{quantity_like_traits<Q>::to_numerical_value(q).value, quantity(
detail::make_delta(quantity_like_traits<Q>::reference)}) ::mp_units::quantity{quantity_like_traits<Q>::to_numerical_value(q).value, quantity_like_traits<Q>::reference})
{ {
} }
@ -306,7 +283,7 @@ public:
} -> std::common_with<rep>; } -> std::common_with<rep>;
} }
{ {
return ::mp_units::quantity{+numerical_value_is_an_implementation_detail_, detail::make_delta(reference)}; return ::mp_units::quantity{+numerical_value_is_an_implementation_detail_, reference};
} }
[[nodiscard]] constexpr QuantityOf<quantity_spec> auto operator-() const [[nodiscard]] constexpr QuantityOf<quantity_spec> auto operator-() const
@ -316,7 +293,7 @@ public:
} -> std::common_with<rep>; } -> std::common_with<rep>;
} }
{ {
return ::mp_units::quantity{-numerical_value_is_an_implementation_detail_, detail::make_delta(reference)}; return ::mp_units::quantity{-numerical_value_is_an_implementation_detail_, reference};
} }
template<typename Q> template<typename Q>
@ -338,7 +315,7 @@ public:
} -> std::common_with<rep>; } -> std::common_with<rep>;
} }
{ {
return ::mp_units::quantity{numerical_value_is_an_implementation_detail_++, detail::make_delta(reference)}; return ::mp_units::quantity{numerical_value_is_an_implementation_detail_++, reference};
} }
template<typename Q> template<typename Q>
@ -360,7 +337,7 @@ public:
} -> std::common_with<rep>; } -> std::common_with<rep>;
} }
{ {
return ::mp_units::quantity{numerical_value_is_an_implementation_detail_--, detail::make_delta(reference)}; return ::mp_units::quantity{numerical_value_is_an_implementation_detail_--, reference};
} }
// compound assignment operators // compound assignment operators
@ -463,16 +440,6 @@ template<typename Value, Reference R>
requires RepresentationOf<Value, get_quantity_spec(R{}).character> requires RepresentationOf<Value, get_quantity_spec(R{}).character>
quantity(Value v, R) -> quantity<R{}, Value>; quantity(Value v, R) -> quantity<R{}, Value>;
template<typename Value, DeltaReference R>
requires(!Reference<R>) &&
RepresentationOf<Value, get_quantity_spec(detail::remove_reference_specifier(R{})).character>
quantity(Value v, R) -> quantity<detail::remove_reference_specifier(R{}), Value>;
// the below is needed only to fire static_asserts in the constructor
template<typename Value, Reference R>
requires(!DeltaReference<R>) && RepresentationOf<Value, get_quantity_spec(R{}).character>
quantity(Value v, R) -> quantity<R{}, Value>;
template<QuantityLike Q> template<QuantityLike Q>
explicit( explicit(
is_specialization_of<decltype(quantity_like_traits<Q>::to_numerical_value(std::declval<Q>())), convert_explicitly>) is_specialization_of<decltype(quantity_like_traits<Q>::to_numerical_value(std::declval<Q>())), convert_explicitly>)
@ -487,7 +454,7 @@ template<auto R1, typename Rep1, auto R2, typename Rep2>
const ret ret_lhs(lhs); const ret ret_lhs(lhs);
const ret ret_rhs(rhs); const ret ret_rhs(rhs);
return quantity{ret_lhs.numerical_value_ref_in(ret::unit) + ret_rhs.numerical_value_ref_in(ret::unit), return quantity{ret_lhs.numerical_value_ref_in(ret::unit) + ret_rhs.numerical_value_ref_in(ret::unit),
detail::make_delta(ret::reference)}; ret::reference};
} }
template<auto R1, typename Rep1, auto R2, typename Rep2> template<auto R1, typename Rep1, auto R2, typename Rep2>
@ -498,7 +465,7 @@ template<auto R1, typename Rep1, auto R2, typename Rep2>
const ret ret_lhs(lhs); const ret ret_lhs(lhs);
const ret ret_rhs(rhs); const ret ret_rhs(rhs);
return quantity{ret_lhs.numerical_value_ref_in(ret::unit) - ret_rhs.numerical_value_ref_in(ret::unit), return quantity{ret_lhs.numerical_value_ref_in(ret::unit) - ret_rhs.numerical_value_ref_in(ret::unit),
detail::make_delta(ret::reference)}; ret::reference};
} }
template<auto R1, typename Rep1, auto R2, typename Rep2> template<auto R1, typename Rep1, auto R2, typename Rep2>
@ -511,15 +478,14 @@ template<auto R1, typename Rep1, auto R2, typename Rep2>
const ret ret_lhs(lhs); const ret ret_lhs(lhs);
const ret ret_rhs(rhs); const ret ret_rhs(rhs);
return quantity{ret_lhs.numerical_value_ref_in(ret::unit) % ret_rhs.numerical_value_ref_in(ret::unit), return quantity{ret_lhs.numerical_value_ref_in(ret::unit) % ret_rhs.numerical_value_ref_in(ret::unit),
detail::make_delta(ret::reference)}; ret::reference};
} }
template<auto R1, typename Rep1, auto R2, typename Rep2> template<auto R1, typename Rep1, auto R2, typename Rep2>
requires detail::InvocableQuantities<std::multiplies<>, quantity<R1, Rep1>, quantity<R2, Rep2>> requires detail::InvocableQuantities<std::multiplies<>, quantity<R1, Rep1>, quantity<R2, Rep2>>
[[nodiscard]] constexpr Quantity auto operator*(const quantity<R1, Rep1>& lhs, const quantity<R2, Rep2>& rhs) [[nodiscard]] constexpr Quantity auto operator*(const quantity<R1, Rep1>& lhs, const quantity<R2, Rep2>& rhs)
{ {
return quantity{lhs.numerical_value_ref_in(get_unit(R1)) * rhs.numerical_value_ref_in(get_unit(R2)), return quantity{lhs.numerical_value_ref_in(get_unit(R1)) * rhs.numerical_value_ref_in(get_unit(R2)), R1 * R2};
detail::make_delta(R1 * R2)};
} }
template<auto R, typename Rep, typename Value> template<auto R, typename Rep, typename Value>
@ -527,7 +493,7 @@ template<auto R, typename Rep, typename Value>
detail::InvokeResultOf<get_quantity_spec(R).character, std::multiplies<>, Rep, const Value&> detail::InvokeResultOf<get_quantity_spec(R).character, std::multiplies<>, Rep, const Value&>
[[nodiscard]] constexpr QuantityOf<get_quantity_spec(R)> auto operator*(const quantity<R, Rep>& q, const Value& v) [[nodiscard]] constexpr QuantityOf<get_quantity_spec(R)> auto operator*(const quantity<R, Rep>& q, const Value& v)
{ {
return quantity{q.numerical_value_ref_in(get_unit(R)) * v, detail::make_delta(R)}; return quantity{q.numerical_value_ref_in(get_unit(R)) * v, R};
} }
template<typename Value, auto R, typename Rep> template<typename Value, auto R, typename Rep>
@ -535,7 +501,7 @@ template<typename Value, auto R, typename Rep>
detail::InvokeResultOf<get_quantity_spec(R).character, std::multiplies<>, const Value&, Rep> detail::InvokeResultOf<get_quantity_spec(R).character, std::multiplies<>, const Value&, Rep>
[[nodiscard]] constexpr QuantityOf<get_quantity_spec(R)> auto operator*(const Value& v, const quantity<R, Rep>& q) [[nodiscard]] constexpr QuantityOf<get_quantity_spec(R)> auto operator*(const Value& v, const quantity<R, Rep>& q)
{ {
return quantity{v * q.numerical_value_ref_in(get_unit(R)), detail::make_delta(R)}; return quantity{v * q.numerical_value_ref_in(get_unit(R)), R};
} }
template<auto R1, typename Rep1, auto R2, typename Rep2> template<auto R1, typename Rep1, auto R2, typename Rep2>
@ -543,8 +509,7 @@ template<auto R1, typename Rep1, auto R2, typename Rep2>
[[nodiscard]] constexpr Quantity auto operator/(const quantity<R1, Rep1>& lhs, const quantity<R2, Rep2>& rhs) [[nodiscard]] constexpr Quantity auto operator/(const quantity<R1, Rep1>& lhs, const quantity<R2, Rep2>& rhs)
{ {
MP_UNITS_EXPECTS_DEBUG(rhs != rhs.zero()); MP_UNITS_EXPECTS_DEBUG(rhs != rhs.zero());
return quantity{lhs.numerical_value_ref_in(get_unit(R1)) / rhs.numerical_value_ref_in(get_unit(R2)), return quantity{lhs.numerical_value_ref_in(get_unit(R1)) / rhs.numerical_value_ref_in(get_unit(R2)), R1 / R2};
detail::make_delta(R1 / R2)};
} }
template<auto R, typename Rep, typename Value> template<auto R, typename Rep, typename Value>
@ -553,7 +518,7 @@ template<auto R, typename Rep, typename Value>
[[nodiscard]] constexpr QuantityOf<get_quantity_spec(R)> auto operator/(const quantity<R, Rep>& q, const Value& v) [[nodiscard]] constexpr QuantityOf<get_quantity_spec(R)> auto operator/(const quantity<R, Rep>& q, const Value& v)
{ {
MP_UNITS_EXPECTS_DEBUG(v != quantity_values<Value>::zero()); MP_UNITS_EXPECTS_DEBUG(v != quantity_values<Value>::zero());
return quantity{q.numerical_value_ref_in(get_unit(R)) / v, detail::make_delta(R)}; return quantity{q.numerical_value_ref_in(get_unit(R)) / v, R};
} }
template<typename Value, auto R, typename Rep> template<typename Value, auto R, typename Rep>
@ -562,7 +527,7 @@ template<typename Value, auto R, typename Rep>
[[nodiscard]] constexpr QuantityOf<inverse(get_quantity_spec(R))> auto operator/(const Value& v, [[nodiscard]] constexpr QuantityOf<inverse(get_quantity_spec(R))> auto operator/(const Value& v,
const quantity<R, Rep>& q) const quantity<R, Rep>& q)
{ {
return quantity{v / q.numerical_value_ref_in(get_unit(R)), detail::make_delta(::mp_units::one / R)}; return quantity{v / q.numerical_value_ref_in(get_unit(R)), ::mp_units::one / R};
} }
template<auto R1, typename Rep1, auto R2, typename Rep2> template<auto R1, typename Rep1, auto R2, typename Rep2>

View File

@ -58,7 +58,7 @@ template<QuantitySpec auto ToQS, typename Q>
[[nodiscard]] constexpr Quantity auto quantity_cast(Q&& q) [[nodiscard]] constexpr Quantity auto quantity_cast(Q&& q)
{ {
return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_, return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_,
detail::make_delta(make_reference(ToQS, std::remove_reference_t<Q>::unit))}; make_reference(ToQS, std::remove_reference_t<Q>::unit)};
} }
/** /**

View File

@ -129,7 +129,7 @@ struct quantity_spec_interface {
[[nodiscard]] constexpr Quantity auto operator()(this Self self, Q&& q) [[nodiscard]] constexpr Quantity auto operator()(this Self self, Q&& q)
{ {
return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_, return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_,
detail::make_delta(detail::make_reference(self, std::remove_cvref_t<Q>::unit))}; detail::make_reference(self, std::remove_cvref_t<Q>::unit)};
} }
#else #else
template<typename Self_ = Self, UnitOf<Self_{}> U> template<typename Self_ = Self, UnitOf<Self_{}> U>
@ -144,7 +144,7 @@ struct quantity_spec_interface {
[[nodiscard]] constexpr Quantity auto operator()(Q&& q) const [[nodiscard]] constexpr Quantity auto operator()(Q&& q) const
{ {
return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_, return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_,
detail::make_delta(detail::make_reference(Self{}, std::remove_cvref_t<Q>::unit))}; detail::make_reference(Self{}, std::remove_cvref_t<Q>::unit)};
} }
#endif #endif
}; };
@ -341,7 +341,7 @@ struct quantity_spec<Self, QS, Args...> : detail::propagate_equation<QS>, detail
[[nodiscard]] constexpr Quantity auto operator()(Q&& q) const [[nodiscard]] constexpr Quantity auto operator()(Q&& q) const
{ {
return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_, return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_,
detail::make_delta(detail::make_reference(Self{}, std::remove_cvref_t<Q>::unit))}; detail::make_reference(Self{}, std::remove_cvref_t<Q>::unit)};
} }
#endif #endif
}; };

View File

@ -41,16 +41,6 @@ namespace detail {
template<QuantitySpec auto Q, Unit auto U> template<QuantitySpec auto Q, Unit auto U>
using reference_t = reference<MP_UNITS_REMOVE_CONST(decltype(Q)), MP_UNITS_REMOVE_CONST(decltype(U))>; using reference_t = reference<MP_UNITS_REMOVE_CONST(decltype(Q)), MP_UNITS_REMOVE_CONST(decltype(U))>;
template<typename R>
requires DeltaReference<R> || AbsoluteReference<R>
[[nodiscard]] consteval Reference auto remove_reference_specifier(R r)
{
if constexpr (requires { R::_original_reference_; })
return R::_original_reference_;
else
return r;
}
} // namespace detail } // namespace detail
MP_UNITS_EXPORT_BEGIN MP_UNITS_EXPORT_BEGIN
@ -184,62 +174,40 @@ struct reference {
}; };
template<typename Rep, DeltaReference R> template<typename Rep, Reference R>
requires RepresentationOf<std::remove_cvref_t<Rep>, requires(!detail::OffsetUnit<decltype(get_unit(R{}))>) &&
get_quantity_spec(detail::remove_reference_specifier(R{})).character> RepresentationOf<std::remove_cvref_t<Rep>, get_quantity_spec(R{}).character>
[[nodiscard]] constexpr quantity<detail::remove_reference_specifier(R{}), std::remove_cvref_t<Rep>> operator*(Rep&& lhs, [[nodiscard]] constexpr quantity<R{}, std::remove_cvref_t<Rep>> operator*(Rep&& lhs, R)
R)
{ {
return quantity{std::forward<Rep>(lhs), R{}}; return quantity{std::forward<Rep>(lhs), R{}};
} }
template<typename Rep, DeltaReference R> template<typename Rep, Reference R>
requires RepresentationOf<std::remove_cvref_t<Rep>, requires(!detail::OffsetUnit<decltype(get_unit(R{}))>) &&
get_quantity_spec(detail::remove_reference_specifier(R{})).character> RepresentationOf<std::remove_cvref_t<Rep>, get_quantity_spec(R{}).character>
[[nodiscard]] constexpr quantity<inverse(detail::remove_reference_specifier(R{})), std::remove_cvref_t<Rep>> operator/( [[nodiscard]] constexpr quantity<inverse(R{}), std::remove_cvref_t<Rep>> operator/(Rep&& lhs, R)
Rep&& lhs, R)
{ {
return quantity{std::forward<Rep>(lhs), detail::make_delta(inverse(detail::remove_reference_specifier(R{})))}; return quantity{std::forward<Rep>(lhs), inverse(R{})};
}
template<typename Rep, AbsoluteReference R>
requires RepresentationOf<std::remove_cvref_t<Rep>,
get_quantity_spec(detail::remove_reference_specifier(R{})).character>
[[nodiscard]] constexpr quantity_point<detail::remove_reference_specifier(R{}),
default_point_origin(detail::remove_reference_specifier(R{})),
std::remove_cvref_t<Rep>>
operator*(Rep&& lhs, R)
{
return quantity_point{std::forward<Rep>(lhs) * detail::make_delta(detail::remove_reference_specifier(R{}))};
}
template<typename Rep, AbsoluteReference R>
requires RepresentationOf<std::remove_cvref_t<Rep>,
get_quantity_spec(detail::remove_reference_specifier(R{})).character>
[[nodiscard]] constexpr quantity_point<inverse(detail::remove_reference_specifier(R{})),
default_point_origin(detail::remove_reference_specifier(R{})),
std::remove_cvref_t<Rep>>
operator/(Rep&& lhs, R)
{
return quantity_point{std::forward<Rep>(lhs) * detail::make_delta(inverse(detail::remove_reference_specifier(R{})))};
} }
template<typename Rep, Reference R> template<typename Rep, Reference R>
requires(!DeltaReference<R>) && RepresentationOf<std::remove_cvref_t<Rep>, get_quantity_spec(R{}).character> requires detail::OffsetUnit<decltype(get_unit(R{}))> &&
RepresentationOf<std::remove_cvref_t<Rep>, get_quantity_spec(R{}).character>
[[noreturn]] constexpr auto operator*(Rep&&, R) [[noreturn]] constexpr auto operator*(Rep&&, R)
{ {
static_assert( static_assert(!detail::OffsetUnit<decltype(get_unit(R{}))>,
DeltaReference<R>, "References using offset units (e.g., temperatures) may be constructed only with the `delta` or "
"References using offset units (e.g., temperatures) must be explicitly qualified with `delta` or `absolute`"); "`absolute` helpers");
} }
template<typename Rep, Reference R> template<typename Rep, Reference R>
requires(!DeltaReference<R>) && RepresentationOf<std::remove_cvref_t<Rep>, get_quantity_spec(R{}).character> requires detail::OffsetUnit<decltype(get_unit(R{}))> &&
RepresentationOf<std::remove_cvref_t<Rep>, get_quantity_spec(R{}).character>
[[noreturn]] constexpr auto operator/(Rep&&, R) [[noreturn]] constexpr auto operator/(Rep&&, R)
{ {
static_assert( static_assert(!detail::OffsetUnit<decltype(get_unit(R{}))>,
DeltaReference<R>, "References using offset units (e.g., temperatures) may be constructed only with the `delta` or "
"References using offset units (e.g., temperatures) must be explicitly qualified with `delta` or `absolute`"); "`absolute` helpers");
} }
template<Reference R, typename Rep> template<Reference R, typename Rep>
@ -267,7 +235,7 @@ template<typename Q, Reference R>
[[nodiscard]] constexpr Quantity auto operator*(Q&& q, R) [[nodiscard]] constexpr Quantity auto operator*(Q&& q, R)
{ {
return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_, return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_,
detail::make_delta(std::remove_cvref_t<Q>::reference * R{})}; std::remove_cvref_t<Q>::reference * R{}};
} }
template<typename Q, Reference R> template<typename Q, Reference R>
@ -275,7 +243,7 @@ template<typename Q, Reference R>
[[nodiscard]] constexpr Quantity auto operator/(Q&& q, R) [[nodiscard]] constexpr Quantity auto operator/(Q&& q, R)
{ {
return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_, return quantity{std::forward<Q>(q).numerical_value_is_an_implementation_detail_,
detail::make_delta(std::remove_cvref_t<Q>::reference / R{})}; std::remove_cvref_t<Q>::reference / R{}};
} }
template<Reference R, typename Q> template<Reference R, typename Q>

View File

@ -80,44 +80,8 @@ concept ReferenceOf = Reference<T> && QuantitySpecOf<decltype(get_quantity_spec(
MP_UNITS_EXPORT_END MP_UNITS_EXPORT_END
// reference specifiers
template<Reference R>
struct delta_ final {
static constexpr Reference auto _original_reference_ = R{};
};
template<Reference R>
struct absolute_ final {
static constexpr Reference auto _original_reference_ = R{};
};
MP_UNITS_EXPORT_BEGIN
template<Reference auto R>
inline constexpr delta_<MP_UNITS_REMOVE_CONST(decltype(R))> delta{};
template<Reference auto R>
inline constexpr absolute_<MP_UNITS_REMOVE_CONST(decltype(R))> absolute{};
template<typename T>
concept DeltaReference = (Reference<T> && !requires { get_unit(T{}).point_origin; }) || is_specialization_of<T, delta_>;
template<typename T>
concept AbsoluteReference = is_specialization_of<T, absolute_>;
MP_UNITS_EXPORT_END
namespace detail { namespace detail {
template<Reference R>
[[nodiscard]] consteval DeltaReference auto make_delta(R r)
{
if constexpr (requires { get_unit(R{}).point_origin; })
return delta<R{}>;
else
return r;
}
template<auto R1, auto R2> template<auto R1, auto R2>
concept SameReference = concept SameReference =
Reference<MP_UNITS_REMOVE_CONST(decltype(R1))> && Reference<MP_UNITS_REMOVE_CONST(decltype(R2))> && (R1 == R2); Reference<MP_UNITS_REMOVE_CONST(decltype(R1))> && Reference<MP_UNITS_REMOVE_CONST(decltype(R2))> && (R1 == R2);

View File

@ -210,4 +210,11 @@ concept UnitCompatibleWith =
Unit<U> && Unit<MP_UNITS_REMOVE_CONST(decltype(FromU))> && QuantitySpec<MP_UNITS_REMOVE_CONST(decltype(QS))> && Unit<U> && Unit<MP_UNITS_REMOVE_CONST(decltype(FromU))> && QuantitySpec<MP_UNITS_REMOVE_CONST(decltype(QS))> &&
(!AssociatedUnit<U> || UnitOf<U, QS>)&&detail::UnitConvertibleTo<FromU, U{}>; (!AssociatedUnit<U> || UnitOf<U, QS>)&&detail::UnitConvertibleTo<FromU, U{}>;
namespace detail {
template<typename T>
concept OffsetUnit = Unit<T> && requires { T::point_origin; };
}
} // namespace mp_units } // namespace mp_units

View File

@ -114,8 +114,8 @@ template<ReferenceOf<dimensionless> auto R, typename Rep>
[[nodiscard]] constexpr quantity<R, Rep> exp(const quantity<R, Rep>& q) [[nodiscard]] constexpr quantity<R, Rep> exp(const quantity<R, Rep>& q)
{ {
using std::exp; using std::exp;
return value_cast<get_unit(R)>(quantity{static_cast<Rep>(exp(q.force_numerical_value_in(q.unit))), return value_cast<get_unit(R)>(
detail::make_delta(detail::clone_reference_with<one>(R))}); quantity{static_cast<Rep>(exp(q.force_numerical_value_in(q.unit))), detail::clone_reference_with<one>(R)});
} }
/** /**
@ -236,7 +236,7 @@ template<auto R, auto S, auto T, typename Rep1, typename Rep2, typename Rep3>
using std::fma; using std::fma;
return quantity{ return quantity{
fma(a.numerical_value_ref_in(a.unit), x.numerical_value_ref_in(x.unit), b.numerical_value_ref_in(b.unit)), fma(a.numerical_value_ref_in(a.unit), x.numerical_value_ref_in(x.unit), b.numerical_value_ref_in(b.unit)),
detail::make_delta(common_reference(R * S, T))}; common_reference(R * S, T)};
} }
/** /**
@ -260,7 +260,7 @@ template<auto R, auto S, auto T, auto Origin, typename Rep1, typename Rep2, type
using std::fma; using std::fma;
return Origin + quantity{fma(a.numerical_value_ref_in(a.unit), x.numerical_value_ref_in(x.unit), return Origin + quantity{fma(a.numerical_value_ref_in(a.unit), x.numerical_value_ref_in(x.unit),
b.quantity_ref_from(b.point_origin).numerical_value_ref_in(b.unit)), b.quantity_ref_from(b.point_origin).numerical_value_ref_in(b.unit)),
detail::make_delta(common_reference(R * S, T))}; common_reference(R * S, T)};
} }
/** /**
@ -277,7 +277,7 @@ template<auto R1, typename Rep1, auto R2, typename Rep2>
constexpr auto ref = common_reference(R1, R2); constexpr auto ref = common_reference(R1, R2);
constexpr auto unit = get_unit(ref); constexpr auto unit = get_unit(ref);
using std::fmod; using std::fmod;
return quantity{fmod(x.numerical_value_in(unit), y.numerical_value_in(unit)), detail::make_delta(ref)}; return quantity{fmod(x.numerical_value_in(unit), y.numerical_value_in(unit)), ref};
} }
/** /**
@ -294,7 +294,7 @@ template<auto R1, typename Rep1, auto R2, typename Rep2>
constexpr auto ref = common_reference(R1, R2); constexpr auto ref = common_reference(R1, R2);
constexpr auto unit = get_unit(ref); constexpr auto unit = get_unit(ref);
using std::remainder; using std::remainder;
return quantity{remainder(x.numerical_value_in(unit), y.numerical_value_in(unit)), detail::make_delta(ref)}; return quantity{remainder(x.numerical_value_in(unit), y.numerical_value_in(unit)), ref};
} }
@ -338,8 +338,8 @@ template<Unit auto To, auto R, typename Rep>
if constexpr (To == get_unit(R)) { if constexpr (To == get_unit(R)) {
return {static_cast<Rep>(floor(q.numerical_value_ref_in(q.unit))), detail::clone_reference_with<To>(R)}; return {static_cast<Rep>(floor(q.numerical_value_ref_in(q.unit))), detail::clone_reference_with<To>(R)};
} else { } else {
return handle_signed_results(quantity{static_cast<Rep>(floor(q.force_numerical_value_in(To))), return handle_signed_results(
detail::make_delta(detail::clone_reference_with<To>(R))}); quantity{static_cast<Rep>(floor(q.force_numerical_value_in(To))), detail::clone_reference_with<To>(R)});
} }
} else { } else {
if constexpr (To == get_unit(R)) { if constexpr (To == get_unit(R)) {
@ -375,8 +375,8 @@ template<Unit auto To, auto R, typename Rep>
if constexpr (To == get_unit(R)) { if constexpr (To == get_unit(R)) {
return {static_cast<Rep>(ceil(q.numerical_value_ref_in(q.unit))), detail::clone_reference_with<To>(R)}; return {static_cast<Rep>(ceil(q.numerical_value_ref_in(q.unit))), detail::clone_reference_with<To>(R)};
} else { } else {
return handle_signed_results(quantity{static_cast<Rep>(ceil(q.force_numerical_value_in(To))), return handle_signed_results(
detail::make_delta(detail::clone_reference_with<To>(R))}); quantity{static_cast<Rep>(ceil(q.force_numerical_value_in(To))), detail::clone_reference_with<To>(R)});
} }
} else { } else {
if constexpr (To == get_unit(R)) { if constexpr (To == get_unit(R)) {
@ -456,7 +456,7 @@ template<auto R1, typename Rep1, auto R2, typename Rep2>
constexpr auto ref = common_reference(R1, R2); constexpr auto ref = common_reference(R1, R2);
constexpr auto unit = get_unit(ref); constexpr auto unit = get_unit(ref);
using std::hypot; using std::hypot;
return quantity{hypot(x.numerical_value_in(unit), y.numerical_value_in(unit)), detail::make_delta(ref)}; return quantity{hypot(x.numerical_value_in(unit), y.numerical_value_in(unit)), ref};
} }
/** /**
@ -474,8 +474,7 @@ template<auto R1, typename Rep1, auto R2, typename Rep2, auto R3, typename Rep3>
constexpr auto ref = common_reference(R1, R2); constexpr auto ref = common_reference(R1, R2);
constexpr auto unit = get_unit(ref); constexpr auto unit = get_unit(ref);
using std::hypot; using std::hypot;
return quantity{hypot(x.numerical_value_in(unit), y.numerical_value_in(unit), z.numerical_value_in(unit)), return quantity{hypot(x.numerical_value_in(unit), y.numerical_value_in(unit), z.numerical_value_in(unit)), ref};
detail::make_delta(ref)};
} }
} // namespace mp_units } // namespace mp_units

View File

@ -27,6 +27,7 @@
#include <mp-units/systems/si/prefixes.h> #include <mp-units/systems/si/prefixes.h>
#ifndef MP_UNITS_IN_MODULE_INTERFACE #ifndef MP_UNITS_IN_MODULE_INTERFACE
#include <mp-units/framework/construction_helpers.h>
#include <mp-units/framework/quantity_point.h> #include <mp-units/framework/quantity_point.h>
#include <mp-units/framework/unit.h> #include <mp-units/framework/unit.h>
#endif #endif
@ -77,7 +78,7 @@ inline constexpr struct weber final : named_unit<"Wb", volt * second> {} weber;
inline constexpr struct tesla final : named_unit<"T", weber / square(metre)> {} tesla; inline constexpr struct tesla final : named_unit<"T", weber / square(metre)> {} tesla;
inline constexpr struct henry final : named_unit<"H", weber / ampere> {} henry; inline constexpr struct henry final : named_unit<"H", weber / ampere> {} henry;
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; inline constexpr auto zeroth_degree_Celsius = ice_point;
inline constexpr struct degree_Celsius final : named_unit<symbol_text{u8"°C", "`C"}, kelvin, zeroth_degree_Celsius> {} degree_Celsius; inline constexpr struct degree_Celsius final : named_unit<symbol_text{u8"°C", "`C"}, kelvin, zeroth_degree_Celsius> {} degree_Celsius;

View File

@ -118,7 +118,7 @@ inline constexpr struct troy_pound final : named_unit<"lb t", mag<12> * troy_onc
inline constexpr struct inch_of_mercury final : named_unit<"inHg", mag_ratio<3'386'389, 1'000> * si::pascal> {} inch_of_mercury; inline constexpr struct inch_of_mercury final : named_unit<"inHg", mag_ratio<3'386'389, 1'000> * si::pascal> {} inch_of_mercury;
// https://en.wikipedia.org/wiki/United_States_customary_units#Temperature // https://en.wikipedia.org/wiki/United_States_customary_units#Temperature
inline constexpr struct zeroth_degree_Fahrenheit final : relative_point_origin<-32 * absolute<mag_ratio<5, 9> * si::degree_Celsius>> {} zeroth_degree_Fahrenheit; inline constexpr struct zeroth_degree_Fahrenheit final : relative_point_origin<absolute<mag_ratio<5, 9> * si::degree_Celsius>(-32)> {} zeroth_degree_Fahrenheit;
inline constexpr struct degree_Fahrenheit final : named_unit<symbol_text{u8"°F", "`F"}, mag_ratio<5, 9> * si::degree_Celsius, zeroth_degree_Fahrenheit> {} degree_Fahrenheit; inline constexpr struct degree_Fahrenheit final : named_unit<symbol_text{u8"°F", "`F"}, mag_ratio<5, 9> * si::degree_Celsius, zeroth_degree_Fahrenheit> {} degree_Fahrenheit;
// clang-format on // clang-format on

View File

@ -801,12 +801,11 @@ static_assert(
////////////////////////////////// //////////////////////////////////
static_assert(quantity_point{42 * m}.quantity_from_zero() == 42 * m); static_assert(quantity_point{42 * m}.quantity_from_zero() == 42 * m);
static_assert((42 * absolute<m>).quantity_from_zero() == 42 * m);
static_assert(quantity_point{isq::height(42 * m)}.quantity_from_zero() == 42 * m); static_assert(quantity_point{isq::height(42 * m)}.quantity_from_zero() == 42 * m);
static_assert(quantity_point{20 * delta<deg_C>}.quantity_from_zero() == 20 * delta<deg_C>); static_assert(quantity_point{delta<deg_C>(20)}.quantity_from_zero() == delta<deg_C>(20));
static_assert((20 * absolute<deg_C>).quantity_from_zero() == 20 * delta<deg_C>); static_assert(quantity_point{delta<deg_C>(20.)}.in(deg_F).quantity_from_zero() == delta<deg_F>(68));
static_assert(quantity_point{20. * delta<deg_C>}.in(deg_F).quantity_from_zero() == 68 * delta<deg_F>); static_assert(absolute<deg_C>(20).quantity_from_zero() == delta<deg_C>(20));
static_assert((20. * absolute<deg_C>).in(deg_F).quantity_from_zero() == 68 * delta<deg_F>); static_assert(absolute<deg_C>(20.).in(deg_F).quantity_from_zero() == delta<deg_F>(68));
static_assert((mean_sea_level + 42 * m).quantity_from_zero() == 42 * m); static_assert((mean_sea_level + 42 * m).quantity_from_zero() == 42 * m);
static_assert((ground_level + 42 * m).quantity_from_zero() == 84 * m); static_assert((ground_level + 42 * m).quantity_from_zero() == 84 * m);
@ -892,12 +891,13 @@ static_assert(std::is_same_v<std::remove_const_t<decltype(quantity_point{isq::he
static_assert(quantity_point{isq::height(123 * m)}.unit == si::metre); static_assert(quantity_point{isq::height(123 * m)}.unit == si::metre);
static_assert(quantity_point{isq::height(123 * m)}.quantity_spec == isq::height); static_assert(quantity_point{isq::height(123 * m)}.quantity_spec == isq::height);
static_assert(std::is_same_v<decltype(20 * absolute<deg_C>)::rep, int>); static_assert(std::is_same_v<decltype(quantity_point{delta<deg_C>(20)})::rep, int>);
static_assert(std::is_same_v<std::remove_const_t<decltype((20 * absolute<deg_C>).point_origin)>, struct si::ice_point>); static_assert(
static_assert(std::is_same_v<std::remove_const_t<decltype((20 * absolute<deg_C>).absolute_point_origin)>, std::is_same_v<std::remove_const_t<decltype(quantity_point{delta<deg_C>(20)}.point_origin)>, struct si::ice_point>);
static_assert(std::is_same_v<std::remove_const_t<decltype(quantity_point{delta<deg_C>(20)}.absolute_point_origin)>,
struct si::absolute_zero>); struct si::absolute_zero>);
static_assert((20 * absolute<deg_C>).unit == si::degree_Celsius); static_assert(quantity_point{delta<deg_C>(20)}.unit == si::degree_Celsius);
static_assert((20 * absolute<deg_C>).quantity_spec == kind_of<isq::thermodynamic_temperature>); static_assert(quantity_point{delta<deg_C>(20)}.quantity_spec == kind_of<isq::thermodynamic_temperature>);
#if MP_UNITS_HOSTED #if MP_UNITS_HOSTED
using namespace std::chrono_literals; using namespace std::chrono_literals;

View File

@ -126,7 +126,7 @@ static_assert(isq::mass(1 * lb_t) == isq::mass(12 * oz_t));
static_assert(isq::pressure(1'000 * inHg) == isq::pressure(3'386'389 * si::pascal)); static_assert(isq::pressure(1'000 * inHg) == isq::pressure(3'386'389 * si::pascal));
// Temperature // Temperature
static_assert(isq::thermodynamic_temperature(9 * delta<deg_F>) == static_assert(delta<isq::thermodynamic_temperature[deg_F]>(9) ==
isq::thermodynamic_temperature(5 * delta<si::degree_Celsius>)); delta<isq::thermodynamic_temperature[si::degree_Celsius]>(5));
} // namespace } // namespace