mirror of
https://github.com/mpusz/mp-units.git
synced 2025-07-30 02:17:16 +02:00
docs: 2.3.0 release announcement updated
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
---
|
||||
draft: true
|
||||
date: 2024-06-15
|
||||
date: 2024-09-24
|
||||
authors:
|
||||
- mpusz
|
||||
categories:
|
||||
@ -13,12 +13,77 @@ categories:
|
||||
[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).**
|
||||
|
||||
This release fine-tunes many key features of the library. This post describes those and a few
|
||||
other smaller interesting improvements, while a much longer list of the most significant changes
|
||||
introduced by the new version can be found in our [Release Notes](../../release_notes.md#2.3.0).
|
||||
This release fine-tunes many key features of the library. This post describes the most interesting
|
||||
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.3.0).
|
||||
|
||||
<!-- 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
|
||||
|
||||
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:
|
||||
|
||||
- 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.
|
||||
|
||||
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.
|
||||
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
|
||||
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` (an incorrect abstraction in this example) with:
|
||||
- a `quantity` (an incorrect abstraction in this example) with:
|
||||
|
||||
```cpp
|
||||
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
|
||||
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