2023-06-23 15:19:08 +02:00
|
|
|
# Interface Introduction
|
|
|
|
|
|
|
|
## New style of definitions
|
|
|
|
|
|
|
|
The **mp-units** library decided to use a rather unusual pattern to define entities.
|
2023-08-03 21:23:34 +02:00
|
|
|
Here is how we define `metre` and `second` [SI](../../appendix/glossary.md#si) base units:
|
2023-06-23 15:19:08 +02:00
|
|
|
|
|
|
|
```cpp
|
|
|
|
inline constexpr struct metre : named_unit<"m", kind_of<isq::length>> {} metre;
|
|
|
|
inline constexpr struct second : named_unit<"s", kind_of<isq::time>> {} second;
|
|
|
|
```
|
|
|
|
|
2023-10-25 21:39:09 +02:00
|
|
|
Please note that the above reuses the same identifier for a type and its value. The rationale
|
2023-06-23 15:19:08 +02:00
|
|
|
behind this is that:
|
|
|
|
|
2023-10-25 21:39:09 +02:00
|
|
|
- Users always work with values and never have to spell such a type name.
|
2023-06-23 15:19:08 +02:00
|
|
|
- The types appear in the compilation errors and during debugging.
|
|
|
|
|
2023-08-30 11:33:30 +02:00
|
|
|
!!! important
|
2023-06-23 15:19:08 +02:00
|
|
|
|
|
|
|
To improve compiler errors' readability and make it easier to correlate them with
|
|
|
|
a user's written code, a new idiom in the library is to use the same identifier for
|
|
|
|
a type and its instance.
|
|
|
|
|
|
|
|
|
2023-07-07 20:42:22 +02:00
|
|
|
## Strong types instead of aliases
|
|
|
|
|
|
|
|
Let's look again at the above units definitions. Another important point to notice is that
|
|
|
|
all the types describing entities in the library are short, nicely named identifiers
|
|
|
|
that derive from longer, more verbose class template instantiations. This is really important
|
|
|
|
to improve the user experience while debugging the program or analyzing the compilation error.
|
|
|
|
|
|
|
|
!!! note
|
|
|
|
|
|
|
|
Such a practice is rare in the industry. Some popular C++ physical units libraries
|
2023-10-25 21:39:09 +02:00
|
|
|
generate enormously long error messages where even only the first line failed to fit
|
2023-07-07 20:42:22 +02:00
|
|
|
on a slide with a tiny font.
|
|
|
|
|
|
|
|
|
2023-06-23 15:19:08 +02:00
|
|
|
## Entities composability
|
|
|
|
|
|
|
|
Many physical units libraries (in C++ or any other programming language) assign strong types
|
2023-10-25 21:39:09 +02:00
|
|
|
to library entities (e.g., derived units). While `metre_per_second` as a type may not look too
|
2023-06-23 15:19:08 +02:00
|
|
|
scary, consider, for example, units of angular momentum. If we followed this path, its
|
2023-08-03 21:23:34 +02:00
|
|
|
[coherent unit](../../appendix/glossary.md#coherent-derived-unit) would look like
|
2023-10-25 21:39:09 +02:00
|
|
|
`kilogram_metre_sq_per_second`. Now, consider how many scaled versions of this unit you would
|
2023-06-23 15:19:08 +02:00
|
|
|
predefine in the library to ensure that all users are happy with your choice?
|
|
|
|
How expensive would it be from the implementation point of view?
|
|
|
|
What about potential future standardization efforts?
|
|
|
|
|
|
|
|
This is why in **mp-units**, we put a strong requirement to make everything as composable as
|
|
|
|
possible. For example, to create a quantity with a unit of speed, one may write:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
quantity<si::metre / si::second> q;
|
|
|
|
```
|
|
|
|
|
2023-10-25 21:39:09 +02:00
|
|
|
In case we use such a unit often and would prefer to have a handy helper for it, we can
|
2023-06-23 15:19:08 +02:00
|
|
|
always do something like this:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
constexpr auto metre_per_second = si::metre / si::second;
|
|
|
|
quantity<metre_per_second> q;
|
|
|
|
```
|
|
|
|
|
2023-10-25 21:39:09 +02:00
|
|
|
or choose any shorter identifier of our choice.
|
2023-06-23 15:19:08 +02:00
|
|
|
|
|
|
|
Coming back to the angular momentum case, thanks to the composability of units, a user can
|
|
|
|
create such a quantity in the following way:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
using namespace mp_units::si::unit_symbols;
|
|
|
|
auto q = la_vector{1, 2, 3} * isq::angular_momentum[kg * m2 / s];
|
|
|
|
```
|
|
|
|
|
|
|
|
It is a much better solution. It is terse and easy to understand. Please also notice how
|
2023-10-25 21:39:09 +02:00
|
|
|
easy it is to obtain any scaled version of such a unit (e.g., `mg * square(mm) / min`)
|
2023-06-23 15:19:08 +02:00
|
|
|
without having to introduce hundreds of types to predefine them.
|
|
|
|
|
|
|
|
|
2023-07-07 20:42:22 +02:00
|
|
|
## Value-based equations
|
|
|
|
|
2023-10-25 21:39:09 +02:00
|
|
|
The **mp-units** library is based on C++20, significantly improving user experience. One of
|
2024-02-15 22:18:39 +01:00
|
|
|
such improvements is the usage of value-based equations.
|
2023-07-07 20:42:22 +02:00
|
|
|
|
|
|
|
As we have learned above, the entities are being used as values in the code, and they compose.
|
|
|
|
Moreover, derived entities can be defined in the library using such value-based equations.
|
|
|
|
This is a huge improvement compared to what we can find in other physical units libraries or
|
|
|
|
what we have to deal with when we want to write some equations for `std::ratio`.
|
|
|
|
|
|
|
|
For example, below are a few definitions of the SI derived units showing the power of C++20
|
2023-10-25 21:39:09 +02:00
|
|
|
extensions to Non-Type Template Parameters, which allow us to directly pass a result of
|
2023-08-03 21:23:34 +02:00
|
|
|
the value-based [unit equation](../../appendix/glossary.md#unit-equation) to a class template
|
2023-07-07 20:42:22 +02:00
|
|
|
definition:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
inline constexpr struct newton : named_unit<"N", kilogram * metre / square(second)> {} newton;
|
|
|
|
inline constexpr struct pascal : named_unit<"Pa", newton / square(metre)> {} pascal;
|
|
|
|
inline constexpr struct joule : named_unit<"J", newton * metre> {} joule;
|
|
|
|
```
|
|
|
|
|
|
|
|
|
2023-06-23 15:19:08 +02:00
|
|
|
## Expression templates
|
|
|
|
|
|
|
|
The previous chapter provided a rationale for not having predefined types for derived entities.
|
|
|
|
In many libraries, such an approach results in long and unreadable compilation errors, as
|
|
|
|
framework-generated types are typically far from being easy to read and understand.
|
|
|
|
|
|
|
|
The **mp-units** library greatly improves the user experience by extensively using
|
|
|
|
expression templates. Such expressions are used consistently throughout the entire library
|
|
|
|
to describe the results of:
|
|
|
|
|
2023-08-03 21:23:34 +02:00
|
|
|
- [dimension equation](../../appendix/glossary.md#dimension-equation) - the result is put into
|
2023-06-23 15:19:08 +02:00
|
|
|
the `derived_dimension<>` class template
|
2023-08-03 21:23:34 +02:00
|
|
|
- [quantity equation](../../appendix/glossary.md#quantity-equation) - the result is put into
|
2023-06-23 15:19:08 +02:00
|
|
|
the `derived_quantity_spec<>` class template
|
2023-08-03 21:23:34 +02:00
|
|
|
- [unit equation](../../appendix/glossary.md#unit-equation) - the result is put into the
|
|
|
|
`derived_unit<>` class template
|
2023-06-23 15:19:08 +02:00
|
|
|
|
|
|
|
For example, if we take the above-defined base units and put the results of their division into
|
|
|
|
the quantity class template like this:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
quantity<metre / second> q;
|
|
|
|
```
|
|
|
|
|
|
|
|
we will observe the following type in the debugger
|
|
|
|
|
|
|
|
```
|
|
|
|
(gdb) ptype q
|
|
|
|
type = class mp_units::quantity<mp_units::derived_unit<metre, mp_units::per<second>>(), double> [with Rep = double] {
|
|
|
|
```
|
|
|
|
|
|
|
|
The same type identifier will be visible in the compilation error (in case it happens).
|
|
|
|
|
2023-08-30 11:33:30 +02:00
|
|
|
!!! important
|
2023-06-23 15:19:08 +02:00
|
|
|
|
|
|
|
Expressions templates are extensively used throughout the library to improve the readability
|
|
|
|
of the resulting types.
|
|
|
|
|
|
|
|
|
|
|
|
### Identities
|
|
|
|
|
2023-10-25 21:39:09 +02:00
|
|
|
As mentioned above, equations can be performed on dimensions, quantities, and units. Each such domain must
|
2023-06-23 15:19:08 +02:00
|
|
|
introduce an identity object that can be used in the resulting expressions. Here is the list of
|
|
|
|
identities used in the library:
|
|
|
|
|
|
|
|
|
|
|
|
| Domain Concept | Identity |
|
|
|
|
|----------------|:---------------:|
|
|
|
|
| `Dimension` | `dimension_one` |
|
|
|
|
| `QuantitySpec` | `dimensionless` |
|
|
|
|
| `Unit` | `one` |
|
|
|
|
|
2023-10-25 21:39:09 +02:00
|
|
|
In the equations, a user can explicitly refer to an identity object. For example:
|
2023-06-23 15:19:08 +02:00
|
|
|
|
|
|
|
```cpp
|
|
|
|
constexpr auto my_unit = one / second;
|
|
|
|
```
|
|
|
|
|
2023-10-06 12:53:18 -06:00
|
|
|
!!! note
|
2023-06-23 15:19:08 +02:00
|
|
|
|
2023-10-06 12:53:18 -06:00
|
|
|
Another way to achieve the same result is to call an `inverse()` function:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
constexpr auto my_unit = inverse(second);
|
|
|
|
```
|
2023-06-23 15:19:08 +02:00
|
|
|
|
2023-10-06 12:53:18 -06:00
|
|
|
Both cases will result in the same expression template being generated and put into the wrapper
|
|
|
|
class template.
|
2023-06-23 15:19:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
### Supported operations and their results
|
|
|
|
|
2023-10-25 21:39:09 +02:00
|
|
|
There are only a few operations that one can do on such entities, and the result of each of them has
|
2023-06-23 15:19:08 +02:00
|
|
|
its unique representation in the library:
|
|
|
|
|
|
|
|
| Operation | Resulting template expression arguments |
|
|
|
|
|:---------------------------------------------:|:---------------------------------------:|
|
|
|
|
| `A * B` | `A, B` |
|
|
|
|
| `B * A` | `A, B` |
|
|
|
|
| `A * A` | `power<A, 2>` |
|
|
|
|
| `{identity} * A` | `A` |
|
|
|
|
| `A * {identity}` | `A` |
|
|
|
|
| `A / B` | `A, per<B>` |
|
|
|
|
| `A / A` | `{identity}` |
|
|
|
|
| `A / {identity}` | `A` |
|
|
|
|
| `{identity} / A` | `{identity}, per<A>` |
|
|
|
|
| `pow<2>(A)` | `power<A, 2>` |
|
|
|
|
| `pow<2>({identity})` | `{identity}` |
|
|
|
|
| `sqrt(A)` or `pow<1, 2>(A)` | `power<A, 1, 2>` |
|
|
|
|
| `sqrt({identity})` or `pow<1, 2>({identity})` | `{identity}` |
|
|
|
|
|
|
|
|
|
|
|
|
### Simplifying the resulting expression templates
|
|
|
|
|
|
|
|
To limit the length and improve the readability of generated types, there are many rules to simplify
|
|
|
|
the resulting expression template.
|
|
|
|
|
|
|
|
1. **Ordering**
|
|
|
|
|
|
|
|
The resulting comma-separated arguments of multiplication are always sorted according to
|
|
|
|
a specific predicate. This is why:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
static_assert(A * B == B * A);
|
|
|
|
static_assert(std::is_same_v<decltype(A * B), decltype(B * A)>);
|
|
|
|
```
|
|
|
|
|
2023-10-10 20:24:23 +02:00
|
|
|
This is probably the most important of all the steps, as it allows comparing types and enables
|
2023-10-25 21:39:09 +02:00
|
|
|
the rest of the simplification rules.
|
2023-06-23 15:19:08 +02:00
|
|
|
|
|
|
|
2. **Aggregation**
|
|
|
|
|
|
|
|
In case two of the same identifiers are found next to each other on the argument list they
|
|
|
|
will be aggregated in one entry:
|
|
|
|
|
|
|
|
| Before | After |
|
|
|
|
|:--------------------------------:|:----------------:|
|
|
|
|
| `A, A` | `power<A, 2>` |
|
|
|
|
| `A, power<A, 2>` | `power<A, 3>` |
|
|
|
|
| `power<A, 1, 2>, power<A, 2>` | `power<A, 5, 2>` |
|
|
|
|
| `power<A, 1, 2>, power<A, 1, 2>` | `A` |
|
|
|
|
|
|
|
|
3. **Simplification**
|
|
|
|
|
|
|
|
In case two of the same identifiers are found in the numerator and denominator argument lists;
|
|
|
|
they are being simplified into one entry:
|
|
|
|
|
|
|
|
| Before | After |
|
|
|
|
|:---------------------:|:--------------------:|
|
|
|
|
| `A, per<A>` | `{identity}` |
|
|
|
|
| `power<A, 2>, per<A>` | `A` |
|
|
|
|
| `power<A, 3>, per<A>` | `power<A, 2>` |
|
|
|
|
| `A, per<power<A, 2>>` | `{identity}, per<A>` |
|
|
|
|
|
|
|
|
4. **Repacking**
|
|
|
|
|
|
|
|
In case an expression uses two results of other operations, the components of its arguments are repacked
|
|
|
|
into one resulting type and simplified there.
|
|
|
|
|
|
|
|
For example, assuming:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
constexpr auto X = A / B;
|
|
|
|
```
|
|
|
|
|
|
|
|
then:
|
|
|
|
|
|
|
|
| Operation | Resulting template expression arguments |
|
|
|
|
|:---------:|:---------------------------------------:|
|
|
|
|
| `X * B` | `A` |
|
|
|
|
| `X * A` | `power<A, 2>, per<B>` |
|
|
|
|
| `X * X` | `power<A, 2>, per<power<B, 2>>` |
|
|
|
|
| `X / X` | `{identity}` |
|
|
|
|
| `X / A` | `{identity}, per<B>` |
|
|
|
|
| `X / B` | `A, per<power<B, 2>>` |
|
|
|
|
|
|
|
|
|
|
|
|
## Example
|
|
|
|
|
|
|
|
Thanks to all of the features described above, a user may write the code like this one:
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
using namespace mp_units::si::unit_symbols;
|
2023-10-25 21:39:09 +02:00
|
|
|
quantity speed = 60. * isq::speed[km / h];
|
|
|
|
quantity duration = 8 * s;
|
|
|
|
quantity acceleration = speed / duration;
|
2023-08-23 16:46:15 +02:00
|
|
|
std::cout << "acceleration: " << acceleration << " (" << acceleration.in(m / s2) << ")\n";
|
2023-06-23 15:19:08 +02:00
|
|
|
```
|
|
|
|
|
2023-10-25 21:39:09 +02:00
|
|
|
The `acceleration` quantity, being the result of the above code, has the following type
|
2023-06-23 15:19:08 +02:00
|
|
|
(after stripping the `mp_units` namespace for brevity):
|
|
|
|
|
|
|
|
```text
|
|
|
|
quantity<reference<derived_quantity_spec<isq::speed, per<isq::time>>{}, derived_unit<si::kilo_<si::metre{}>, per<non_si::hour, si::second>>{}>{}, int>
|
|
|
|
```
|
|
|
|
|
|
|
|
and the text output presents:
|
|
|
|
|
|
|
|
```text
|
|
|
|
acceleration: 7.5 km h⁻¹ s⁻¹ (2.08333 m/s²)
|
|
|
|
```
|