301 lines
10 KiB
Markdown
301 lines
10 KiB
Markdown
---
|
||
date: 2024-11-05
|
||
authors:
|
||
- mpusz
|
||
categories:
|
||
- Releases
|
||
comments: true
|
||
---
|
||
|
||
# mp-units 2.4.0 released!
|
||
|
||
**A new product version can be obtained from
|
||
[GitHub](https://github.com/mpusz/mp-units/releases/tag/v2.4.0) and
|
||
[Conan](https://conan.io/center/recipes/mp-units?version=2.4.0).**
|
||
|
||
This release was unexpected. We planned a significant new feature to happen next, but while
|
||
preparing for it, and also while writing API Reference documentation, we made so many vital fixes
|
||
and improvements that we decided that they deserve a dedicated release first.
|
||
|
||
This post describes the most significant improvements while a much longer list of the changes
|
||
introduced by the new version can be found in our [Release Notes](../../release_notes.md#2.4.0).
|
||
|
||
<!-- more -->
|
||
|
||
## ISQ quantities cleanup
|
||
|
||
Initially, we kept quantities defined in "IEC 80000-13: Information science and technology" in
|
||
a standalone `iec80000` namespace, which was renamed to `iec` in the previous release.
|
||
It turned out that this was incorrect. Those quantities are also a part of the ISQ. This is why,
|
||
in this release, we moved all of them to the `isq` namespace (:boom: **breaking change** :boom:).
|
||
|
||
From now on, `iec` namespace does not provide any quantities and serves purely as a system of units
|
||
definition. It contains binary prefixes (based on the powers of two) and some units introduced
|
||
by IEC (e.g., `var`, `erlang`, `bit`, or ``baud`).
|
||
|
||
!!! note
|
||
|
||
The quantities in `iec` namespace are now deprecated and will be removed in future releases.
|
||
|
||
Also, it turns out that the latest ISO 80000-3 revision makes a small cleanup to the `phase_speed`
|
||
and `group_speed` quantities. Those were always defined as scalar quantities but also had
|
||
alternative names `phase_velocity` and `group_velocity`. This is misleading as _velocity_ is
|
||
typically considered a vector quantity. It is why those `XXX_velocity` aliases were removed from
|
||
the ISO standard and from **mp-units** library (:boom: **breaking change** :boom:).
|
||
|
||
|
||
## Units equality
|
||
|
||
Previously we assumed that units like `J`, `N m`, and `kg m²/s²` are equal. In some cases,
|
||
this might not be entirely correct. Some quantities require a specific derived unit instead of
|
||
a unit with a special name. For example:
|
||
|
||
- `N m` should be used for _moment of force_ (instead of `J`),
|
||
- `V A` should be used for _apparent power_ (instead of `W`).
|
||
|
||
This is why, starting from this release units like `J`, `N m`, and `kg m²/s²` will not compare equal
|
||
(:boom: **breaking change** :boom:). However, they are deemed equivalent:
|
||
|
||
```cpp
|
||
static_assert(equivalent(J, N * m));
|
||
static_assert(equivalent(W, V * A));
|
||
```
|
||
|
||
|
||
## Portable text output
|
||
|
||
From the very beginning, the text output of symbols could be formatted in two different ways:
|
||
|
||
- Unicode,
|
||
- portable using so-called ASCII alternatives.
|
||
|
||
**mp-units** used the terms "Unicode" or "ASCII" and 'U' or 'A' formatting options for them.
|
||
Even though those terms are widely understood in the C++ community, they are technically
|
||
incorrect.
|
||
|
||
During the recent SG16 meeting, we looked for proper alternatives and ended up with the "portable"
|
||
and "UTF-8" terms (:boom: **breaking change** :boom:).
|
||
|
||
From now on, we will provide the following:
|
||
|
||
- `text_encoding::utf8`, `symbol_text<N, M>::utf8()`, and `U` formatting option,
|
||
- `text_encoding::portable`, `symbol_text<N, M>::portable()`, and `P` formatting option.
|
||
|
||
!!! note
|
||
|
||
The old identifiers and formatting options are now deprecated and will be removed in future
|
||
releases.
|
||
|
||
*[SG16]: WG21 Unicode Study Group
|
||
|
||
|
||
## `char_traits` removed from `fixed_string`
|
||
|
||
During the same SG16 meeting, the room was strongly against providing `char_traits` for
|
||
`fixed_string`. This is why `char_traits` support was removed in this release
|
||
(:boom: **breaking change** :boom:).
|
||
|
||
|
||
## Improved units' text output
|
||
|
||
In the previous release, we introduced common unit abstraction. Initially, all its components
|
||
were printed in parenthesis which contained a list of all the scaled units separated with `=`.
|
||
After some feedback, we decided to change it to a new syntax.
|
||
|
||
For example, the following:
|
||
|
||
```cpp
|
||
std::cout << 1 * km + 1 * mi << "\n";
|
||
std::cout << 1 * nmi + 1 * mi << "\n";
|
||
std::cout << 1 * km / h + 1 * m / s << "\n";
|
||
```
|
||
|
||
will print:
|
||
|
||
=== "Now"
|
||
|
||
```text
|
||
40771 EQUIV{[1/25146 mi], [1/15625 km]}
|
||
108167 EQUIV{[1/50292 mi], [1/57875 nmi]}
|
||
23 EQUIV{[1/5 km/h], [1/18 m/s]}
|
||
```
|
||
|
||
=== "Before"
|
||
|
||
```text
|
||
40771 ([1/25146] mi = [1/15625] km)
|
||
108167 ([1/50292] mi = [1/57875] nmi)
|
||
23 ([1/5] km/h = [1/18] m/s)
|
||
```
|
||
|
||
As we can see above, the scaled units output changed as well. Now, the entire scaled unit is
|
||
encapsulated within `[...]` brackets to better denote its scope.
|
||
|
||
Additionally, small magnitudes of scaled units do not use the power of `10`, and also scaled
|
||
units do not have a composition priority over the derived units anymore.
|
||
|
||
As a result of those changes, the following:
|
||
|
||
```cpp
|
||
constexpr Unit auto L_per_100km = L / (mag<100> * km);
|
||
std::cout << 6.7 * L_per_100km << "\n";
|
||
```
|
||
|
||
prints:
|
||
|
||
=== "Now"
|
||
|
||
```text
|
||
6.7 L/[100 km]
|
||
```
|
||
|
||
=== "Before"
|
||
|
||
```text
|
||
6.7 × 10⁻² l/km
|
||
```
|
||
|
||
One more change that we can see above is that litre now use 'L' instead of 'l' for its symbol.
|
||
The latter one too often is confused with the number `1`.
|
||
|
||
The next improvement adds proper formatting support for magnitudes. All of the formatting options
|
||
that were working before for the unit symbols now also work for magnitudes.
|
||
|
||
For example:
|
||
|
||
```cpp
|
||
using enum text_encoding;
|
||
using enum unit_symbol_solidus;
|
||
using usf = unit_symbol_formatting;
|
||
|
||
static_assert(unit_symbol(mag<1> / (mag<2> * mag<pi>)*metre) == "[2⁻¹ 𝜋⁻¹ m]");
|
||
static_assert(unit_symbol<usf{.solidus = always}>(mag<1> / (mag<2> * mag<pi>)*metre) == "[1/(2 𝜋) m]");
|
||
static_assert(unit_symbol<usf{.encoding = portable, .solidus = always}>(mag<1> / (mag<2> * mag<pi>)*metre) ==
|
||
"[1/(2 pi) m]");
|
||
```
|
||
|
||
As we can see above, the library also learned how to print magnitude symbols. This required
|
||
a change in the `mag_constant` definition. Now, it takes a magnitude symbol and has to be `final`
|
||
like for other similar types in the library (:boom: **breaking change** :boom:):
|
||
|
||
```cpp
|
||
inline constexpr struct pi final : mag_constant<symbol_text{u8"π", "pi"}, std::numbers::pi_v<long double>> {} pi;
|
||
inline constexpr auto π = pi;
|
||
```
|
||
|
||
## Unicode identifiers
|
||
|
||
The example above introduced something interesting: a `π` identifier for a variable. With the
|
||
latest changes to the C++ language, we can officially use Unicode symbols as identifiers in
|
||
the C++ code.
|
||
|
||
In this release, we've added Unicode identifiers support not only for `π` magnitude constant
|
||
but also for unit symbols.
|
||
|
||
Now we can type the following:
|
||
|
||
=== "With UTF-8 glyphs"
|
||
|
||
```cpp
|
||
quantity resistance = 60 * kΩ;
|
||
quantity capacitance = 100 * µF;
|
||
```
|
||
|
||
=== "Portable"
|
||
|
||
```cpp
|
||
quantity resistance = 60 * kohm;
|
||
quantity capacitance = 100 * uF;
|
||
```
|
||
|
||
This might make the source code easier to understand, but typing those identifiers can be tricky.
|
||
Sometimes, the best solution to type it might be a copy-paste approach. If we do not like this
|
||
idea, we can still use old portable identifiers for those as well.
|
||
|
||
|
||
## Convertibility with `QuantityLike` and `QuantityPointLike` entities
|
||
|
||
In this release, we decided to fine-tune further the traits that customize the conversion between
|
||
custom quantity and quantity point types and the ones provided with **mp-units**
|
||
(:boom: **breaking change** :boom:).
|
||
|
||
Previously, `to_numerical_value` and `from_numerical_value` returned a type wrapped in a special
|
||
tag type describing the conversion type (explicit or implicit).
|
||
|
||
This was a novel and experimental approach. Finally, we decided not to do it and used a bit more
|
||
verbose but a more standard solution. From now on, we need to provide two additional static data
|
||
members of type `bool`:
|
||
|
||
- `explicit_import` - `true` means that the conversion to the **mp-units** abstraction is explicit,
|
||
- `explicit_export` - `true` means that the conversion from the **mp-units** abstraction is
|
||
explicit.
|
||
|
||
=== "Now"
|
||
|
||
```cpp
|
||
template<>
|
||
struct mp_units::quantity_point_like_traits<Timestamp> {
|
||
static constexpr auto reference = si::second;
|
||
static constexpr auto point_origin = default_point_origin(reference);
|
||
static constexpr bool explicit_import = false;
|
||
static constexpr bool explicit_export = true;
|
||
using rep = decltype(Timestamp::seconds);
|
||
|
||
static constexpr rep to_numerical_value(Timestamp ts)
|
||
{
|
||
return ts.seconds;
|
||
}
|
||
|
||
static constexpr Timestamp from_numerical_value(rep v)
|
||
{
|
||
return Timestamp(v);
|
||
}
|
||
};
|
||
```
|
||
|
||
=== "Before"
|
||
|
||
```cpp
|
||
template<>
|
||
struct mp_units::quantity_point_like_traits<Timestamp> {
|
||
static constexpr auto reference = si::second;
|
||
static constexpr auto point_origin = default_point_origin(reference);
|
||
using rep = decltype(Timestamp::seconds);
|
||
|
||
static constexpr convert_implicitly<rep> to_numerical_value(Timestamp ts)
|
||
{
|
||
return ts.seconds;
|
||
}
|
||
|
||
static constexpr convert_explicitly<Timestamp> from_numerical_value(rep v)
|
||
{
|
||
return Timestamp(v);
|
||
}
|
||
};
|
||
```
|
||
|
||
|
||
## Symbolic constants implementation should be _implementation-defined_
|
||
|
||
In the process of writing API Reference, we decided to hide all the metadata associated with
|
||
symbolic constants - tag types used to define units, dimensions, quantity specification, etc.
|
||
(:boom: **breaking change** :boom:).
|
||
|
||
All the types and values exposed by such types are needed only in the implementation details
|
||
of the library. Users should not need them. Hiding those and making them
|
||
_implementation-defined_ gives other vendors the freedom to choose different ways to implement
|
||
features of this library in their codebases.
|
||
|
||
!!! important
|
||
|
||
Based on [Hyrum's Law](https://www.hyrumslaw.com/) some users may depend on this
|
||
information already, and this release will break their code.
|
||
|
||
If that is the case for you, we would love to hear about your use case and its rationale.
|
||
It may mean that we should either:
|
||
|
||
- extend the library's functionality to support your use case out of the box and keep those
|
||
members hidden,
|
||
- restore public visibility of such members and enforce this in the API Reference so that all
|
||
the users of various library implementations may use them in the same way as you.
|