mirror of
https://github.com/mpusz/mp-units.git
synced 2025-08-01 03:14:29 +02:00
docs: "Interface Introduction" chapter added
This commit is contained in:
245
docs/users_guide/framework_basics/interface_introduction.md
Normal file
245
docs/users_guide/framework_basics/interface_introduction.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# Interface Introduction
|
||||
|
||||
## New style of definitions
|
||||
|
||||
The **mp-units** library decided to use a rather unusual pattern to define entities.
|
||||
Here is how we define `metre` and `second` [SI](../../../appendix/glossary/#si) base units:
|
||||
|
||||
```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;
|
||||
```
|
||||
|
||||
Please note that the above reuses the same identifier for a type and its object. The rationale
|
||||
behind this is that:
|
||||
|
||||
- Users always work with objects and never have to spell such a type name.
|
||||
- The types appear in the compilation errors and during debugging.
|
||||
|
||||
!!! info
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Entities composability
|
||||
|
||||
Many physical units libraries (in C++ or any other programming language) assign strong types
|
||||
to library entities (i.e. derived units). While `metre_per_second` as a type may not look too
|
||||
scary, consider, for example, units of angular momentum. If we followed this path, its
|
||||
[coherent unit](../../../appendix/glossary/#coherent-derived-unit) would look like
|
||||
`kilogram_metre_sq_per_second`. Now, consider how many scaled versions of this unit would you
|
||||
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;
|
||||
```
|
||||
|
||||
In case you use such an unit often and would prefer to have a handy helper for it, you can
|
||||
always do something like this:
|
||||
|
||||
```cpp
|
||||
constexpr auto metre_per_second = si::metre / si::second;
|
||||
quantity<metre_per_second> q;
|
||||
```
|
||||
|
||||
or choose any shorter identifier of your choice.
|
||||
|
||||
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
|
||||
easy it is to obtain any scaled version of such a unit (i.e. `mg * square(mm) / min`)
|
||||
without having to introduce hundreds of types to predefine them.
|
||||
|
||||
|
||||
## 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:
|
||||
|
||||
- [dimension equation](../../../appendix/glossary/#dimension-equation) - the result is put into
|
||||
the `derived_dimension<>` class template
|
||||
- [quantity equation](../../../appendix/glossary/#quantity-equation) - the result is put into
|
||||
the `derived_quantity_spec<>` class template
|
||||
- [unit equation](../../../appendix/glossary/#unit-equation) - the result is put into
|
||||
the `derived_unit<>` class template
|
||||
|
||||
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).
|
||||
|
||||
!!! info
|
||||
|
||||
Expressions templates are extensively used throughout the library to improve the readability
|
||||
of the resulting types.
|
||||
|
||||
|
||||
### Identities
|
||||
|
||||
As mentioned above, equations can be done on dimensions, quantities, and units. Each such domain must
|
||||
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` |
|
||||
|
||||
In the equations, a user can refer to an identity object either explicitly:
|
||||
|
||||
```cpp
|
||||
constexpr auto my_unit = one / second;
|
||||
```
|
||||
|
||||
or implicitly:
|
||||
|
||||
```cpp
|
||||
constexpr auto my_unit = 1 / second;
|
||||
```
|
||||
|
||||
Both cases with result in the same expression template being generated and put into the wrapper
|
||||
class template.
|
||||
|
||||
|
||||
### Supported operations and their results
|
||||
|
||||
There are only a few operations that one can do on such entities and the result of each of them has
|
||||
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)>);
|
||||
```
|
||||
|
||||
This is probably the most important of all steps, as it allows comparing types and enables the rest of
|
||||
simplification rules.
|
||||
|
||||
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;
|
||||
auto speed = 60. * isq::speed[km / h];
|
||||
auto duration = 8 * s;
|
||||
auto acceleration = speed / duration;
|
||||
std::cout << "acceleration: " << acceleration << " (" << acceleration[m / s2] << ")\n";
|
||||
```
|
||||
|
||||
The `acceleration`, being the result of the above code, has the following type
|
||||
(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²)
|
||||
```
|
@@ -106,6 +106,7 @@ nav:
|
||||
- Terms and Definitions: users_guide/terms_and_definitions.md
|
||||
- Framework Basics:
|
||||
- Basic Concepts: users_guide/framework_basics/basic_concepts.md
|
||||
- Interface Introduction: users_guide/framework_basics/interface_introduction.md
|
||||
- Systems of Quantities: users_guide/framework_basics/systems_of_quantities.md
|
||||
- Systems of Units: users_guide/framework_basics/systems_of_units.md
|
||||
- Value Conversions: users_guide/framework_basics/value_conversions.md
|
||||
|
Reference in New Issue
Block a user