Merge pull request #522 from mpusz/implicit_point_origin

feat: implementation of implicit point origins
This commit is contained in:
Mateusz Pusz
2023-12-26 11:09:43 +01:00
committed by GitHub
21 changed files with 903 additions and 251 deletions

View File

@@ -54,18 +54,6 @@ jobs:
# compiler: { type: MSVC, version: 193, cc: "", cxx: "" }, # compiler: { type: MSVC, version: 193, cc: "", cxx: "" },
# conan-config: "", # conan-config: "",
# } # }
- {
name: "GCC-11",
os: ubuntu-22.04,
compiler:
{
type: GCC,
version: 11,
cc: "gcc-11",
cxx: "g++-11",
},
conan-config: "",
}
- { - {
name: "GCC-12", name: "GCC-12",
os: ubuntu-22.04, os: ubuntu-22.04,

View File

@@ -53,17 +53,6 @@ jobs:
# os: windows-2022, # os: windows-2022,
# compiler: { type: MSVC, version: 193, cc: "", cxx: "" }, # compiler: { type: MSVC, version: 193, cc: "", cxx: "" },
# } # }
- {
name: "GCC-11",
os: ubuntu-22.04,
compiler:
{
type: GCC,
version: 11,
cc: "gcc-11",
cxx: "g++-11",
},
}
- { - {
name: "GCC-12", name: "GCC-12",
os: ubuntu-22.04, os: ubuntu-22.04,

View File

@@ -37,11 +37,11 @@ jobs:
contents: read contents: read
security-events: write security-events: write
env: env:
CC: gcc-11 CC: gcc-12
CXX: g++-11 CXX: g++-12
BUILD_TYPE: Debug BUILD_TYPE: Debug
COMPILER_TYPE: GCC COMPILER_TYPE: GCC
COMPILER_VERSION: 11 COMPILER_VERSION: 12
STDLIB: libstdc++11 STDLIB: libstdc++11
strategy: strategy:

View File

@@ -73,7 +73,7 @@ class MPUnitsConan(ConanFile):
@property @property
def _minimum_compilers_version(self): def _minimum_compilers_version(self):
return { return {
"gcc": "11", "gcc": "12",
"clang": "16", "clang": "16",
"apple-clang": "15" "apple-clang": "15"
# , "msvc": "192" # , "msvc": "192"

View File

@@ -96,10 +96,10 @@ to form a quantity.
!!! note !!! note
The same applies to the `quantity_point` construction. To prevent similar issues during The same applies to the construction of `quantity_point` using an explicit point origin.
construction, it always needs to get both a `quantity` and To prevent similar safety issues during maintenance, the initialization always requires
a [`PointOrigin`](../users_guide/framework_basics/concepts.md#PointOrigin) that we use providing both a `quantity` and a [`PointOrigin`](../users_guide/framework_basics/concepts.md#PointOrigin)
as a reference point. that we use as a reference point.
## Why a dimensionless quantity is not just a fundamental arithmetic type? ## Why a dimensionless quantity is not just a fundamental arithmetic type?

View File

@@ -14,9 +14,7 @@
As of today, the library compiles fine on the following compilers (or newer): As of today, the library compiles fine on the following compilers (or newer):
- gcc-11 - gcc-12
- please note that we observed some ICEs on gcc-11
- no problems with gcc-12.2+
- clang-16 - clang-16
- apple-clang-15 - apple-clang-15

View File

@@ -86,6 +86,8 @@ static_assert(140 * km / (2 * h) == 70 * km / h);
## Quantity points ## Quantity points
The quantity point specifies an absolute quantity with respect to an origin. The quantity point specifies an absolute quantity with respect to an origin.
If no origin is provided explicitly, an implicit one will be provided by the library.
Together with quantities, they model [The Affine Space](../users_guide/framework_basics/the_affine_space.md). Together with quantities, they model [The Affine Space](../users_guide/framework_basics/the_affine_space.md).
Quantity points should be used in all places where adding two values is meaningless Quantity points should be used in all places where adding two values is meaningless
@@ -106,10 +108,10 @@ int main()
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 = si::zeroth_degree_Celsius + 20. * deg_C; quantity_point temp{20. * deg_C};
std::cout << "Temperature: " std::cout << "Temperature: "
<< temp.quantity_from(si::zeroth_degree_Celsius) << " (" << temp.quantity_from_zero() << " ("
<< temp.in(deg_F).quantity_from(usc::zeroth_degree_Fahrenheit) << ")\n"; << temp.in(deg_F).quantity_from_zero() << ")\n";
} }
``` ```

View File

@@ -27,9 +27,7 @@ The library source code is hosted on [GitHub](https://github.com/mpusz/mp-units)
As of today, the library compiles fine on the following compilers (or newer): As of today, the library compiles fine on the following compilers (or newer):
- gcc-11 - gcc-12
- please note that we observed some ICEs on gcc-11
- no problems with gcc-12.2+
- clang-16 - clang-16
- apple-clang-15 - apple-clang-15

View File

@@ -68,145 +68,245 @@ As we already know, a `quantity` type provides all operations required for a _ve
the affine space. the affine space.
## _Point_ is modeled by `PointOrigin` and `quantity_point` ## _Point_ is modeled by `quantity_point` and `PointOrigin`
In the **mp-units** library the _point_ abstraction is modelled by: In the **mp-units** library the _point_ abstraction is modelled by:
- [`PointOrigin` concept](concepts.md#PointOrigin) that specifies measurement origin, - [`PointOrigin` concept](concepts.md#PointOrigin) that specifies measurement origin,
- `quantity_point` class template that specifies a _point_ relative to a specific predefined origin. - `quantity_point` class template that specifies a _point_ relative to a specific predefined origin.
### Absolute _point_ origin
The **absolute point origin** specifies where the "zero" of our measurement's scale is. User can
specify such an origin by deriving from the `absolute_point_origin` class template:
```cpp
constexpr struct mean_sea_level : absolute_point_origin<mean_sea_level, isq::altitude> {} mean_sea_level;
```
!!! info
The `absolute_point_origin` class template uses 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
### `quantity_point` ### `quantity_point`
The `quantity_point` class template specifies an absolute quantity with respect to an origin: The `quantity_point` class template specifies an absolute quantity measured from a predefined
origin:
```cpp ```cpp
template<Reference auto R, template<Reference auto R,
PointOriginFor<get_quantity_spec(R)> auto PO, PointOriginFor<get_quantity_spec(R)> auto PO = default_point_origin(R),
RepresentationOf<get_quantity_spec(R).character> Rep = double> RepresentationOf<get_quantity_spec(R).character> Rep = double>
class quantity_point; class quantity_point;
``` ```
As we can see above, the `quantity_point` class template exposes one additional parameter compared As we can see above, the `quantity_point` class template exposes one additional parameter compared
to `quantity`. The `PO` parameter satisfies a [`PointOriginFor` concept](concepts.md#PointOriginFor) to `quantity`. The `PO` parameter satisfies a [`PointOriginFor` concept](concepts.md#PointOriginFor)
and specifies the origin of our measurement scale. and specifies the origin of our measurement scale. By default, it is initialized with a quantity's
zeroth point using the following rules:
- if the measurement unit of a quantity specifies its point origin in its definition
(e.g., degree Celsius), then this point is being used,
- otherwise, an instantiation of `zeroth_point_origin<QuantitySpec>` is being used which
provides a zeroth point for a specific quantity type.
!!! tip !!! tip
`quantity_point` definition can be found in the `mp-units/quantity_point.h` header file. The `quantity_point` definition can be found in the `mp-units/quantity_point.h` header file.
As a _point_ can be represented with a _vector_ from the origin, a `quantity_point` class
template can be created with the following operations: ### Implicit point origin
Let's assume that Alice goes for a trip driving a car. She likes taking notes about interesting
places that she visits on the road. For every such item, she writes down:
- its name,
- a readout from the car's odometer at the location,
- a current timestamp.
We can implement this in the following way:
```cpp ```cpp
quantity_point qp1 = mean_sea_level + 42 * m; using std::chrono::system_clock;
quantity_point qp2 = 42 * m + mean_sea_level;
quantity_point qp3 = mean_sea_level - 42 * m; struct trip_log_item {
std::string name;
quantity_point<isq::distance[km]> odometer;
quantity_point<si::second> timestamp;
};
using trip_log = std::vector<trip_log_item>;
``` ```
```cpp
trip_log log;
quantity_point timestamp_1{quantity{system_clock::now().time_since_epoch()}};
log.emplace_back("home", quantity_point{1356 * km}, timestamp_1);
// some time passes
quantity_point timestamp_2{quantity{system_clock::now().time_since_epoch()}};
log.emplace_back("castle", quantity_point{1401 * km}, timestamp_2);
```
This is an excellent example of where points are helpful. There is no doubt about the correctness
of their usage in this scenario:
- adding two odometer readouts or two timestamps have no physical sense, and that is why we will
expect a compile-time error when we try to perform such operations accidentally,
- subtracting two odometer readouts or timestamps is perfectly valid and results in a quantity
storing the interval value between the two points.
Having such a database, we can print the trip log in the following way:
```cpp
for (const auto& item : log) {
std::cout << "POI: " << item.name << "\n";
std::cout << "- Distance from home: " << item.odometer - log.front().odometer;
std::cout << "- Trip duration from start: " << (item.timestamp - log.front().timestamp).in(non_si::minute);
}
```
Moreover, if Alice had reset the car's trip odometer before leaving home, we could have rewritten
one of the previous lines like that:
```cpp
std::cout << "Distance from home: " << item.odometer.quantity_from_zero();
```
The above always returns a quantity measured from the "ultimate" zeroth point of a scale used for
this specific quantity type.
!!! tip
Storing _points_ is the most efficient representation we can choose in this scenario:
- to store a value, we read it directly from the instrument, and no additional transformation
is needed,
- to print the absolute value (e.g., odometer), we have the value available right away,
- to get any relative quantity (e.g., distance from the start, distance from the previous point,
etc.), we have to perform a single subtraction operation.
If we stored _vectors_ in our database instead, we would have to pay at runtime for additional
operations:
- to store a quantity, we would have to perform the subtraction right away to get the interval
between the current value and some reference point,
- to print the absolute value, we would have to add the quantity to the reference point that
we need to store somewhere in the database as well,
- to get a relative quantity, only the currently stored one is immediate; all other values
will require at least one quantity addition operation.
Now, let's assume that Bob, a friend of Alice, also keeps a log of his trips but he, of
course, measures distances from his own home with the odometer in his car. Everything is fine as
long as we deal with one trip at a time, but if we start to work with both at once, we may
accidentally subtract points from different trips. The library will not prevent
us from doing so.
The points from Alice's and Bob's trips should be considered separate, and to enforce it at
compilation time, we need to introduce explicit origins.
### Absolute _point_ origin
The **absolute point origin** specifies the "zero" of our measurement's scale. User can
specify such an origin by deriving from the `absolute_point_origin` class template:
```cpp
enum class actor { alice, bob };
template<actor A>
struct zeroth_odometer_t : absolute_point_origin<zeroth_odometer_t<A>, isq::distance> {};
template<actor A>
inline constexpr zeroth_odometer_t<A> zeroth_odometer;
```
!!! 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
!!! note
Unfortunately, due to inconsistencies in C++ language rules:
- we can't define the above in one line of code,
- provide the same identifier for a class and variable template.
Odometer is not the only one that can get an explicit point origin in our case. As timestamps are
provided by the `std::chrono::system_clock`, their values are always relative to the epoch of this
clock.
!!! note
The **mp-units** library provides means to specify
[interoperability with other units libraries](../use_cases/interoperability_with_other_units_libraries.md).
It also has built-in compatibility with `std::chrono` types, so users do not have to define
interoperability traits for such types by themselves. Those are provided in the
`mp-units/chrono.h` header file.
Now, we can refactor our database to benefit from the explicit points:
```cpp
template<actor A>
struct trip_log_item {
std::string point_name;
quantity_point<si::kilo<si::metre>, zeroth_odometer<A>> odometer;
quantity_point<si::second, chrono_point_origin<system_clock>> timestamp;
};
template<actor A>
using trip_log = std::vector<trip_log_item<A>>;
```
We also need to update the initialization part in our code. In the case of implicit zeroth origins,
we could construct `quantity_point` directly from the value of a `quantity`. This is no longer
the case.
As a _point_ can be represented with a _vector_ from the origin, to improve the safety of the code
we write, a `quantity_point` class template must be created with one of the following operations:
```cpp
quantity_point qp1 = zeroth_odometer<actor::alice> + 1356 * km;
quantity_point qp2 = 1356 * km + zeroth_odometer<actor::alice>;
quantity_point qp3 = zeroth_odometer<actor::alice> - 1356 * km;
```
Although, the `qp3` above does not have a physical sense in this specific scenario.
!!! note !!! note
[It is not allowed to subtract a _point_ from a _vector_](#operations-in-the-affine-space) [It is not allowed to subtract a _point_ from a _vector_](#operations-in-the-affine-space)
thus `42 * m - mean_sea_level` is an invalid operation. thus `1356 * km - zeroth_odometer<actor::alice>` is an invalid operation.
!!! info
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.
Similarly to [creation of a quantity](../../getting_started/quick_start.md#creating-a-quantity), 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 if someone does not like the operator-based syntax to create a `quantity_point`, the same results
can be achieved with two-parameter constructor: can be achieved with a two-parameter constructor:
```cpp ```cpp
quantity_point qp4{42 * m, mean_sea_level}; quantity_point qp4{1356 * km, zeroth_odometer<actor::alice>};
quantity_point qp5{-42 * m, mean_sea_level};
``` ```
The provided `quantity` representing an offset from the origin is stored inside the `quantity_point` Also, as now our timestamps have a proper point origin provided in a type, we can simplify the
class template and can be obtained with a `quantity_from(PointOrigin)` member function: previous code by directly converting `std::chrono::time_point` to our `quantity_point` type.
With all the above, we can refactor our initialization part to the following:
```cpp ```cpp
constexpr quantity_point everest_base_camp_alt = mean_sea_level + isq::altitude(5364 * m); trip_log<actor::alice> alice_log;
static_assert(everest_base_camp_alt.quantity_from(mean_sea_level) == 5364 * m);
alice_log.emplace_back("home", zeroth_odometer<actor::alice> + 1356 * km, system_clock::now());
// some time passes
alice_log.emplace_back("castle", zeroth_odometer<actor::alice> + 1401 * km, system_clock::now());
``` ```
### Relative _point_ origin
We often do not have only one ultimate "zero" point when we measure things.
Continuing the Mount Everest trip example above, measuring all daily hikes from the `mean_sea_level`
might not be efficient. Maybe we know that we are not good climbers, so all our climbs can be
represented with an 8-bit integer type allowing us to save memory in our database of climbs?
Why not use `everest_base_camp_alt` as our reference point?
For this purpose, we can define a `relative_point_origin` in the following way:
```cpp
constexpr struct everest_base_camp : relative_point_origin<everest_base_camp_alt> {} everest_base_camp;
```
The above can be used as an origin for subsequent _points_:
```cpp
constexpr quantity_point first_climb_alt = everest_base_camp + isq::altitude(std::uint8_t{42} * m);
static_assert(first_climb_alt.quantity_from(everest_base_camp) == 42 * m);
static_assert(first_climb_alt.quantity_from(mean_sea_level) == 5406 * m);
```
As we can see above, the `quantity_from()` member function returns a relative distance from the
provided point origin.
### Converting between different representations of the same _point_
As we might represent the same _point_ with _vectors_ from various origins, the **mp-units** library
provides facilities to convert the _point_ to the `quantity_point` class templates expressed in terms
of different origins.
For this purpose, we can either use:
- a converting constructor:
```cpp
constexpr quantity_point<isq::altitude[m], mean_sea_level, int> qp = first_climb_alt;
static_assert(qp.quantity_ref_from(qp.point_origin) == 5406 * m);
```
- a dedicated conversion interface:
```cpp
constexpr quantity_point qp = first_climb_alt.point_for(mean_sea_level);
static_assert(qp.quantity_ref_from(qp.point_origin) == 5406 * m);
```
!!! note
It is only allowed to convert between various origins defined in terms of the same
`absolute_point_origin`. Even if it is possible to express the same _point_ as a _vector_
from another `absolute_point_origin`, the library will not provide such a conversion.
A custom user-defined conversion function will be needed to add this functionality.
Said otherwise, in the **mp-units** library, there is no way to spell how two distinct
`absolute_point_origin` types relate to each other.
### _Point_ arithmetics ### _Point_ arithmetics
Let's assume we will attend the CppCon conference hosted in Aurora, CO, and we want to estimate As another example, let's assume we will attend the CppCon conference hosted in Aurora, CO,
the distance we will travel. We have to take a taxi to a local airport, fly to DEN airport with and we want to estimate the distance we will travel. We have to take a taxi to a local airport,
a stopover in FRA, and, in the end, get a cab to the Gaylord Rockies Resort & Convention Center: fly to DEN airport with a stopover in FRA, and, in the end, get a cab to the Gaylord Rockies
Resort & Convention Center:
```cpp ```cpp
constexpr struct home : absolute_point_origin<home, isq::distance> {} home; constexpr struct home : absolute_point_origin<home, isq::distance> {} home;
@@ -255,70 +355,203 @@ Taxi distance: 31.2544 km
!!! note !!! note
It is not allowed to subtract two point origins defined in terms of `absolute_point_origin` It is not allowed to subtract two point origins defined in terms of `absolute_point_origin`
(e.g. `mean_sea_level - mean_sea_level`) as those do not contain information about the unit (e.g., `home - home`) as those do not contain information about the unit, so we are not able
so we are not able to determine a resulting `quantity` type. to determine a resulting `quantity` type.
### Relative _point_ origin
We often do not have only one ultimate "zero" point when we measure things.
For example, let's assume that we have the following absolute point origin:
```cpp
constexpr struct mean_sea_level : absolute_point_origin<mean_sea_level, isq::altitude> {} mean_sea_level;
```
If we want to model a trip to Mount Everest, measuring all daily hikes from the `mean_sea_level`
might not be efficient. We may know that we are not good climbers, so all our climbs can be
represented with an 8-bit integer type, allowing us to save memory in our database of climbs.
For this purpose, we can define a `relative_point_origin` in the following way:
```cpp
constexpr struct everest_base_camp : relative_point_origin<mean_sea_level + 5364 * m> {} everest_base_camp;
```
The above can be used as an origin for subsequent _points_:
```cpp
constexpr quantity_point first_climb_alt = everest_base_camp + isq::altitude(std::uint8_t{42} * m);
static_assert(first_climb_alt.quantity_from(everest_base_camp) == 42 * m);
static_assert(first_climb_alt.quantity_from(mean_sea_level) == 5406 * m);
static_assert(first_climb_alt.quantity_from_zero() == 5406 * m);
```
As we can see above, the `quantity_from()` member function returns a relative distance from the
provided point origin while the `quantity_from_zero()` returns the distance from the absolute point
origin.
### Converting between different representations of the same _point_
As we might represent the same _point_ with _vectors_ from various origins, the **mp-units** library
provides facilities to convert the _point_ to `quantity_point` class templates expressed in
terms of origins relative to each other in the type system.
For this purpose, we can use:
- a converting constructor:
```cpp
constexpr quantity_point<isq::altitude[m], mean_sea_level, int> qp = first_climb_alt;
static_assert(qp.quantity_ref_from(qp.point_origin) == 5406 * m);
```
- a dedicated conversion interface:
```cpp
constexpr quantity_point qp = first_climb_alt.point_for(mean_sea_level);
static_assert(qp.quantity_ref_from(qp.point_origin) == 5406 * m);
```
!!! note
It is only allowed to convert between various origins defined in terms of the same
`absolute_point_origin`. Even if it is theoretically possible to express the same _point_ as
a _vector_ from another `absolute_point_origin`, the library will not allow such a conversion.
A custom user-defined conversion function will be needed to add this functionality.
Said otherwise, in the **mp-units** library, there is no way to spell how two distinct
`absolute_point_origin` types relate to each other.
### Temperature support ### Temperature support
Another important example of [relative point origins](#relative-point-origins) is support Another important example of [relative point origins](#relative-point-origins) is the support
of temperature quantity points in units different than kelvin [`K`]. of temperature quantity points. The **mp-units** library provides a few predefined point origins
for this purpose:
The [SI](../../appendix/glossary.md#si) system definition in the **mp-units** library provides
two predefined point origins:
```cpp ```cpp
namespace mp_units::si { namespace si {
inline constexpr struct absolute_zero : absolute_point_origin<absolute_zero, isq::thermodynamic_temperature> {} absolute_zero; inline constexpr struct absolute_zero : absolute_point_origin<absolute_zero, isq::thermodynamic_temperature> {} absolute_zero;
inline constexpr struct ice_point : relative_point_origin<absolute_zero + 273.15 * kelvin> {} ice_point; inline constexpr struct zeroth_kelvin : decltype(absolute_zero) {} zeroth_kelvin;
inline constexpr struct ice_point : relative_point_origin<quantity_point{273.15 * kelvin}> {} ice_point;
inline constexpr struct zeroth_degree_Celsius : decltype(ice_point) {} zeroth_degree_Celsius;
}
namespace usc {
inline constexpr struct zeroth_degree_Fahrenheit :
relative_point_origin<si::zeroth_degree_Celsius - 32 * (mag<ratio{5, 9}> * si::degree_Celsius)> {} zeroth_degree_Fahrenheit;
} }
``` ```
With the above, we can be explicit what is the origin of our temperature point. For example, if The above is a great example of how point origins can be stacked on top of each other:
we want to implement the degree Celsius scale we can do it as follows:
```cpp - `usc::zeroth_degree_Fahrenheit` is defined relative to `si::zeroth_degree_Celsius`
using Celsius_point = quantity_point<isq::Celsius_temperature[deg_C], si::ice_point>; - `si::zeroth_degree_Celsius` is defined relative to `si::zeroth_kelvin`.
```
!!! note !!! note
Notice that while stacking point origins, we can use not only different representation types Notice that while stacking point origins, we can use not only different representation types
but also different units for an origin and a _point_. In the above example, the relative but also different units for origins and a _point_. In the above example, the relative
point origin is defined in terms of `si::kelvin`, while the quantity point uses point origin for degree Celsius is defined in terms of `si::kelvin`, while the quantity point
`si::degree_Celsius`. for it will use `si::degree_Celsius` as a unit.
To play a bit with temperatures we can implement a simple room's AC temperature controller in 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 :
named_unit<basic_symbol_text{"°C", "`C"}, kelvin, zeroth_degree_Celsius> {} degree_Celsius;
}
namespace usc {
inline constexpr struct degree_Fahrenheit :
named_unit<basic_symbol_text{"°F", "`F"}, mag<ratio{5, 9}> * si::degree_Celsius,
zeroth_degree_Fahrenheit> {} degree_Fahrenheit;
}
```
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 taste 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 * 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
the following way: the following way:
```cpp ```cpp
constexpr struct room_reference_temp : relative_point_origin<si::ice_point + 21 * deg_C> {} room_reference_temp; constexpr struct room_reference_temp : relative_point_origin<quantity_point{21 * deg_C}> {} 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 * deg_C); constexpr auto step_delta = isq::Celsius_temperature(0.5 * deg_C);
constexpr int number_of_steps = 6; constexpr int number_of_steps = 6;
room_temp room_low = room_reference_temp - number_of_steps * step_delta; room_temp room_ref{};
room_temp room_high = room_reference_temp + number_of_steps * step_delta; room_temp room_low = room_ref - number_of_steps * step_delta;
room_temp room_high = room_ref + number_of_steps * step_delta;
std::println("| {:<14} | {:^18} | {:^18} | {:^18} |", "Temperature", "Room reference", "Ice point", "Absolute zero"); std::println("Room reference temperature: {} ({}, {})\n",
room_ref.quantity_from_zero(),
room_ref.in(usc::degree_Fahrenheit).quantity_from_zero(),
room_ref.in(si::kelvin).quantity_from_zero());
std::println("| {:<14} | {:^18} | {:^18} | {:^18} |",
"Temperature", "Room reference", "Ice point", "Absolute zero");
std::println("|{0:=^16}|{0:=^20}|{0:=^20}|{0:=^20}|", ""); std::println("|{0:=^16}|{0:=^20}|{0:=^20}|{0:=^20}|", "");
auto print = [&](std::string_view label, auto v){ auto print = [&](std::string_view label, auto v) {
std::println("| {:<14} | {:^18} | {:^18} | {:^18} |", fmt::println("| {:<14} | {:^18} | {:^18} | {:^18} |", label,
label, v - room_reference_temp, v - si::ice_point, v - si::absolute_zero); v - room_reference_temp, v - si::ice_point, v - si::absolute_zero);
}; };
print("Lowest", room_low); print("Lowest", room_low);
print("Default", room_reference_temp); print("Default", room_ref);
print("Highest", room_high); print("Highest", room_high);
``` ```
The above prints: The above prints:
```text ```text
Room reference temperature: 21 °C (69.8 °F, 294.15 K)
| Temperature | Room reference | Ice point | Absolute zero | | Temperature | Room reference | Ice point | Absolute zero |
|================|====================|====================|====================| |================|====================|====================|====================|
| Lowest | -3 °C | 18 °C | 291.15 °C | | Lowest | -3 °C | 18 °C | 291.15 °C |

View File

@@ -271,9 +271,13 @@ The **mp-units** library comes with built-in interoperability with those types.
quantity_point qp4 = my_origin + 1 * s; quantity_point qp4 = my_origin + 1 * s;
auto tp4 = to_chrono_time_point(qp4); // Compile-time Error (1) auto tp4 = to_chrono_time_point(qp4); // Compile-time Error (1)
quantity_point qp5{1 * s};
auto tp5 = to_chrono_time_point(qp5); // Compile-time Error (2)
``` ```
1. `my_origin` is not defined in terms of `chrono_point_origin<Clock>`. 1. `my_origin` is not defined in terms of `chrono_point_origin<Clock>`.
2. `zeroth_point_origin` is not defined in terms of `chrono_point_origin<Clock>`.
Here is an example of how interoperability described in this chapter can be used in practice: Here is an example of how interoperability described in this chapter can be used in practice:

View File

@@ -33,8 +33,6 @@ inline constexpr struct dim_currency : base_dimension<"$"> {} dim_currency;
QUANTITY_SPEC(currency, dim_currency); QUANTITY_SPEC(currency, dim_currency);
constexpr struct zero : absolute_point_origin<zero, currency> {} zero;
inline constexpr struct euro : named_unit<"EUR", kind_of<currency>> {} euro; inline constexpr struct euro : named_unit<"EUR", kind_of<currency>> {} euro;
inline constexpr struct us_dollar : named_unit<"USD", kind_of<currency>> {} us_dollar; inline constexpr struct us_dollar : named_unit<"USD", kind_of<currency>> {} us_dollar;
inline constexpr struct great_british_pound : named_unit<"GBP", kind_of<currency>> {} great_british_pound; inline constexpr struct great_british_pound : named_unit<"GBP", kind_of<currency>> {} great_british_pound;
@@ -90,18 +88,18 @@ quantity<To, Rep> exchange_to(quantity<From, Rep> q)
template<ReferenceOf<currency> auto To, ReferenceOf<currency> auto From, auto PO, typename Rep> template<ReferenceOf<currency> auto To, ReferenceOf<currency> auto From, auto PO, typename Rep>
quantity_point<To, PO, Rep> exchange_to(quantity_point<From, PO, Rep> q) quantity_point<To, PO, Rep> exchange_to(quantity_point<From, PO, Rep> q)
{ {
return quantity_point{zero + static_cast<Rep>(exchange_rate<q.unit, get_unit(To)>() * return quantity_point{
(q - q.absolute_point_origin).numerical_value_in(q.unit)) * static_cast<Rep>(exchange_rate<q.unit, get_unit(To)>() * q.quantity_from_zero().numerical_value_in(q.unit)) * To};
To};
} }
int main() int main()
{ {
using namespace unit_symbols; using namespace unit_symbols;
quantity_point price_usd = zero + 100 * USD; quantity_point price_usd{100 * USD};
quantity_point price_euro = exchange_to<euro>(price_usd); quantity_point price_euro = exchange_to<euro>(price_usd);
std::cout << price_usd.quantity_from(zero) << " -> " << price_euro.quantity_from(zero) << "\n"; std::cout << price_usd.quantity_from_zero() << " -> " << price_euro.quantity_from_zero() << "\n";
// std::cout << price_usd.quantity_from(zero) + price_euro.quantity_from(zero) << "\n"; // does not compile // std::cout << price_usd.quantity_from_zero() + price_euro.quantity_from_zero() << "\n"; // does
// not compile
} }

View File

@@ -178,10 +178,10 @@ distance spherical_distance(position<T> from, position<T> to)
using si::sin, si::cos, si::asin, si::acos; using si::sin, si::cos, si::asin, si::acos;
const quantity from_lat = from.lat.quantity_from(equator); const quantity from_lat = from.lat.quantity_from_zero();
const quantity from_lon = from.lon.quantity_from(prime_meridian); const quantity from_lon = from.lon.quantity_from_zero();
const quantity to_lat = to.lat.quantity_from(equator); const quantity to_lat = to.lat.quantity_from_zero();
const quantity to_lon = to.lon.quantity_from(prime_meridian); const quantity to_lon = to.lon.quantity_from_zero();
// https://en.wikipedia.org/wiki/Great-circle_distance#Formulae // https://en.wikipedia.org/wiki/Great-circle_distance#Formulae
if constexpr (sizeof(T) >= 8) { if constexpr (sizeof(T) >= 8) {

View File

@@ -97,8 +97,8 @@ template<earth_gravity_model M>
hae_altitude<M> to_hae(msl_altitude msl, position<long double> pos) hae_altitude<M> to_hae(msl_altitude msl, position<long double> pos)
{ {
const auto geoid_undulation = const auto geoid_undulation =
isq::height(GeographicLibWhatsMyOffset(pos.lat.quantity_from(equator).numerical_value_in(si::degree), isq::height(GeographicLibWhatsMyOffset(pos.lat.quantity_from_zero().numerical_value_in(si::degree),
pos.lon.quantity_from(prime_meridian).numerical_value_in(si::degree)) * pos.lon.quantity_from_zero().numerical_value_in(si::degree)) *
si::metre); si::metre);
return height_above_ellipsoid<M> + (msl - mean_sea_level - geoid_undulation); return height_above_ellipsoid<M> + (msl - mean_sea_level - geoid_undulation);
} }

View File

@@ -63,19 +63,52 @@ struct relative_point_origin {
static constexpr PointOrigin auto absolute_point_origin = QP.absolute_point_origin; static constexpr PointOrigin auto absolute_point_origin = QP.absolute_point_origin;
}; };
template<QuantitySpec auto QS>
struct zeroth_point_origin_ : absolute_point_origin<zeroth_point_origin_<QS>, QS> {};
template<QuantitySpec auto QS>
inline constexpr zeroth_point_origin_<QS> zeroth_point_origin;
namespace detail {
template<PointOrigin PO>
inline constexpr bool is_specialization_of_zeroth_point_origin = false;
template<auto QS>
inline constexpr bool is_specialization_of_zeroth_point_origin<zeroth_point_origin_<QS>> = true;
template<PointOrigin PO>
[[nodiscard]] consteval bool is_zeroth_point_origin(PO)
{
return is_specialization_of_zeroth_point_origin<PO>;
}
} // namespace detail
template<PointOrigin PO1, PointOrigin PO2> template<PointOrigin PO1, PointOrigin PO2>
[[nodiscard]] consteval bool operator==(PO1 po1, PO2 po2) [[nodiscard]] consteval bool operator==(PO1 po1, PO2 po2)
{ {
if constexpr (detail::AbsolutePointOrigin<PO1> && detail::AbsolutePointOrigin<PO2>) if constexpr (detail::AbsolutePointOrigin<PO1> && detail::AbsolutePointOrigin<PO2>)
return is_same_v<typename PO1::_type_, typename PO2::_type_>; return is_same_v<typename PO1::_type_, typename PO2::_type_> ||
(detail::is_zeroth_point_origin(po1) && detail::is_zeroth_point_origin(po2) &&
interconvertible(po1.quantity_spec, po2.quantity_spec));
else if constexpr (detail::RelativePointOrigin<PO1> && detail::RelativePointOrigin<PO2>) else if constexpr (detail::RelativePointOrigin<PO1> && detail::RelativePointOrigin<PO2>)
return PO1::quantity_point == PO2::quantity_point; return PO1::quantity_point == PO2::quantity_point;
else if constexpr (detail::RelativePointOrigin<PO1>) else if constexpr (detail::RelativePointOrigin<PO1>)
return detail::same_absolute_point_origins(po1, po2) && return detail::same_absolute_point_origins(po1, po2) &&
detail::is_eq_zero(PO1::quantity_point.quantity_from(PO1::quantity_point.absolute_point_origin)); detail::is_eq_zero(PO1::quantity_point.quantity_from_zero());
else if constexpr (detail::RelativePointOrigin<PO2>) else if constexpr (detail::RelativePointOrigin<PO2>)
return detail::same_absolute_point_origins(po1, po2) && return detail::same_absolute_point_origins(po1, po2) &&
detail::is_eq_zero(PO2::quantity_point.quantity_from(PO2::quantity_point.absolute_point_origin)); detail::is_eq_zero(PO2::quantity_point.quantity_from_zero());
}
template<Reference R>
[[nodiscard]] consteval PointOriginFor<get_quantity_spec(R{})> auto default_point_origin(R)
{
if constexpr (requires { get_unit(R{}).point_origin; })
return get_unit(R{}).point_origin;
else
return zeroth_point_origin<get_quantity_spec(R{})>;
} }
namespace detail { namespace detail {
@@ -100,7 +133,7 @@ template<PointOrigin PO>
* @tparam PO a type that represents the origin point from which the quantity point is measured from * @tparam PO a type that represents the origin point from which the quantity point is measured from
* @tparam Rep a type to be used to represent values of a quantity point * @tparam Rep a type to be used to represent values of a quantity point
*/ */
template<Reference auto R, PointOriginFor<get_quantity_spec(R)> auto PO, template<Reference auto R, PointOriginFor<get_quantity_spec(R)> auto PO = default_point_origin(R),
RepresentationOf<get_quantity_spec(R).character> Rep = double> RepresentationOf<get_quantity_spec(R).character> Rep = double>
class quantity_point { class quantity_point {
public: public:
@@ -135,7 +168,14 @@ public:
quantity_point(quantity_point&&) = default; quantity_point(quantity_point&&) = default;
template<typename Q> template<typename Q>
requires std::same_as<std::remove_cvref_t<Q>, quantity_type> requires QuantityOf<std::remove_cvref_t<Q>, get_quantity_spec(R)> && std::constructible_from<quantity_type, Q> &&
(point_origin == default_point_origin(R)) && (implicitly_convertible(Q::quantity_spec, quantity_spec))
constexpr explicit quantity_point(Q&& q) : quantity_from_origin_is_an_implementation_detail_(std::forward<Q>(q))
{
}
template<typename Q>
requires QuantityOf<std::remove_cvref_t<Q>, get_quantity_spec(R)> && std::constructible_from<quantity_type, Q>
constexpr quantity_point(Q&& q, std::remove_const_t<decltype(PO)>) : constexpr quantity_point(Q&& q, std::remove_const_t<decltype(PO)>) :
quantity_from_origin_is_an_implementation_detail_(std::forward<Q>(q)) quantity_from_origin_is_an_implementation_detail_(std::forward<Q>(q))
{ {
@@ -219,6 +259,20 @@ public:
return *this - PO2{}; return *this - PO2{};
} }
[[nodiscard]] constexpr Quantity auto quantity_from_zero() const
{
if constexpr (requires { unit.point_origin; }) {
// original quantity point unit can be lost in the below operation
const auto q = quantity_from(unit.point_origin);
if constexpr (requires { q.in(unit); })
// restore the unit if possible (non-truncating)
return q.in(unit);
else
return q;
} else
return quantity_from(absolute_point_origin);
}
// unit conversions // unit conversions
template<UnitCompatibleWith<unit, quantity_spec> U> template<UnitCompatibleWith<unit, quantity_spec> U>
requires detail::QuantityConvertibleTo<quantity_type, quantity<detail::make_reference(quantity_spec, U{}), Rep>> requires detail::QuantityConvertibleTo<quantity_type, quantity<detail::make_reference(quantity_spec, U{}), Rep>>
@@ -322,6 +376,9 @@ public:
}; };
// CTAD // CTAD
template<Quantity Q>
quantity_point(Q q) -> quantity_point<Q::reference, default_point_origin(Q::reference), typename Q::rep>;
template<Quantity Q, PointOriginFor<Q::quantity_spec> PO> template<Quantity Q, PointOriginFor<Q::quantity_spec> PO>
quantity_point(Q q, PO) -> quantity_point<Q::reference, PO{}, typename Q::rep>; quantity_point(Q q, PO) -> quantity_point<Q::reference, PO{}, typename Q::rep>;
@@ -339,6 +396,9 @@ template<auto R1, auto PO1, typename Rep1, auto R2, typename Rep2>
const quantity<R2, Rep2>& q) const quantity<R2, Rep2>& q)
requires requires { qp.quantity_ref_from(PO1) + q; } requires requires { qp.quantity_ref_from(PO1) + q; }
{ {
if constexpr (detail::is_zeroth_point_origin(PO1))
return quantity_point{qp.quantity_ref_from(PO1) + q};
else
return quantity_point{qp.quantity_ref_from(PO1) + q, PO1}; return quantity_point{qp.quantity_ref_from(PO1) + q, PO1};
} }
@@ -373,6 +433,9 @@ template<auto R1, auto PO1, typename Rep1, auto R2, typename Rep2>
const quantity<R2, Rep2>& q) const quantity<R2, Rep2>& q)
requires requires { qp.quantity_ref_from(PO1) - q; } requires requires { qp.quantity_ref_from(PO1) - q; }
{ {
if constexpr (detail::is_zeroth_point_origin(PO1))
return quantity_point{qp.quantity_ref_from(PO1) - q};
else
return quantity_point{qp.quantity_ref_from(PO1) - q, PO1}; return quantity_point{qp.quantity_ref_from(PO1) - q, PO1};
} }

View File

@@ -1401,6 +1401,12 @@ template<QuantitySpec From, QuantitySpec To>
return detail::convertible_impl(from, to) >= detail::specs_convertible_result::cast; return detail::convertible_impl(from, to) >= detail::specs_convertible_result::cast;
} }
template<QuantitySpec QS1, QuantitySpec QS2>
[[nodiscard]] consteval bool interconvertible(QS1 qs1, QS2 qs2)
{
return implicitly_convertible(qs1, qs2) && implicitly_convertible(qs2, qs1);
}
namespace detail { namespace detail {
template<QuantitySpec Q> template<QuantitySpec Q>

View File

@@ -29,6 +29,7 @@
#include <mp-units/bits/external/type_traits.h> #include <mp-units/bits/external/type_traits.h>
#include <mp-units/bits/get_associated_quantity.h> #include <mp-units/bits/get_associated_quantity.h>
#include <mp-units/bits/magnitude.h> #include <mp-units/bits/magnitude.h>
#include <mp-units/bits/quantity_point_concepts.h>
#include <mp-units/bits/quantity_spec_concepts.h> #include <mp-units/bits/quantity_spec_concepts.h>
#include <mp-units/bits/ratio.h> #include <mp-units/bits/ratio.h>
#include <mp-units/bits/symbol_text.h> #include <mp-units/bits/symbol_text.h>
@@ -39,6 +40,18 @@
namespace mp_units { namespace mp_units {
namespace detail {
template<Unit U, bool = requires { U::point_origin; }>
struct propagate_point_origin {};
template<Unit U>
struct propagate_point_origin<U, true> {
static constexpr auto point_origin = U::point_origin;
};
} // namespace detail
/** /**
* @brief Unit being a scaled version of another unit * @brief Unit being a scaled version of another unit
* *
@@ -49,7 +62,7 @@ namespace mp_units {
* instantiate this type automatically based on the unit arithmetic equation provided by the user. * instantiate this type automatically based on the unit arithmetic equation provided by the user.
*/ */
template<Magnitude auto M, Unit U> template<Magnitude auto M, Unit U>
struct scaled_unit { struct scaled_unit : detail::propagate_point_origin<U> {
static constexpr MP_UNITS_CONSTRAINED_AUTO_WORKAROUND(Magnitude) auto mag = M; static constexpr MP_UNITS_CONSTRAINED_AUTO_WORKAROUND(Magnitude) auto mag = M;
static constexpr U reference_unit{}; static constexpr U reference_unit{};
}; };
@@ -116,6 +129,14 @@ struct named_unit<Symbol, QS> {
static constexpr auto quantity_spec = QS; static constexpr auto quantity_spec = QS;
}; };
template<basic_symbol_text Symbol, detail::QuantityKindSpec auto QS, PointOrigin auto PO>
requires(!Symbol.empty()) && detail::BaseDimension<std::remove_const_t<decltype(QS.dimension)>>
struct named_unit<Symbol, QS, PO> {
static constexpr auto symbol = Symbol; ///< Unique base unit identifier
static constexpr auto quantity_spec = QS;
static constexpr auto point_origin = PO;
};
/** /**
* @brief Specialization for a unit that can be reused by several base quantities * @brief Specialization for a unit that can be reused by several base quantities
* *
@@ -146,6 +167,13 @@ struct named_unit<Symbol, U> : std::remove_const_t<decltype(U)> {
static constexpr auto symbol = Symbol; ///< Unique unit identifier static constexpr auto symbol = Symbol; ///< Unique unit identifier
}; };
template<basic_symbol_text Symbol, Unit auto U, PointOrigin auto PO>
requires(!Symbol.empty())
struct named_unit<Symbol, U, PO> : std::remove_const_t<decltype(U)> {
static constexpr auto symbol = Symbol; ///< Unique unit identifier
static constexpr auto point_origin = PO;
};
/** /**
* @brief Specialization for a unit with special name valid only for a specific quantity * @brief Specialization for a unit with special name valid only for a specific quantity
* *
@@ -162,6 +190,14 @@ struct named_unit<Symbol, U, QS> : std::remove_const_t<decltype(U)> {
static constexpr auto quantity_spec = QS; static constexpr auto quantity_spec = QS;
}; };
template<basic_symbol_text Symbol, AssociatedUnit auto U, detail::QuantityKindSpec auto QS, PointOrigin auto PO>
requires(!Symbol.empty()) && (QS.dimension == detail::get_associated_quantity(U).dimension)
struct named_unit<Symbol, U, QS, PO> : std::remove_const_t<decltype(U)> {
static constexpr auto symbol = Symbol; ///< Unique unit identifier
static constexpr auto quantity_spec = QS;
static constexpr auto point_origin = PO;
};
/** /**
* @brief A prefixed unit * @brief A prefixed unit
* *
@@ -286,14 +322,14 @@ canonical_unit(M, U) -> canonical_unit<M, U>;
#endif #endif
template<Unit T, basic_symbol_text Symbol, detail::QuantityKindSpec auto Q> template<Unit T, basic_symbol_text Symbol, detail::QuantityKindSpec auto Q, auto... Args>
[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol, Q>&); [[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol, Q, Args...>&);
template<Unit T, basic_symbol_text Symbol> template<Unit T, basic_symbol_text Symbol, auto... Args>
[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol>&); [[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol, Args...>&);
template<Unit T, basic_symbol_text Symbol, Unit auto U> template<Unit T, basic_symbol_text Symbol, Unit auto U, auto... Args>
[[nodiscard]] consteval auto get_canonical_unit_impl(T, const named_unit<Symbol, U>&); [[nodiscard]] consteval auto get_canonical_unit_impl(T, const named_unit<Symbol, U, Args...>&);
template<typename T, typename F, int Num, int... Den> template<typename T, typename F, int Num, int... Den>
[[nodiscard]] consteval auto get_canonical_unit_impl(T, const power<F, Num, Den...>&); [[nodiscard]] consteval auto get_canonical_unit_impl(T, const power<F, Num, Den...>&);
@@ -308,20 +344,20 @@ template<Unit T, auto M, typename U>
return canonical_unit{M * base.mag, base.reference_unit}; return canonical_unit{M * base.mag, base.reference_unit};
} }
template<Unit T, basic_symbol_text Symbol, detail::QuantityKindSpec auto Q> template<Unit T, basic_symbol_text Symbol, detail::QuantityKindSpec auto Q, auto... Args>
[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol, Q>&) [[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol, Q, Args...>&)
{ {
return canonical_unit{mag<1>, t}; return canonical_unit{mag<1>, t};
} }
template<Unit T, basic_symbol_text Symbol> template<Unit T, basic_symbol_text Symbol, auto... Args>
[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol>&) [[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol, Args...>&)
{ {
return canonical_unit{mag<1>, t}; return canonical_unit{mag<1>, t};
} }
template<Unit T, basic_symbol_text Symbol, Unit auto U> template<Unit T, basic_symbol_text Symbol, Unit auto U, auto... Args>
[[nodiscard]] consteval auto get_canonical_unit_impl(T, const named_unit<Symbol, U>&) [[nodiscard]] consteval auto get_canonical_unit_impl(T, const named_unit<Symbol, U, Args...>&)
{ {
return get_canonical_unit_impl(U, U); return get_canonical_unit_impl(U, U);
} }

View File

@@ -1,38 +0,0 @@
// 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/quantity_point.h>
#include <mp-units/systems/si/units.h>
namespace mp_units::si {
// clang-format off
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;
inline constexpr struct ice_point : relative_point_origin<absolute_zero + 273.15 * kelvin> {} ice_point;
inline constexpr struct zeroth_degree_Celsius : decltype(ice_point) {} zeroth_degree_Celsius;
// clang-format on
} // namespace mp_units::si

View File

@@ -23,7 +23,6 @@
#pragma once #pragma once
#include <mp-units/systems/si/constants.h> #include <mp-units/systems/si/constants.h>
#include <mp-units/systems/si/point_origins.h>
#include <mp-units/systems/si/prefixes.h> #include <mp-units/systems/si/prefixes.h>
#include <mp-units/systems/si/unit_symbols.h> #include <mp-units/systems/si/unit_symbols.h>
#include <mp-units/systems/si/units.h> #include <mp-units/systems/si/units.h>

View File

@@ -22,6 +22,7 @@
#pragma once #pragma once
#include <mp-units/quantity_point.h>
#include <mp-units/systems/isq/atomic_and_nuclear_physics.h> #include <mp-units/systems/isq/atomic_and_nuclear_physics.h>
#include <mp-units/systems/isq/base_quantities.h> #include <mp-units/systems/isq/base_quantities.h>
#include <mp-units/systems/isq/space_and_time.h> #include <mp-units/systems/isq/space_and_time.h>
@@ -39,7 +40,11 @@ inline constexpr struct metre : named_unit<"m", kind_of<isq::length>> {} metre;
inline constexpr struct gram : named_unit<"g", kind_of<isq::mass>> {} gram; inline constexpr struct gram : named_unit<"g", kind_of<isq::mass>> {} gram;
inline constexpr struct kilogram : decltype(kilo<gram>) {} kilogram; inline constexpr struct kilogram : decltype(kilo<gram>) {} kilogram;
inline constexpr struct ampere : named_unit<"A", kind_of<isq::electric_current>> {} ampere; inline constexpr struct ampere : named_unit<"A", kind_of<isq::electric_current>> {} ampere;
inline constexpr struct kelvin : named_unit<"K", kind_of<isq::thermodynamic_temperature>> {} kelvin;
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;
inline constexpr struct kelvin : named_unit<"K", kind_of<isq::thermodynamic_temperature>, zeroth_kelvin> {} kelvin;
inline constexpr struct mole : named_unit<"mol", kind_of<isq::amount_of_substance>> {} mole; inline constexpr struct mole : named_unit<"mol", kind_of<isq::amount_of_substance>> {} mole;
inline constexpr struct candela : named_unit<"cd", kind_of<isq::luminous_intensity>> {} candela; inline constexpr struct candela : named_unit<"cd", kind_of<isq::luminous_intensity>> {} candela;
@@ -68,7 +73,11 @@ inline constexpr struct siemens : named_unit<"S", one / ohm> {} siemens;
inline constexpr struct weber : named_unit<"Wb", volt * second> {} weber; inline constexpr struct weber : named_unit<"Wb", volt * second> {} weber;
inline constexpr struct tesla : named_unit<"T", weber / square(metre)> {} tesla; inline constexpr struct tesla : named_unit<"T", weber / square(metre)> {} tesla;
inline constexpr struct henry : named_unit<"H", weber / ampere> {} henry; inline constexpr struct henry : named_unit<"H", weber / ampere> {} henry;
inline constexpr struct degree_Celsius : named_unit<basic_symbol_text{"°C", "`C"}, kelvin> {} degree_Celsius;
inline constexpr struct ice_point : relative_point_origin<quantity_point{273.15 * kelvin}> {} ice_point;
inline constexpr struct zeroth_degree_Celsius : decltype(ice_point) {} zeroth_degree_Celsius;
inline constexpr struct degree_Celsius : named_unit<basic_symbol_text{"°C", "`C"}, kelvin, zeroth_degree_Celsius> {} degree_Celsius;
inline constexpr struct lumen : named_unit<"lm", candela * steradian> {} lumen; inline constexpr struct lumen : named_unit<"lm", candela * steradian> {} lumen;
inline constexpr struct lux : named_unit<"lx", lumen / square(metre)> {} lux; inline constexpr struct lux : named_unit<"lx", lumen / square(metre)> {} lux;
inline constexpr struct becquerel : named_unit<"Bq", one / second, kind_of<isq::activity>> {} becquerel; inline constexpr struct becquerel : named_unit<"Bq", one / second, kind_of<isq::activity>> {} becquerel;

View File

@@ -110,9 +110,9 @@ inline constexpr struct troy_pound : named_unit<"lb t", mag<12> * troy_once> {}
inline constexpr struct inch_of_mercury : named_unit<"inHg", mag<ratio(3'386'389, 1'000)> * si::pascal> {} inch_of_mercury; inline constexpr struct inch_of_mercury : 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 degree_Fahrenheit : named_unit<basic_symbol_text{"°F", "`F"}, mag<ratio{5, 9}> * si::degree_Celsius> {} degree_Fahrenheit; inline constexpr struct zeroth_degree_Fahrenheit : relative_point_origin<si::zeroth_degree_Celsius - 32 * (mag<ratio{5, 9}> * si::degree_Celsius)> {} zeroth_degree_Fahrenheit;
inline constexpr struct degree_Fahrenheit : named_unit<basic_symbol_text{"°F", "`F"}, mag<ratio{5, 9}> * si::degree_Celsius, zeroth_degree_Fahrenheit> {} degree_Fahrenheit;
inline constexpr struct zeroth_degree_Fahrenheit : relative_point_origin<si::zeroth_degree_Celsius - 32 * degree_Fahrenheit> {} zeroth_degree_Fahrenheit;
// clang-format on // clang-format on
namespace unit_symbols { namespace unit_symbols {

View File

@@ -25,6 +25,7 @@
#include <mp-units/quantity_point.h> #include <mp-units/quantity_point.h>
#include <mp-units/systems/isq/isq.h> #include <mp-units/systems/isq/isq.h>
#include <mp-units/systems/si/si.h> #include <mp-units/systems/si/si.h>
#include <mp-units/systems/usc/usc.h>
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
@@ -33,9 +34,13 @@ namespace {
using namespace mp_units; using namespace mp_units;
using namespace mp_units::si::unit_symbols; using namespace mp_units::si::unit_symbols;
using namespace mp_units::usc::unit_symbols;
using namespace std::chrono_literals; using namespace std::chrono_literals;
using sys_seconds = std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>; using sys_seconds = std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
inline constexpr struct zeroth_length : absolute_point_origin<zeroth_length, isq::length> {
} zeroth_length;
inline constexpr struct mean_sea_level : absolute_point_origin<mean_sea_level, isq::height> { inline constexpr struct mean_sea_level : absolute_point_origin<mean_sea_level, isq::height> {
} mean_sea_level; } mean_sea_level;
@@ -88,6 +93,7 @@ static_assert(my_ground_level == ground_level);
static_assert(same_ground_level1 == ground_level); static_assert(same_ground_level1 == ground_level);
static_assert(same_ground_level2 == my_ground_level); static_assert(same_ground_level2 == my_ground_level);
static_assert(mean_sea_level != zeroth_length);
static_assert(mean_sea_level != other_absolute_level); static_assert(mean_sea_level != other_absolute_level);
static_assert(my_mean_sea_level != other_absolute_level); static_assert(my_mean_sea_level != other_absolute_level);
static_assert(ground_level != other_ground_level); static_assert(ground_level != other_ground_level);
@@ -106,6 +112,31 @@ static_assert(relative_po<absolute_po<isq::length> + isq::height(42 * m)>.quanti
static_assert(relative_po<absolute_po<kind_of<isq::length>> + isq::height(42 * m)>.quantity_spec == isq::height); static_assert(relative_po<absolute_po<kind_of<isq::length>> + isq::height(42 * m)>.quantity_spec == isq::height);
static_assert(relative_po<absolute_po<isq::height> + 42 * m>.quantity_spec == isq::height); static_assert(relative_po<absolute_po<isq::height> + 42 * m>.quantity_spec == isq::height);
inline constexpr struct my_kelvin : named_unit<"my_K", mag<10> * si::kelvin> {
} my_kelvin;
static_assert(default_point_origin(si::kelvin) == si::absolute_zero);
static_assert(default_point_origin(si::milli<si::kelvin>) == si::absolute_zero);
static_assert(default_point_origin(mag<10> * si::kelvin) == si::absolute_zero);
static_assert(default_point_origin(my_kelvin) == si::absolute_zero);
static_assert(default_point_origin(si::degree_Celsius) == si::ice_point);
static_assert(default_point_origin(mag<10> * si::degree_Celsius) == si::ice_point);
static_assert(default_point_origin(si::metre) == zeroth_point_origin<kind_of<isq::length>>);
static_assert(default_point_origin(si::kelvin / si::second) ==
zeroth_point_origin<kind_of<isq::thermodynamic_temperature / isq::time>>);
static_assert(default_point_origin(si::degree_Celsius / si::second) ==
zeroth_point_origin<kind_of<isq::thermodynamic_temperature / isq::time>>);
static_assert(zeroth_point_origin<isq::length / isq::time> == zeroth_point_origin<isq::speed>);
static_assert(zeroth_point_origin<inverse(isq::period_duration)> == zeroth_point_origin<isq::frequency>);
static_assert(zeroth_point_origin<kind_of<isq::length>> == zeroth_point_origin<isq::height>);
static_assert(zeroth_point_origin<kind_of<inverse(isq::time)>> == zeroth_point_origin<isq::frequency>);
static_assert(zeroth_point_origin<isq::length> != zeroth_point_origin<isq::height>);
static_assert(zeroth_point_origin<isq::width> != zeroth_point_origin<isq::height>);
static_assert(zeroth_point_origin<inverse(isq::time)> != zeroth_point_origin<isq::frequency>);
///////////////////// /////////////////////
// class invariants // class invariants
@@ -126,6 +157,8 @@ concept invalid_types = requires {
requires !requires { typename QP<isq::width[m], ground_level, int>; }; requires !requires { typename QP<isq::width[m], ground_level, int>; };
requires !requires { typename QP<isq::length[m], mean_sea_level, int>; }; requires !requires { typename QP<isq::length[m], mean_sea_level, int>; };
requires !requires { typename QP<isq::length[m], ground_level, int>; }; requires !requires { typename QP<isq::length[m], ground_level, int>; };
requires !requires { typename QP<isq::length[m], zeroth_point_origin<isq::height>, int>; };
requires !requires { typename QP<isq::width[m], zeroth_point_origin<isq::height>, int>; };
// quantity used as Rep // quantity used as Rep
requires !requires { typename QP<si::metre, mean_sea_level, quantity<si::metre, int>>; }; requires !requires { typename QP<si::metre, mean_sea_level, quantity<si::metre, int>>; };
// quantity point used as Rep // quantity point used as Rep
@@ -151,6 +184,9 @@ concept valid_types = requires {
typename QP<si::metre, ground_level, int>; typename QP<si::metre, ground_level, int>;
typename QP<isq::height[m], ground_level, int>; typename QP<isq::height[m], ground_level, int>;
typename QP<special_height[m], ground_level, int>; typename QP<special_height[m], ground_level, int>;
typename QP<isq::height[m], zeroth_point_origin<isq::length>, int>;
typename QP<isq::height[m], zeroth_point_origin<kind_of<isq::length>>, int>;
typename QP<si::metre, zeroth_point_origin<isq::height>, int>;
}; };
static_assert(valid_types<quantity_point>); static_assert(valid_types<quantity_point>);
@@ -185,6 +221,20 @@ static_assert(std::three_way_comparable<quantity_point<si::metre, mean_sea_level
// member values // member values
////////////////// //////////////////
static_assert(quantity_point<si::metre>::reference == si::metre);
static_assert(quantity_point<si::metre>::quantity_spec == kind_of<isq::length>);
static_assert(quantity_point<si::metre>::dimension == isq::dim_length);
static_assert(quantity_point<si::metre>::unit == si::metre);
static_assert(is_of_type<quantity_point<si::metre>::point_origin, zeroth_point_origin_<kind_of<isq::length>>>);
static_assert(is_of_type<quantity_point<si::metre>::absolute_point_origin, zeroth_point_origin_<kind_of<isq::length>>>);
static_assert(quantity_point<isq::height[m]>::reference == isq::height[m]);
static_assert(quantity_point<isq::height[m]>::quantity_spec == isq::height);
static_assert(quantity_point<isq::height[m]>::dimension == isq::dim_length);
static_assert(quantity_point<isq::height[m]>::unit == si::metre);
static_assert(is_of_type<quantity_point<isq::height[m]>::point_origin, zeroth_point_origin_<isq::height>>);
static_assert(is_of_type<quantity_point<isq::height[m]>::absolute_point_origin, zeroth_point_origin_<isq::height>>);
static_assert(quantity_point<si::metre, mean_sea_level>::reference == si::metre); static_assert(quantity_point<si::metre, mean_sea_level>::reference == si::metre);
static_assert(quantity_point<si::metre, mean_sea_level>::quantity_spec == kind_of<isq::length>); static_assert(quantity_point<si::metre, mean_sea_level>::quantity_spec == kind_of<isq::length>);
static_assert(quantity_point<si::metre, mean_sea_level>::dimension == isq::dim_length); static_assert(quantity_point<si::metre, mean_sea_level>::dimension == isq::dim_length);
@@ -253,7 +303,7 @@ static_assert(quantity_point<si::degree_Celsius, si::ice_point>::dimension == is
static_assert(quantity_point<si::degree_Celsius, si::ice_point>::unit == si::degree_Celsius); static_assert(quantity_point<si::degree_Celsius, si::ice_point>::unit == si::degree_Celsius);
static_assert(is_of_type<quantity_point<si::degree_Celsius, si::ice_point>::point_origin, struct si::ice_point>); static_assert(is_of_type<quantity_point<si::degree_Celsius, si::ice_point>::point_origin, struct si::ice_point>);
static_assert( static_assert(
is_of_type<quantity_point<si::degree_Celsius, si::ice_point>::absolute_point_origin, struct si::absolute_zero>); is_of_type<quantity_point<si::degree_Celsius, si::ice_point>::absolute_point_origin, struct si::zeroth_kelvin>);
static_assert(quantity_point<isq::Celsius_temperature[si::degree_Celsius], si::ice_point>::reference == static_assert(quantity_point<isq::Celsius_temperature[si::degree_Celsius], si::ice_point>::reference ==
isq::Celsius_temperature[si::degree_Celsius]); isq::Celsius_temperature[si::degree_Celsius]);
@@ -266,7 +316,7 @@ static_assert(is_of_type<quantity_point<isq::Celsius_temperature[si::degree_Cels
struct si::ice_point>); struct si::ice_point>);
static_assert( static_assert(
is_of_type<quantity_point<isq::Celsius_temperature[si::degree_Celsius], si::ice_point>::absolute_point_origin, is_of_type<quantity_point<isq::Celsius_temperature[si::degree_Celsius], si::ice_point>::absolute_point_origin,
struct si::absolute_zero>); struct si::zeroth_kelvin>);
////////////////// //////////////////
@@ -332,6 +382,80 @@ static_assert(!std::convertible_to<int, quantity_point<dimensionless[one], zero,
// construction from a quantity // construction from a quantity
///////////////////////////////// /////////////////////////////////
// -----------------------------
// implicit zeroth point origins
// -----------------------------
static_assert(std::constructible_from<quantity_point<si::metre>, quantity<si::metre>>);
static_assert(!std::convertible_to<quantity<si::metre>, quantity_point<si::metre>>);
static_assert(std::constructible_from<quantity_point<isq::height[m]>, quantity<isq::height[m]>>);
static_assert(!std::convertible_to<quantity<isq::height[m]>, quantity_point<isq::height[m]>>);
static_assert(std::constructible_from<quantity_point<isq::height[m]>, quantity<si::metre>>);
static_assert(!std::convertible_to<quantity<si::metre>, quantity_point<isq::height[m]>>);
static_assert(std::constructible_from<quantity_point<si::metre>, quantity<isq::height[m]>>);
static_assert(!std::convertible_to<quantity<isq::height[m]>, quantity_point<si::metre>>);
static_assert(std::constructible_from<quantity_point<m / s>, quantity<isq::speed[m / s]>>);
static_assert(!std::convertible_to<quantity<isq::speed[m / s]>, quantity_point<m / s>>);
static_assert(std::constructible_from<quantity_point<isq::speed[m / s]>, quantity<m / s>>);
static_assert(!std::convertible_to<quantity<m / s>, quantity_point<isq::speed[m / s]>>);
static_assert(std::constructible_from<quantity_point<isq::length[m] / isq::time[s]>, quantity<isq::speed[m / s]>>);
static_assert(!std::convertible_to<quantity<isq::speed[m / s]>, quantity_point<isq::length[m] / isq::time[s]>>);
static_assert(std::constructible_from<quantity_point<isq::speed[m / s]>, quantity<isq::length[m] / isq::time[s]>>);
static_assert(!std::convertible_to<quantity<isq::length[m] / isq::time[s]>, quantity_point<isq::speed[m / s]>>);
static_assert(std::constructible_from<quantity_point<isq::speed[m / s]>, quantity<m / s>>);
static_assert(!std::convertible_to<quantity<m / s>, quantity_point<isq::speed[m / s]>>);
static_assert(std::constructible_from<quantity_point<dimensionless[one]>, quantity<dimensionless[one]>>);
static_assert(!std::convertible_to<quantity<dimensionless[one]>, quantity_point<dimensionless[one]>>);
// different dimensions
static_assert(!std::constructible_from<quantity_point<isq::height[m]>, quantity<si::second>>);
static_assert(!std::convertible_to<quantity<si::second>, quantity_point<isq::height[m]>>);
// convertible but different quantity_specs
static_assert(std::constructible_from<quantity_point<isq::length[m]>, quantity<isq::height[m]>>);
static_assert(!std::convertible_to<quantity<isq::height[m]>, quantity_point<isq::length[m]>>);
static_assert(std::constructible_from<quantity_point<isq::height[m]>, quantity<special_height[m]>>);
static_assert(!std::convertible_to<quantity<special_height[m]>, quantity_point<isq::height[m]>>);
// quantity_specs with common_quantity_spec
static_assert(!std::constructible_from<quantity_point<isq::width[m]>, quantity<isq::height[m]>>);
static_assert(!std::convertible_to<quantity<isq::height[m]>, quantity_point<isq::width[m]>>);
static_assert(!std::constructible_from<quantity_point<isq::height[m]>, quantity<isq::width[m]>>);
static_assert(!std::convertible_to<quantity<isq::width[m]>, quantity_point<isq::height[m]>>);
// non-convertible quantity_specs
static_assert(!std::constructible_from<quantity_point<isq::height[m]>, quantity<isq::length[m]>>);
static_assert(!std::convertible_to<quantity<isq::length[m]>, quantity_point<isq::height[m]>>);
static_assert(!std::constructible_from<quantity_point<special_height[m]>, quantity<isq::height[m]>>);
static_assert(!std::convertible_to<quantity<special_height[m]>, quantity_point<isq::height[m]>>);
// quantity-like
static_assert(!std::constructible_from<quantity_point<si::second>, std::chrono::seconds>);
static_assert(!std::convertible_to<std::chrono::seconds, quantity_point<si::second>>);
static_assert(!std::constructible_from<quantity_point<isq::time[s]>, std::chrono::seconds>);
static_assert(!std::convertible_to<std::chrono::seconds, quantity_point<isq::time[s]>>);
static_assert(!std::constructible_from<quantity_point<isq::period_duration[s]>, std::chrono::seconds>);
static_assert(!std::convertible_to<std::chrono::seconds, quantity_point<isq::period_duration[s]>>);
// ----------------------
// explicit point origins
// ----------------------
static_assert(!std::constructible_from<quantity_point<si::metre, mean_sea_level>, quantity<si::metre>>); static_assert(!std::constructible_from<quantity_point<si::metre, mean_sea_level>, quantity<si::metre>>);
static_assert(!std::convertible_to<quantity<si::metre>, quantity_point<si::metre, mean_sea_level>>); static_assert(!std::convertible_to<quantity<si::metre>, quantity_point<si::metre, mean_sea_level>>);
@@ -350,6 +474,13 @@ static_assert(!std::convertible_to<quantity<special_height[m]>, quantity_point<i
static_assert(!std::constructible_from<quantity_point<dimensionless[one], zero>, quantity<dimensionless[one]>>); static_assert(!std::constructible_from<quantity_point<dimensionless[one], zero>, quantity<dimensionless[one]>>);
static_assert(!std::convertible_to<quantity<dimensionless[one]>, quantity_point<dimensionless[one], zero>>); static_assert(!std::convertible_to<quantity<dimensionless[one]>, quantity_point<dimensionless[one], zero>>);
// quantity_specs with common_quantity_spec
static_assert(!std::constructible_from<quantity_point<isq::width[m], zeroth_length>, quantity<isq::height[m]>>);
static_assert(!std::convertible_to<quantity<isq::height[m]>, quantity_point<isq::width[m], zeroth_length>>);
static_assert(!std::constructible_from<quantity_point<isq::height[m], zeroth_length>, quantity<isq::width[m]>>);
static_assert(!std::convertible_to<quantity<isq::width[m]>, quantity_point<isq::height[m], zeroth_length>>);
// different dimensions // different dimensions
static_assert(!std::constructible_from<quantity_point<isq::height[m], mean_sea_level>, quantity<si::second>>); static_assert(!std::constructible_from<quantity_point<isq::height[m], mean_sea_level>, quantity<si::second>>);
static_assert(!std::convertible_to<quantity<si::second>, quantity_point<isq::height[m], mean_sea_level>>); static_assert(!std::convertible_to<quantity<si::second>, quantity_point<isq::height[m], mean_sea_level>>);
@@ -388,7 +519,60 @@ static_assert(
// construction from a quantity point // construction from a quantity point
/////////////////////////////////////// ///////////////////////////////////////
// same origins // implicit origin
static_assert(std::constructible_from<quantity_point<si::metre>, quantity_point<si::metre>>);
static_assert(std::convertible_to<quantity_point<si::metre>, quantity_point<si::metre>>);
static_assert(std::constructible_from<quantity_point<isq::height[m]>, quantity_point<isq::height[m]>>);
static_assert(std::convertible_to<quantity_point<isq::height[m]>, quantity_point<isq::height[m]>>);
static_assert(std::constructible_from<quantity_point<si::metre>, quantity_point<isq::height[m]>>);
static_assert(std::convertible_to<quantity_point<isq::height[m]>, quantity_point<si::metre>>);
static_assert(std::constructible_from<quantity_point<isq::height[m]>, quantity_point<si::metre>>);
static_assert(std::convertible_to<quantity_point<si::metre>, quantity_point<isq::height[m]>>);
static_assert(
std::constructible_from<quantity_point<isq::length[m] / isq::time[s]>, quantity_point<isq::speed[m / s]>>);
static_assert(std::convertible_to<quantity_point<isq::speed[m / s]>, quantity_point<isq::length[m] / isq::time[s]>>);
static_assert(std::constructible_from<quantity_point<m / s>, quantity_point<isq::speed[m / s]>>);
static_assert(std::convertible_to<quantity_point<isq::speed[m / s]>, quantity_point<m / s>>);
static_assert(std::constructible_from<quantity_point<m / s>, quantity_point<isq::length[m] / isq::time[s]>>);
static_assert(std::convertible_to<quantity_point<isq::length[m] / isq::time[s]>, quantity_point<m / s>>);
// convertible but different quantity_specs
static_assert(!std::constructible_from<quantity_point<isq::length[m]>, quantity_point<isq::height[m]>>);
static_assert(!std::convertible_to<quantity_point<isq::height[m]>, quantity_point<isq::length[m]>>);
static_assert(!std::constructible_from<quantity_point<isq::height[m]>, quantity_point<special_height[m]>>);
static_assert(!std::convertible_to<quantity_point<special_height[m]>, quantity_point<isq::height[m]>>);
// quantity_specs with common_quantity_spec
static_assert(!std::constructible_from<quantity_point<isq::width[m]>, quantity_point<isq::height[m]>>);
static_assert(!std::convertible_to<quantity_point<isq::height[m]>, quantity_point<isq::width[m]>>);
static_assert(!std::constructible_from<quantity_point<isq::height[m]>, quantity_point<isq::width[m]>>);
static_assert(!std::convertible_to<quantity_point<isq::width[m]>, quantity_point<isq::height[m]>>);
// non-convertible quantity_specs
static_assert(!std::constructible_from<quantity_point<isq::height[m]>, quantity_point<isq::length[m]>>);
static_assert(!std::convertible_to<quantity_point<isq::length[m]>, quantity_point<isq::height[m]>>);
static_assert(!std::constructible_from<quantity_point<special_height[m]>, quantity_point<isq::height[m]>>);
static_assert(!std::convertible_to<quantity_point<isq::height[m]>, quantity_point<special_height[m]>>);
// mixed origins
static_assert(!std::constructible_from<quantity_point<si::metre, mean_sea_level>, quantity_point<si::metre>>);
static_assert(!std::convertible_to<quantity_point<si::metre>, quantity_point<si::metre, mean_sea_level>>);
static_assert(!std::constructible_from<quantity_point<isq::height[m], mean_sea_level>, quantity_point<isq::height[m]>>);
static_assert(!std::convertible_to<quantity_point<isq::height[m]>, quantity_point<isq::height[m], mean_sea_level>>);
static_assert(!std::constructible_from<quantity_point<si::metre>, quantity_point<si::metre, mean_sea_level>>);
static_assert(!std::convertible_to<quantity_point<si::metre, mean_sea_level>, quantity_point<si::metre>>);
static_assert(!std::constructible_from<quantity_point<isq::height[m]>, quantity_point<isq::height[m], mean_sea_level>>);
static_assert(!std::convertible_to<quantity_point<isq::height[m], mean_sea_level>, quantity_point<isq::height[m]>>);
// same explicit origins
static_assert( static_assert(
std::constructible_from<quantity_point<si::metre, mean_sea_level>, quantity_point<si::metre, mean_sea_level>>); std::constructible_from<quantity_point<si::metre, mean_sea_level>, quantity_point<si::metre, mean_sea_level>>);
static_assert( static_assert(
@@ -485,6 +669,22 @@ static_assert(
static_assert( static_assert(
std::convertible_to<quantity_point<dimensionless[one], zero>, quantity_point<dimensionless[percent], zero>>); std::convertible_to<quantity_point<dimensionless[one], zero>, quantity_point<dimensionless[percent], zero>>);
static_assert(std::constructible_from<quantity_point<isq::length[m], zeroth_length>,
quantity_point<isq::height[m], zeroth_length>>);
static_assert(
std::convertible_to<quantity_point<isq::height[m], zeroth_length>, quantity_point<isq::length[m], zeroth_length>>);
// quantity_specs with common_quantity_spec
static_assert(!std::constructible_from<quantity_point<isq::width[m], zeroth_length>,
quantity_point<isq::height[m], zeroth_length>>);
static_assert(
!std::convertible_to<quantity_point<isq::height[m], zeroth_length>, quantity_point<isq::width[m], zeroth_length>>);
static_assert(!std::constructible_from<quantity_point<isq::height[m], zeroth_length>,
quantity_point<isq::width[m], zeroth_length>>);
static_assert(
!std::convertible_to<quantity_point<isq::width[m], zeroth_length>, quantity_point<isq::height[m], zeroth_length>>);
// different dimensions // different dimensions
static_assert( static_assert(
!std::constructible_from<quantity_point<isq::height[m], mean_sea_level>, quantity_point<si::kelvin, si::ice_point>>); !std::constructible_from<quantity_point<isq::height[m], mean_sea_level>, quantity_point<si::kelvin, si::ice_point>>);
@@ -589,6 +789,15 @@ static_assert(
// obtaining a relative quantity // obtaining a relative quantity
////////////////////////////////// //////////////////////////////////
static_assert(quantity_point{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 * deg_C}.quantity_from_zero() == 20 * deg_C);
static_assert(quantity_point{20. * deg_C}.in(deg_F).quantity_from_zero() == 68 * deg_F);
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((tower_peak + 42 * m).quantity_from_zero() == 126 * m);
static_assert((mean_sea_level + 42 * m).quantity_from(mean_sea_level) == 42 * m); static_assert((mean_sea_level + 42 * m).quantity_from(mean_sea_level) == 42 * m);
static_assert((mean_sea_level + isq::height(42 * m)).quantity_from(mean_sea_level) == 42 * m); static_assert((mean_sea_level + isq::height(42 * m)).quantity_from(mean_sea_level) == 42 * m);
@@ -653,17 +862,43 @@ static_assert(invalid_unit_conversion<quantity_point>);
// CTAD // CTAD
///////// /////////
static_assert(std::is_same_v<decltype(quantity_point{123 * m})::rep, int>);
static_assert(std::is_same_v<std::remove_const_t<decltype(quantity_point{123 * m}.point_origin)>,
zeroth_point_origin_<kind_of<isq::length>>>);
static_assert(std::is_same_v<std::remove_const_t<decltype(quantity_point{123 * m}.absolute_point_origin)>,
zeroth_point_origin_<kind_of<isq::length>>>);
static_assert(quantity_point{123 * m}.unit == si::metre);
static_assert(quantity_point{123 * m}.quantity_spec == kind_of<isq::length>);
static_assert(std::is_same_v<decltype(quantity_point{isq::height(123 * m)})::rep, int>);
static_assert(std::is_same_v<std::remove_const_t<decltype(quantity_point{isq::height(123 * m)}.point_origin)>,
zeroth_point_origin_<isq::height>>);
static_assert(std::is_same_v<std::remove_const_t<decltype(quantity_point{isq::height(123 * m)}.absolute_point_origin)>,
zeroth_point_origin_<isq::height>>);
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(std::is_same_v<decltype(quantity_point{20 * deg_C})::rep, int>);
static_assert(std::is_same_v<std::remove_const_t<decltype(quantity_point{20 * deg_C}.point_origin)>,
struct si::zeroth_degree_Celsius>);
static_assert(std::is_same_v<std::remove_const_t<decltype(quantity_point{20 * deg_C}.absolute_point_origin)>,
struct si::zeroth_kelvin>);
static_assert(quantity_point{20 * deg_C}.unit == si::degree_Celsius);
static_assert(quantity_point{20 * deg_C}.quantity_spec == kind_of<isq::thermodynamic_temperature>);
using namespace std::chrono_literals; using namespace std::chrono_literals;
static_assert(std::is_same_v<decltype(quantity_point{sys_seconds{123s}})::rep, std::chrono::seconds::rep>); static_assert(std::is_same_v<decltype(quantity_point{sys_seconds{123s}})::rep, std::chrono::seconds::rep>);
static_assert(std::is_same_v<std::remove_const_t<decltype(quantity_point{sys_seconds{123s}}.point_origin)>, static_assert(std::is_same_v<std::remove_const_t<decltype(quantity_point{sys_seconds{123s}}.point_origin)>,
chrono_point_origin_<std::chrono::system_clock>>); chrono_point_origin_<std::chrono::system_clock>>);
static_assert(std::is_same_v<std::remove_const_t<decltype(quantity_point{sys_seconds{123s}}.absolute_point_origin)>,
chrono_point_origin_<std::chrono::system_clock>>);
static_assert(quantity_point{sys_seconds{24h}}.unit == si::second); static_assert(quantity_point{sys_seconds{24h}}.unit == si::second);
static_assert(quantity_point{sys_seconds{24h}}.quantity_spec == kind_of<isq::time>); static_assert(quantity_point{sys_seconds{24h}}.quantity_spec == kind_of<isq::time>);
// //////////// ////////////
// // getters // getters
// //////////// ////////////
constexpr quantity_point mean_sea_level_qp = mean_sea_level + 1 * m; constexpr quantity_point mean_sea_level_qp = mean_sea_level + 1 * m;
constexpr quantity_point my_mean_sea_level_qp = my_mean_sea_level + 1 * m; constexpr quantity_point my_mean_sea_level_qp = my_mean_sea_level + 1 * m;
@@ -744,14 +979,14 @@ static_assert([](auto v) {
//////////////////////// ////////////////////////
// same type // same type
static_assert((mean_sea_level + 1 * m += 1 * m).quantity_from(mean_sea_level).numerical_value_in(m) == 2); static_assert((mean_sea_level + 1 * m += 1 * m).quantity_from_zero().numerical_value_in(m) == 2);
static_assert((mean_sea_level + 2 * m -= 1 * m).quantity_from(mean_sea_level).numerical_value_in(m) == 1); static_assert((mean_sea_level + 2 * m -= 1 * m).quantity_from_zero().numerical_value_in(m) == 1);
// different types // different types
static_assert((mean_sea_level + 2.5 * m += 3 * m).quantity_from(mean_sea_level).numerical_value_in(m) == 5.5); static_assert((mean_sea_level + 2.5 * m += 3 * m).quantity_from_zero().numerical_value_in(m) == 5.5);
static_assert((mean_sea_level + 123 * m += 1 * km).quantity_from(mean_sea_level).numerical_value_in(m) == 1123); static_assert((mean_sea_level + 123 * m += 1 * km).quantity_from_zero().numerical_value_in(m) == 1123);
static_assert((mean_sea_level + 5.5 * m -= 3 * m).quantity_from(mean_sea_level).numerical_value_in(m) == 2.5); static_assert((mean_sea_level + 5.5 * m -= 3 * m).quantity_from_zero().numerical_value_in(m) == 2.5);
static_assert((mean_sea_level + 1123 * m -= 1 * km).quantity_from(mean_sea_level).numerical_value_in(m) == 123); static_assert((mean_sea_level + 1123 * m -= 1 * km).quantity_from_zero().numerical_value_in(m) == 123);
template<template<auto, auto, typename> typename QP> template<template<auto, auto, typename> typename QP>
@@ -792,12 +1027,32 @@ concept invalid_binary_operations = requires {
requires !requires { isq::length(1 * m) + QP<si::metre, mean_sea_level, int>(1 * m); }; requires !requires { isq::length(1 * m) + QP<si::metre, mean_sea_level, int>(1 * m); };
requires !requires { QP<isq::height[m], mean_sea_level, int>(1 * m) + isq::length(1 * m); }; requires !requires { QP<isq::height[m], mean_sea_level, int>(1 * m) + isq::length(1 * m); };
requires !requires { isq::length(1 * m) + QP<isq::height[m], mean_sea_level, int>(1 * m); }; requires !requires { isq::length(1 * m) + QP<isq::height[m], mean_sea_level, int>(1 * m); };
requires !requires { QP<si::metre, zeroth_point_origin<isq::height>, int>(1 * m) + isq::length(1 * m); };
requires !requires { isq::length(1 * m) + QP<si::metre, zeroth_point_origin<isq::height>, int>(1 * m); };
requires !requires { QP<isq::height[m], zeroth_point_origin<isq::height>, int>(1 * m) + isq::length(1 * m); };
requires !requires { isq::length(1 * m) + QP<isq::height[m], zeroth_point_origin<isq::height>, int>(1 * m); };
requires !requires { Origin + isq::length(1 * m); }; requires !requires { Origin + isq::length(1 * m); };
requires !requires { isq::length(1 * m) + Origin; }; requires !requires { isq::length(1 * m) + Origin; };
// can't subtract more generic quantity (violates point_origin quantity_spec) // can't subtract more generic quantity (violates point_origin quantity_spec)
requires !requires { QP<si::metre, mean_sea_level, int>(1 * m) - isq::length(1 * m); }; requires !requires { QP<si::metre, mean_sea_level, int>(1 * m) - isq::length(1 * m); };
requires !requires { QP<isq::height[m], mean_sea_level, int>(1 * m) - isq::length(1 * m); }; requires !requires { QP<isq::height[m], mean_sea_level, int>(1 * m) - isq::length(1 * m); };
requires !requires { QP<si::metre, zeroth_point_origin<isq::height>, int>(1 * m) - isq::length(1 * m); };
requires !requires { QP<isq::height[m], zeroth_point_origin<isq::height>, int>(1 * m) - isq::length(1 * m); };
requires !requires {
QP<isq::height[m] / isq::time[s], zeroth_point_origin<isq::height / isq::time>, int>(10 * isq::height[m] /
(2 * isq::time[s])) +
5 * isq::speed[m / s];
};
requires !requires {
5 * isq::speed[m / s] + QP<isq::height[m] / isq::time[s], zeroth_point_origin<isq::height / isq::time>, int>(
10 * isq::height[m] / (2 * isq::time[s]));
};
requires !requires {
QP<isq::height[m] / isq::time[s], zeroth_point_origin<isq::height / isq::time>, int>(10 * isq::height[m] /
(2 * isq::time[s])) -
5 * isq::speed[m / s];
};
requires !requires { Origin - isq::length(1 * m); }; requires !requires { Origin - isq::length(1 * m); };
// quantity point can't be subtracted from a quantity // quantity point can't be subtracted from a quantity
@@ -819,6 +1074,28 @@ concept invalid_binary_operations = requires {
}; };
requires !requires { mean_sea_level - QP<isq::height[m], other_absolute_level, int>(1 * m); }; requires !requires { mean_sea_level - QP<isq::height[m], other_absolute_level, int>(1 * m); };
requires !requires { QP<isq::height[m], mean_sea_level, int>(1 * m) - other_absolute_level; }; requires !requires { QP<isq::height[m], mean_sea_level, int>(1 * m) - other_absolute_level; };
requires !requires { QP<isq::height[m], mean_sea_level, int>(1 * m) - quantity_point{1 * m}; };
requires !requires { quantity_point{1 * m} - QP<isq::height[m], mean_sea_level, int>(1 * m); };
requires !requires {
QP<isq::width[m], zeroth_point_origin<isq::width>, int>(1 * m) - quantity_point{isq::height(1 * m)};
};
requires !requires {
quantity_point{isq::height(1 * m)} - QP<isq::width[m], zeroth_point_origin<isq::width>, int>(1 * m);
};
requires !requires {
QP<isq::width[m], zeroth_point_origin<isq::width>, int>(1 * m) - quantity_point{isq::length(1 * m)};
};
requires !requires {
quantity_point{isq::length(1 * m)} - QP<isq::width[m], zeroth_point_origin<isq::width>, int>(1 * m);
};
requires !requires {
quantity_point{10 * isq::height[m] / (2 * isq::time[s])} -
QP<isq::speed[m / s], zeroth_point_origin<isq::speed>, int>(5 * isq::speed[m / s]);
};
requires !requires {
QP<isq::speed[m / s], zeroth_point_origin<isq::speed>, int>(5 * isq::speed[m / s]) -
quantity_point{10 * isq::height[m] / (2 * isq::time[s])};
};
// cant'subtract two unrelated points // cant'subtract two unrelated points
requires !requires { Origin - OtherOrigin; }; requires !requires { Origin - OtherOrigin; };
@@ -1244,6 +1521,50 @@ static_assert(
is_of_type<(zero_m_per_s + 10 * isq::height[m] / (2 * isq::time[s])) + (10 * isq::height[m] / (2 * isq::time[s])), is_of_type<(zero_m_per_s + 10 * isq::height[m] / (2 * isq::time[s])) + (10 * isq::height[m] / (2 * isq::time[s])),
quantity_point<(isq::height / isq::time)[m / s], zero_m_per_s, int>>); quantity_point<(isq::height / isq::time)[m / s], zero_m_per_s, int>>);
static_assert((quantity_point{5 * isq::speed[m / s]} + 10 * isq::length[m] / (2 * isq::time[s])).quantity_from_zero() ==
10 * isq::speed[m / s]);
static_assert((10 * isq::length[m] / (2 * isq::time[s]) + quantity_point{5 * isq::speed[m / s]}).quantity_from_zero() ==
10 * isq::speed[m / s]);
static_assert((quantity_point{5 * isq::speed[m / s]} - 10 * isq::length[m] / (2 * isq::time[s])).quantity_from_zero() ==
0 * isq::speed[m / s]);
static_assert((quantity_point{10 * isq::length[m] / (2 * isq::time[s])} + 5 * isq::speed[m / s]).quantity_from_zero() ==
10 * isq::speed[m / s]);
static_assert((5 * isq::speed[m / s] + quantity_point{10 * isq::length[m] / (2 * isq::time[s])}).quantity_from_zero() ==
10 * isq::speed[m / s]);
static_assert((quantity_point{10 * isq::length[m] / (2 * isq::time[s])} - 5 * isq::speed[m / s]).quantity_from_zero() ==
0 * isq::speed[m / s]);
static_assert((quantity_point{5 * isq::speed[m / s]} + 10 * isq::height[m] / (2 * isq::time[s])).quantity_from_zero() ==
10 * isq::speed[m / s]);
static_assert((10 * isq::height[m] / (2 * isq::time[s]) + quantity_point{5 * isq::speed[m / s]}).quantity_from_zero() ==
10 * isq::speed[m / s]);
static_assert((quantity_point{5 * isq::speed[m / s]} - 10 * isq::height[m] / (2 * isq::time[s])).quantity_from_zero() ==
0 * isq::speed[m / s]);
static_assert(is_of_type<quantity_point{10 * isq::length[m] / (2 * isq::time[s])} + 5 * isq::speed[m / s],
quantity_point<isq::speed[m / s], zeroth_point_origin<isq::speed>, int>>);
static_assert(is_of_type<10 * isq::height[m] / (2 * isq::time[s]) + quantity_point{5 * isq::speed[m / s]},
quantity_point<isq::speed[m / s], zeroth_point_origin<isq::speed>, int>>);
static_assert(is_of_type<quantity_point{5 * isq::speed[m / s]} + 10 * isq::height[m] / (2 * isq::time[s]),
quantity_point<isq::speed[m / s], zeroth_point_origin<isq::speed>, int>>);
static_assert(is_of_type<5 * isq::speed[m / s] + quantity_point{10 * isq::length[m] / (2 * isq::time[s])},
quantity_point<isq::speed[m / s], zeroth_point_origin<isq::speed>, int>>);
static_assert(is_of_type<quantity_point{10 * isq::length[m] / (2 * isq::time[s])} - 5 * isq::speed[m / s],
quantity_point<isq::speed[m / s], zeroth_point_origin<isq::speed>, int>>);
static_assert(is_of_type<quantity_point{5 * isq::speed[m / s]} - 10 * isq::height[m] / (2 * isq::time[s]),
quantity_point<isq::speed[m / s], zeroth_point_origin<isq::speed>, int>>);
static_assert(
is_of_type<quantity_point{10 * isq::length[m] / (2 * isq::time[s])} - quantity_point{5 * isq::speed[m / s]},
quantity<isq::speed[m / s], int>>);
static_assert(
is_of_type<quantity_point{5 * isq::speed[m / s]} - quantity_point{10 * isq::length[m] / (2 * isq::time[s])},
quantity<isq::speed[m / s], int>>);
static_assert(
is_of_type<quantity_point{10 * isq::height[m] / (2 * isq::time[s])} + (10 * isq::height[m] / (2 * isq::time[s])),
quantity_point<(isq::height / isq::time)[m / s], zeroth_point_origin<isq::height / isq::time>, int>>);
inline constexpr struct zero_Hz : absolute_point_origin<zero_Hz, kind_of<isq::frequency>> { inline constexpr struct zero_Hz : absolute_point_origin<zero_Hz, kind_of<isq::frequency>> {
} zero_Hz; } zero_Hz;
@@ -1281,6 +1602,40 @@ static_assert(is_of_type<(zero_Hz + 10 / (2 * isq::period_duration[s])) - (zero_
static_assert(is_of_type<(zero_Hz + 5 * isq::frequency[Hz]) - (zero_Hz + 10 / (2 * isq::period_duration[s])), static_assert(is_of_type<(zero_Hz + 5 * isq::frequency[Hz]) - (zero_Hz + 10 / (2 * isq::period_duration[s])),
quantity<isq::frequency[Hz], int>>); quantity<isq::frequency[Hz], int>>);
static_assert((quantity_point{10 / (2 * isq::period_duration[s])} + 5 * isq::frequency[Hz]).quantity_from_zero() ==
10 * isq::frequency[Hz]);
static_assert((10 / (2 * isq::period_duration[s]) + quantity_point{zero_Hz + 5 * isq::frequency[Hz]})
.quantity_from_zero() == 10 * isq::frequency[Hz]);
static_assert((quantity_point{5 * isq::frequency[Hz]} + 10 / (2 * isq::period_duration[s])).quantity_from_zero() ==
10 * isq::frequency[Hz]);
static_assert((5 * isq::frequency[Hz] + quantity_point{10 / (2 * isq::period_duration[s])}).quantity_from_zero() ==
10 * isq::frequency[Hz]);
static_assert((quantity_point{10 / (2 * isq::period_duration[s])} - 5 * isq::frequency[Hz]).quantity_from_zero() ==
0 * isq::frequency[Hz]);
static_assert((quantity_point{5 * isq::frequency[Hz]} - 10 / (2 * isq::period_duration[s])).quantity_from_zero() ==
0 * isq::frequency[Hz]);
static_assert(quantity_point{10 / (2 * isq::period_duration[s])} - quantity_point{5 * isq::frequency[Hz]} ==
0 * isq::frequency[Hz]);
static_assert(quantity_point{5 * isq::frequency[Hz]} - quantity_point{10 / (2 * isq::period_duration[s])} ==
0 * isq::frequency[Hz]);
static_assert(is_of_type<quantity_point{10 / (2 * isq::period_duration[s])} + 5 * isq::frequency[Hz],
quantity_point<isq::frequency[Hz], zeroth_point_origin<isq::frequency>, int>>);
static_assert(is_of_type<10 / (2 * isq::period_duration[s]) + quantity_point{5 * isq::frequency[Hz]},
quantity_point<isq::frequency[Hz], zeroth_point_origin<isq::frequency>, int>>);
static_assert(is_of_type<quantity_point{5 * isq::frequency[Hz]} + 10 / (2 * isq::period_duration[s]),
quantity_point<isq::frequency[Hz], zeroth_point_origin<isq::frequency>, int>>);
static_assert(is_of_type<5 * isq::frequency[Hz] + quantity_point{10 / (2 * isq::period_duration[s])},
quantity_point<isq::frequency[Hz], zeroth_point_origin<isq::frequency>, int>>);
static_assert(is_of_type<quantity_point{10 / (2 * isq::period_duration[s])} - 5 * isq::frequency[Hz],
quantity_point<isq::frequency[Hz], zeroth_point_origin<isq::frequency>, int>>);
static_assert(is_of_type<quantity_point{5 * isq::frequency[Hz]} - 10 / (2 * isq::period_duration[s]),
quantity_point<isq::frequency[Hz], zeroth_point_origin<isq::frequency>, int>>);
static_assert(is_of_type<quantity_point{10 / (2 * isq::period_duration[s])} - quantity_point{5 * isq::frequency[Hz]},
quantity<isq::frequency[Hz], int>>);
static_assert(is_of_type<quantity_point{5 * isq::frequency[Hz]} - quantity_point{10 / (2 * isq::period_duration[s])},
quantity<isq::frequency[Hz], int>>);
// Different named dimensions // Different named dimensions
template<typename... Ts> template<typename... Ts>
consteval bool invalid_addition(Ts... ts) consteval bool invalid_addition(Ts... ts)
@@ -1307,4 +1662,16 @@ static_assert(invalid_addition(5 * isq::activity[Bq], zero_Hz + 10 / (2 * isq::t
static_assert(invalid_addition(5 * isq::activity[Bq], 10 / (2 * isq::time[s]), zero_Hz + 5 * isq::frequency[Hz])); static_assert(invalid_addition(5 * isq::activity[Bq], 10 / (2 * isq::time[s]), zero_Hz + 5 * isq::frequency[Hz]));
static_assert(invalid_subtraction(zero_Bq + 5 * isq::activity[Bq], 10 / (2 * isq::time[s]), 5 * isq::frequency[Hz])); static_assert(invalid_subtraction(zero_Bq + 5 * isq::activity[Bq], 10 / (2 * isq::time[s]), 5 * isq::frequency[Hz]));
static_assert(invalid_addition(quantity_point{5 * isq::activity[Bq]}, 5 * isq::frequency[Hz]));
static_assert(invalid_addition(5 * isq::activity[Bq], quantity_point{5 * isq::frequency[Hz]}));
static_assert(invalid_subtraction(quantity_point{5 * isq::activity[Bq]}, 5 * isq::frequency[Hz]));
static_assert(invalid_subtraction(quantity_point{5 * isq::activity[Bq]}, quantity_point{5 * isq::frequency[Hz]}));
static_assert(invalid_addition(quantity_point{5 * isq::activity[Bq]}, 10 / (2 * isq::time[s]), 5 * isq::frequency[Hz]));
static_assert(invalid_addition(5 * isq::activity[Bq], quantity_point{10 / (2 * isq::time[s])}, 5 * isq::frequency[Hz]));
static_assert(invalid_addition(5 * isq::activity[Bq], 10 / (2 * isq::time[s]), quantity_point{5 * isq::frequency[Hz]}));
static_assert(invalid_subtraction(quantity_point{5 * isq::activity[Bq]}, 10 / (2 * isq::time[s]),
5 * isq::frequency[Hz]));
} // namespace } // namespace