mirror of
https://github.com/mpusz/mp-units.git
synced 2025-07-31 19:04:27 +02:00
docs: 2.3.0 release announcement updated
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
draft: true
|
draft: true
|
||||||
date: 2024-06-15
|
date: 2024-09-24
|
||||||
authors:
|
authors:
|
||||||
- mpusz
|
- mpusz
|
||||||
categories:
|
categories:
|
||||||
@@ -13,12 +13,77 @@ categories:
|
|||||||
[GitHub](https://github.com/mpusz/mp-units/releases/tag/v2.3.0) and
|
[GitHub](https://github.com/mpusz/mp-units/releases/tag/v2.3.0) and
|
||||||
[Conan](https://conan.io/center/recipes/mp-units?version=2.3.0).**
|
[Conan](https://conan.io/center/recipes/mp-units?version=2.3.0).**
|
||||||
|
|
||||||
This release fine-tunes many key features of the library. This post describes those and a few
|
This release fine-tunes many key features of the library. This post describes the most interesting
|
||||||
other smaller interesting improvements, while a much longer list of the most significant changes
|
improvements, while a much longer list of the changes introduced by the new version can be found in
|
||||||
introduced by the new version can be found in our [Release Notes](../../release_notes.md#2.3.0).
|
our [Release Notes](../../release_notes.md#2.3.0).
|
||||||
|
|
||||||
<!-- more -->
|
<!-- more -->
|
||||||
|
|
||||||
|
## CMake and Conan options changed
|
||||||
|
|
||||||
|
During the review on the ConanCenter, we got feedback that we should improve the handling of
|
||||||
|
options for which value is automatically determined based on the current configuration.
|
||||||
|
Instead of explicitly setting the `auto` value, we defer the choice between `True`/`False` until
|
||||||
|
the configuration stage and set it there once all the settings are known. `auto` value for such
|
||||||
|
option was removed (:boom: **breaking change** :boom:).
|
||||||
|
|
||||||
|
If you didn't set any value at the command line for such options, everything stays the same for
|
||||||
|
you. However, some changes are needed if you explicitly used `auto` like below:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
conan install . -o 'mp-units:std_format=auto' -s compiler.cppstd=23 -b missing
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you have to either skip such an option to keep automatic deduction:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
conan install . -s compiler.cppstd=23 -b missing
|
||||||
|
```
|
||||||
|
|
||||||
|
or set it explicitly to `True` or `False` to force a specific configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
conan install . -o 'mp-units:std_format=True' -s compiler.cppstd=23 -b missing
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Representation type template parameter added to value conversion functions
|
||||||
|
|
||||||
|
Previously, changing a representation type was only possible with a `value_cast<NewRep>(q)`
|
||||||
|
non-member function while a change of unit was supported by all `value_cast<NewU>(q)`,
|
||||||
|
`q.in(NewU)`, and `q.force_in(NewU)`. The rationale for it was that passing an explicit type to
|
||||||
|
a member function template requires a `template` disambiguator when we are dealing with a dependent
|
||||||
|
name (e.g., `quantity` type is determined based on a template parameter).
|
||||||
|
|
||||||
|
During a discussion in LEWGI at the St. Louis WG21 Meeting, we decided to provide such additional
|
||||||
|
overloads despite possible issues when a dependent name is used. In such case, a user needs
|
||||||
|
to provide a `template` disambiguator or switch back to using `value_cast`:
|
||||||
|
|
||||||
|
*[LEWGI]: Library Evolution Working Group Incubator
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// non-dependent name
|
||||||
|
auto f(quantity<m, int> q) { return q.in<double>(km); }
|
||||||
|
auto g(quantity<m, int> q) { return value_cast<double, km>(q); }
|
||||||
|
|
||||||
|
// dependent name
|
||||||
|
auto h(QuantityOf<isq::length> auto q) { return q.template in<double>(km); }
|
||||||
|
auto i(QuantityOf<isq::length> auto q) { return value_cast<double, km>(q); }
|
||||||
|
```
|
||||||
|
|
||||||
|
The table below provides all the value conversion functions in **mp-units** that may be run on
|
||||||
|
`x` being the instance of either `quantity` or `quantity_point`:
|
||||||
|
|
||||||
|
| Forcing | Representation | Unit | Member function | Non-member function |
|
||||||
|
|:-------:|:--------------:|:----:|--------------------|------------------------------------------------|
|
||||||
|
| No | Same | `u` | `x.in(u)` | |
|
||||||
|
| No | `T` | Same | `x.in<T>()` | |
|
||||||
|
| No | `T` | `u` | `x.in<T>(u)` | |
|
||||||
|
| Yes | Same | `u` | `x.force_in(u)` | `value_cast<u>(x)` |
|
||||||
|
| Yes | `T` | Same | `x.force_in<T>()` | `value_cast<T>(x)` |
|
||||||
|
| Yes | `T` | `u` | `x.force_in<T>(u)` | `value_cast<u, T>(x)` or `value_cast<T, u>(x)` |
|
||||||
|
|
||||||
|
|
||||||
## Quantity reference specifiers
|
## Quantity reference specifiers
|
||||||
|
|
||||||
The features described in this chapter directly solve an issue raised on
|
The features described in this chapter directly solve an issue raised on
|
||||||
@@ -50,7 +115,7 @@ of the affine space abstractions and how they influence temperature handling.
|
|||||||
|
|
||||||
After a lengthy discussion on handling such scenarios, we decided to:
|
After a lengthy discussion on handling such scenarios, we decided to:
|
||||||
|
|
||||||
- make the above code ill-formed,
|
- make the above code ill-formed (:boom: **breaking change** :boom:),
|
||||||
- provide an alternative way to create a `quantity` with the `delta` quantity construction helper.
|
- provide an alternative way to create a `quantity` with the `delta` quantity construction helper.
|
||||||
|
|
||||||
Here are the main points of this new design:
|
Here are the main points of this new design:
|
||||||
@@ -94,9 +159,9 @@ Here are the main points of this new design:
|
|||||||
|
|
||||||
With such changes to the interface design, the offending code will not compile as initially written.
|
With such changes to the interface design, the offending code will not compile as initially written.
|
||||||
Users will be forced to think more about what they write. To enable the compilation, the users have
|
Users will be forced to think more about what they write. To enable the compilation, the users have
|
||||||
to explicitly create a:
|
to create explicitly:
|
||||||
|
|
||||||
- `quantity_point` (the intended abstraction in this example) with any of the below syntaxes:
|
- a `quantity_point` (the intended abstraction in this example) with any of the below syntaxes:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
quantity_point Temperature = absolute<deg_C>(28.0);
|
quantity_point Temperature = absolute<deg_C>(28.0);
|
||||||
@@ -104,7 +169,7 @@ to explicitly create a:
|
|||||||
quantity_point Temperature(delta<deg_C>(28.0));
|
quantity_point Temperature(delta<deg_C>(28.0));
|
||||||
```
|
```
|
||||||
|
|
||||||
- `quantity` (an incorrect abstraction in this example) with:
|
- a `quantity` (an incorrect abstraction in this example) with:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
quantity Temperature = delta<deg_C>(28.0);
|
quantity Temperature = delta<deg_C>(28.0);
|
||||||
@@ -113,3 +178,272 @@ to explicitly create a:
|
|||||||
|
|
||||||
Thanks to the new design, we can immediately see what happens here and why the result might be
|
Thanks to the new design, we can immediately see what happens here and why the result might be
|
||||||
incorrect in the second case.
|
incorrect in the second case.
|
||||||
|
|
||||||
|
|
||||||
|
## `quantity_point_like_traits` are based on numerical value instead of a quantity
|
||||||
|
|
||||||
|
In this release, we decided to fine-tune the traits that customize the conversion between custom
|
||||||
|
quantity point types and the ones provided with **mp-units** (:boom: **breaking change** :boom:).
|
||||||
|
|
||||||
|
Previously, such type traits were based on the `quantity` type. This was inconsistent with
|
||||||
|
`quantity_like_traits`, that is working on raw values. Also, there are cases where a custom
|
||||||
|
quantity point abstraction is not modelled with a quantity type. In such cases, the previous
|
||||||
|
approach required additional types to be introduced for no good reason.
|
||||||
|
|
||||||
|
=== "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);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "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<quantity<reference, rep>> to_quantity(Timestamp ts)
|
||||||
|
{
|
||||||
|
return ts.seconds * si::second;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr convert_explicitly<Timestamp> from_quantity(quantity<reference, rep> q)
|
||||||
|
{
|
||||||
|
return Timestamp(q.numerical_value_ref_in(si::second));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## `mag<pi>`
|
||||||
|
|
||||||
|
With this release, we introduced a new strongly-typed constant to create a magnitude involving
|
||||||
|
scaling by `pi`. The solution used before was not consistent with magnitudes of integral values
|
||||||
|
and also was leaking a floating-point value of `std::numbers::pi_v<long double>` to the resulting
|
||||||
|
magnitude type. With the new approach, this is no longer the case, and the user-facing interface
|
||||||
|
is more consistent:
|
||||||
|
|
||||||
|
=== "Now"
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
inline constexpr struct degree final : named_unit<{u8"°", "deg"}, mag<pi> / mag<180> * si::radian> {} degree;
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Before"
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
inline constexpr struct degree final : named_unit<{u8"°", "deg"}, mag_pi / mag<180> * si::radian> {} degree;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Superpowers of the unit `one`
|
||||||
|
|
||||||
|
In this release, we also added a long-awaited change. From now on a quantity of a unit `one` can be:
|
||||||
|
|
||||||
|
- **implicitly constructed from** the raw value,
|
||||||
|
- **explicitly converted to** a raw value,
|
||||||
|
- **compared to** a raw value.
|
||||||
|
|
||||||
|
=== "Now"
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
quantity<one> inc(quantity<one> q) { return q + 1; }
|
||||||
|
void legacy(double) { /* ... */ }
|
||||||
|
|
||||||
|
if (auto q = inc(42); q != 0)
|
||||||
|
legacy(static_cast<int>(q));
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Before"
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
quantity<one> inc(quantity<one> q) { return q + 1 * one; }
|
||||||
|
void legacy(double) { /* ... */ }
|
||||||
|
|
||||||
|
if (auto q = inc(42 * one); q != 0 * one)
|
||||||
|
legacy(q.numerical_value_in(one));
|
||||||
|
```
|
||||||
|
|
||||||
|
This property also expands to usual arithmetic operators.
|
||||||
|
|
||||||
|
With the above change, we can now achieve the same results in a terser way:
|
||||||
|
|
||||||
|
=== "Now"
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
static_assert(10 * km / (5 * km) == 2);
|
||||||
|
const quantity gain = 1. / index;
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Before"
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
static_assert(10 * km / (5 * km) == 2 * one);
|
||||||
|
const quantity gain = 1. / index * one;
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
Those rules do not apply to all the dimensionless quantities. It would be unsafe and misleading
|
||||||
|
to allow such operations on units with a magnitude different than `1` (e.g., `percent` or
|
||||||
|
`radian`).
|
||||||
|
|
||||||
|
|
||||||
|
## `import std;` support
|
||||||
|
|
||||||
|
This release brings experimental support for `import std;`. The only compiler that supports
|
||||||
|
it for now is clang-18+. Until all the compilers start to support it and CMake removes
|
||||||
|
the experimental tag from this feature, we will also keep it experimental.
|
||||||
|
|
||||||
|
As all of the C++ compilers are buggy for now, it is not allowed to bring the same definitions
|
||||||
|
through the `import std;` and regular header files. This applies not only to the current project
|
||||||
|
but also to all its dependencies. This is why, in order to use it with **mp-units**, we need to
|
||||||
|
disable all the dependencies as well (enforced with `conanfile.py`). It means that we have to use
|
||||||
|
`std::format` (instead of [fmtlib](https://github.com/fmtlib/fmt)) and remove functions contract
|
||||||
|
checking.
|
||||||
|
|
||||||
|
With the above assumptions, we can refactor our smoot example to:
|
||||||
|
|
||||||
|
```cpp hl_lines="2"
|
||||||
|
import mp_units;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
using namespace mp_units;
|
||||||
|
|
||||||
|
inline constexpr struct smoot final : named_unit<"smoot", mag<67> * usc::inch> {} smoot;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
constexpr quantity dist = 364.4 * smoot;
|
||||||
|
std::println("Harvard Bridge length = {::N[.1f]} ({::N[.1f]}, {::N[.2f]}) ± 1 εar",
|
||||||
|
dist, dist.in(usc::foot), dist.in(si::metre));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## `unit_can_be_prefixed` removed
|
||||||
|
|
||||||
|
Previously, the `unit_can_be_prefixed` type trait was used to limit the possibility to prefix
|
||||||
|
some units that are officially known as non-prefixable (e.g., hour, minute).
|
||||||
|
|
||||||
|
It turned out that it is not easy to determine whether some units can be prefixed. For example,
|
||||||
|
for degree Celsius, the ISO 80000-5 standard explicitly states:
|
||||||
|
|
||||||
|
> Prefixes are not allowed in combination with the unit °C.
|
||||||
|
|
||||||
|
On the other hand [this NIST page](https://www.nist.gov/pml/owm/writing-si-metric-system-units)
|
||||||
|
says:
|
||||||
|
|
||||||
|
> Prefix symbols may be used with the unit symbol ºC and prefix names may be used with the unit
|
||||||
|
> name “degree Celsius.” For example, 12 mºC (12 millidegrees Celsius) is acceptable.
|
||||||
|
|
||||||
|
It seems that it is also a [common engineering practice](https://github.com/search?q=repo%3Atorvalds%2Flinux+millidegree&type=code).
|
||||||
|
|
||||||
|
To prevent such issues, we decided to simplify the library's design and remove the
|
||||||
|
`unit_can_be_prefixed` type trait (:boom: **breaking change** :boom:).
|
||||||
|
|
||||||
|
From now on, every named unit in the library can be prefixed with the SI or IEC prefix.
|
||||||
|
|
||||||
|
|
||||||
|
## `iec80000` system renamed to `iec`
|
||||||
|
|
||||||
|
As we mentioned IEC already, in this release, we decided to rename the name of the system and its
|
||||||
|
corresponding namespace from `iec80000` to `iec` (:boom: **breaking change** :boom:).
|
||||||
|
|
||||||
|
This should be easier to type and is more correct for some quantities and units that are introduced
|
||||||
|
by IEC but not necessarily in the ISO/IEC 80000 series of documents (e.g., `iec::var`).
|
||||||
|
|
||||||
|
|
||||||
|
## Error messages-related improvements
|
||||||
|
|
||||||
|
The readability of compile-time error messages is always a challenge for generic C++ libraries.
|
||||||
|
However, for quantities and units library, generating readable errors is the most important
|
||||||
|
requirement. If you do not make errors, you do not need such a library in your project :wink:.
|
||||||
|
|
||||||
|
This is why we put lots of effort into improving here. Besides submitting compiler bugs to improve
|
||||||
|
on their part, we also try to do our best here.
|
||||||
|
|
||||||
|
Some compilers do not present the type resulting from calling a function within a template
|
||||||
|
argument. This ends up with statements like `get_quantity_spec(si::second{})` in the error message.
|
||||||
|
Some less experienced users of the library may not know what this mean, and then why the
|
||||||
|
conversion error happens.
|
||||||
|
|
||||||
|
To improve this, we injected additional helper concepts into the definitions. It results with
|
||||||
|
a bit longer but a more readable error in the end.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
=== "Now"
|
||||||
|
|
||||||
|
```text hl_lines="19"
|
||||||
|
error: no matching member function for call to 'in'
|
||||||
|
15 | const quantity time_to_goal = (distance * speed).in(s);
|
||||||
|
| ~~~~~~~~~~~~~~~~~~~^~
|
||||||
|
note: candidate template ignored: constraints not satisfied [with ToU = struct second]
|
||||||
|
221 | [[nodiscard]] constexpr QuantityOf<quantity_spec> auto in(ToU) const
|
||||||
|
| ^
|
||||||
|
note: because 'detail::UnitCompatibleWith<si::second, unit, quantity_spec>' evaluated to false
|
||||||
|
219 | template<detail::UnitCompatibleWith<unit, quantity_spec> ToU>
|
||||||
|
| ^
|
||||||
|
note: because '!AssociatedUnit<si::second>' evaluated to false
|
||||||
|
164 | (!AssociatedUnit<U> || UnitOf<U, QS>) && detail::UnitConvertibleTo<FromU, U{}>;
|
||||||
|
| ^
|
||||||
|
note: and 'UnitOf<si::second, kind_of_<derived_quantity_spec<power<isq::length, 2>, per<isq::time> > >{}>' evaluated to false
|
||||||
|
164 | (!AssociatedUnit<U> || UnitOf<U, QS>) && detail::UnitConvertibleTo<FromU, U{}>;
|
||||||
|
| ^
|
||||||
|
note: because 'detail::QuantitySpecConvertibleTo<get_quantity_spec(si::second{}), kind_of_<derived_quantity_spec<power<isq::length, 2>, per<isq::time> > >{}>' evaluated to false
|
||||||
|
141 | detail::QuantitySpecConvertibleTo<get_quantity_spec(U{}), QS> &&
|
||||||
|
| ^
|
||||||
|
note: because 'implicitly_convertible(kind_of_<struct time>{}, kind_of_<derived_quantity_spec<power<isq::length, 2>, per<isq::time> > >{})' evaluated to false
|
||||||
|
151 | implicitly_convertible(From, To);
|
||||||
|
| ^
|
||||||
|
1 error generated.
|
||||||
|
Compiler returned: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Before"
|
||||||
|
|
||||||
|
```text hl_lines="16"
|
||||||
|
error: no matching member function for call to 'in'
|
||||||
|
15 | const quantity time_to_goal = (distance * speed).in(s);
|
||||||
|
| ~~~~~~~~~~~~~~~~~~~^~
|
||||||
|
note: candidate template ignored: constraints not satisfied [with U = struct second]
|
||||||
|
185 | [[nodiscard]] constexpr QuantityOf<quantity_spec> auto in(U) const
|
||||||
|
| ^
|
||||||
|
note: because 'detail::UnitCompatibleWith<si::second, unit, quantity_spec>' evaluated to false
|
||||||
|
183 | template<detail::UnitCompatibleWith<unit, quantity_spec> U>
|
||||||
|
| ^
|
||||||
|
note: because '!AssociatedUnit<si::second>' evaluated to false
|
||||||
|
207 | (!AssociatedUnit<U> || UnitOf<U, QS>)&&(detail::have_same_canonical_reference_unit(U{}, U2));
|
||||||
|
| ^
|
||||||
|
note: and 'UnitOf<si::second, kind_of_<derived_quantity_spec<power<isq::length, 2>, per<isq::time> > >{}>' evaluated to false
|
||||||
|
207 | (!AssociatedUnit<U> || UnitOf<U, QS>)&&(detail::have_same_canonical_reference_unit(U{}, U2));
|
||||||
|
| ^
|
||||||
|
note: because 'implicitly_convertible(get_quantity_spec(si::second{}), kind_of_<derived_quantity_spec<power<length, 2>, per<time> > >{})' evaluated to false
|
||||||
|
187 | implicitly_convertible(get_quantity_spec(U{}), QS) &&
|
||||||
|
| ^
|
||||||
|
1 error generated.
|
||||||
|
Compiler returned: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
The above error messages were stripped a bit of the additional information (file name,
|
||||||
|
namespace name, nested curlies) to provide better readability.
|
||||||
|
Reference in New Issue
Block a user