2024-06-21 08:21:35 +02:00
|
|
|
|
---
|
2024-09-26 20:48:20 +02:00
|
|
|
|
date: 2024-09-27
|
2024-06-21 08:21:35 +02:00
|
|
|
|
authors:
|
|
|
|
|
|
- mpusz
|
|
|
|
|
|
categories:
|
|
|
|
|
|
- Releases
|
2024-09-30 14:47:28 +02:00
|
|
|
|
comments: true
|
2024-06-21 08:21:35 +02:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# mp-units 2.3.0 released!
|
|
|
|
|
|
|
|
|
|
|
|
**A new product version can be obtained from
|
|
|
|
|
|
[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).**
|
|
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
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).
|
2024-06-21 08:21:35 +02:00
|
|
|
|
|
|
|
|
|
|
<!-- more -->
|
|
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
## 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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-09-30 19:11:10 +02:00
|
|
|
|
## MSVC compiler support
|
|
|
|
|
|
|
|
|
|
|
|
The MSVC compiler has the most bugs in the C++20 support from all our compilers. However,
|
|
|
|
|
|
with this release, we could apply many workarounds to the library's code to make it work. 🎉
|
|
|
|
|
|
|
|
|
|
|
|
Please note those workarounds were only applied to the library's code and not to our unit tests
|
|
|
|
|
|
and examples. Trying to build the entire project on MSVC will inevitably fail to compile unless
|
|
|
|
|
|
the MSVC bugs are resolved. MSVC developers still have some work to do.
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
## 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)` |
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-06-21 08:21:35 +02:00
|
|
|
|
## Quantity reference specifiers
|
|
|
|
|
|
|
|
|
|
|
|
The features described in this chapter directly solve an issue raised on
|
|
|
|
|
|
[std-proposals reflector](https://lists.isocpp.org/std-proposals/2024/06/10118.php). As it was
|
|
|
|
|
|
reported, the code below may look correct, but it provides an invalid result:
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
quantity Volume = 1.0 * m3;
|
|
|
|
|
|
quantity Temperature = 28.0 * deg_C;
|
|
|
|
|
|
quantity n_ = 0.04401 * kg / mol;
|
|
|
|
|
|
quantity R_boltzman = 8.314 * N * m / (K * mol);
|
|
|
|
|
|
quantity mass = 40.0 * kg;
|
|
|
|
|
|
quantity Pressure = R_boltzman * Temperature.in(K) * mass / n_ / Volume;
|
|
|
|
|
|
std::cout << Pressure << "\n";
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The problem is related to the accidental usage of a `quantity` rather than `quantity_point` for
|
|
|
|
|
|
`Temperature`. This means that after conversion to kelvins, we will get `28 K` instead of
|
2024-06-30 12:03:24 +02:00
|
|
|
|
the expected `301.15 K`, corrupting all further calculations.
|
2024-06-21 08:21:35 +02:00
|
|
|
|
|
|
|
|
|
|
A correct code should use a `quantity_point`:
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
quantity_point Temperature(28.0 * deg_C);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
This might be an obvious thing for domain experts, but new users of the library may not be aware
|
|
|
|
|
|
of the affine space abstractions and how they influence temperature handling.
|
|
|
|
|
|
|
2024-06-30 12:03:24 +02:00
|
|
|
|
After a lengthy discussion on handling such scenarios, we decided to:
|
2024-06-21 08:21:35 +02:00
|
|
|
|
|
2024-09-26 20:48:20 +02:00
|
|
|
|
- make the above code deprecated (:boom: **breaking change** :boom:),
|
2024-06-30 12:03:24 +02:00
|
|
|
|
- provide an alternative way to create a `quantity` with the `delta` quantity construction helper.
|
2024-06-21 08:21:35 +02:00
|
|
|
|
|
|
|
|
|
|
Here are the main points of this new design:
|
|
|
|
|
|
|
2024-06-30 12:03:24 +02:00
|
|
|
|
1. All references/units that specify point origin in their definition (i.e., `si::kelvin`,
|
|
|
|
|
|
`si::degree_Celsius`, and `usc::degree_Fahrenheit`) are excluded from the multiply syntax
|
|
|
|
|
|
(:boom: **breaking change** :boom:).
|
|
|
|
|
|
2. A new `delta` quantity construction helper is introduced:
|
2024-06-21 08:21:35 +02:00
|
|
|
|
|
2024-06-30 12:03:24 +02:00
|
|
|
|
- `delta<m>(42)` results with a `quantity<si::metre, int>`,
|
|
|
|
|
|
- `delta<deg_C>(5)` results with a `quantity<si::deg_C, int>`.
|
2024-06-21 08:21:35 +02:00
|
|
|
|
|
2024-06-30 12:03:24 +02:00
|
|
|
|
3. A new `absolute` quantity point construction helper is introduced:
|
2024-06-21 08:21:35 +02:00
|
|
|
|
|
2024-06-30 12:03:24 +02:00
|
|
|
|
- `absolute<m>(42)` results with a `quantity_point<si::metre, zeroth_point_origin<kind_of<isq::length>>{}, int>`,
|
|
|
|
|
|
- `absolute<deg_C>(5)` results with a `quantity<si::metre, si::ice_point, int>`.
|
2024-06-21 08:21:35 +02:00
|
|
|
|
|
2024-06-30 12:03:24 +02:00
|
|
|
|
!!! info
|
2024-06-21 08:21:35 +02:00
|
|
|
|
|
2024-06-30 12:03:24 +02:00
|
|
|
|
Please note that `si::kelvin` is also excluded from the multiply syntax to prevent the
|
|
|
|
|
|
following surprising issues:
|
2024-06-21 08:21:35 +02:00
|
|
|
|
|
2024-06-30 12:03:24 +02:00
|
|
|
|
=== "Now"
|
2024-06-21 08:21:35 +02:00
|
|
|
|
|
2024-06-30 12:03:24 +02:00
|
|
|
|
```cpp
|
|
|
|
|
|
quantity q = delta<K>(300);
|
|
|
|
|
|
quantity_point qp = absolute<K>(300);
|
|
|
|
|
|
static_assert(q.in(deg_C) != qp.in(deg_C).quantity_from_zero());
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
=== "Before"
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
quantity q(300 * K);
|
|
|
|
|
|
quantity_point qp(300 * K);
|
|
|
|
|
|
static_assert(q.in(deg_C) != qp.in(deg_C).quantity_from_zero());
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
We believe that the code enforced with new utilities makes it much easier to understand what
|
|
|
|
|
|
happens here.
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2024-09-24 09:03:06 +02:00
|
|
|
|
to create explicitly:
|
2024-06-30 12:03:24 +02:00
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
- a `quantity_point` (the intended abstraction in this example) with any of the below syntaxes:
|
2024-06-30 12:03:24 +02:00
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
quantity_point Temperature = absolute<deg_C>(28.0);
|
|
|
|
|
|
auto Temperature = absolute<deg_C>(28.0);
|
|
|
|
|
|
quantity_point Temperature(delta<deg_C>(28.0));
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
- a `quantity` (an incorrect abstraction in this example) with:
|
2024-06-30 12:03:24 +02:00
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
quantity Temperature = delta<deg_C>(28.0);
|
|
|
|
|
|
auto Temperature = delta<deg_C>(28.0);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Thanks to the new design, we can immediately see what happens here and why the result might be
|
|
|
|
|
|
incorrect in the second case.
|
2024-09-24 09:03:06 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## `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);
|
2024-09-26 20:48:20 +02:00
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
static constexpr convert_implicitly<rep> to_numerical_value(Timestamp ts)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ts.seconds;
|
|
|
|
|
|
}
|
2024-09-26 20:48:20 +02:00
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
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);
|
2024-09-26 20:48:20 +02:00
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
static constexpr convert_implicitly<quantity<reference, rep>> to_quantity(Timestamp ts)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ts.seconds * si::second;
|
|
|
|
|
|
}
|
2024-09-26 20:48:20 +02:00
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
static constexpr convert_explicitly<Timestamp> from_quantity(quantity<reference, rep> q)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Timestamp(q.numerical_value_ref_in(si::second));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2024-09-26 20:48:20 +02:00
|
|
|
|
!!! note
|
|
|
|
|
|
|
|
|
|
|
|
The old behavior is deprecated and will be removed in future releases.
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
## `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;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2024-09-26 20:48:20 +02:00
|
|
|
|
!!! note
|
|
|
|
|
|
|
|
|
|
|
|
The old `mag_pi` helper is marked as deprecated and will be removed in future releases.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Common units
|
|
|
|
|
|
|
|
|
|
|
|
Adding or subtracting two quantities of different units will force the library to find a common
|
|
|
|
|
|
unit for those. This is to prevent data truncation. For the cases when one of the units is an
|
|
|
|
|
|
integral multiple of the another, the resulting quantity will use a "smaller" one in its result.
|
|
|
|
|
|
For example:
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
static_assert((1 * kg + 1 * g).unit == g);
|
|
|
|
|
|
static_assert((1 * km + 1 * mm).unit == mm);
|
|
|
|
|
|
static_assert((1 * yd + 1 * mi).unit == yd);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
However, in many cases an arithmetic on quantities of different units will result in a yet another
|
|
|
|
|
|
unit. This happens when none of the source units is an integral multiple of another. In such cases,
|
|
|
|
|
|
the library returns a special type that denotes that we are dealing with a common unit of such
|
|
|
|
|
|
an equation.
|
|
|
|
|
|
|
|
|
|
|
|
Previously we returned a scaled unit calculated against our arbitrarily appointed reference unit.
|
|
|
|
|
|
This resulted often in a long and messy type exposing the prime-factorized magnitude of the unit
|
|
|
|
|
|
(implementation detail). In this release, we introduced a new `common_unit` wrapper for such cases:
|
|
|
|
|
|
|
|
|
|
|
|
=== "Now"
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
quantity q = 1 * km + 1 * mi; // quantity<common_unit<international::mile, si::kilo_<si::metre>>{}, int>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
=== "Before"
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
quantity q = 1 * km + 1 * mi; // quantity<scaled_unit<magnitude<power_v<2, 3>{}, power_v<5, -3>{}>{}, si::metre>{}, int>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
!!! note
|
|
|
|
|
|
|
|
|
|
|
|
A user should never explicitly instantiate a `common_unit` class template. The library's
|
|
|
|
|
|
framework will do it based on the provided quantity equation.
|
|
|
|
|
|
|
|
|
|
|
|
Such units need special printing rules for their symbols. As they represent a minimum set of common
|
|
|
|
|
|
units resulting from the addition or subtraction of multiple quantities, from this release, we
|
|
|
|
|
|
print all of them as a scaled version of the source unit. Previously we were printing them relative
|
|
|
|
|
|
to some arbitrary reference unit (implementation detail) that often was not spelled by the user at
|
|
|
|
|
|
all in the source code. 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 ([1/25146] mi = [1/15625] km)
|
|
|
|
|
|
108167 ([1/50292] mi = [1/57875] nmi)
|
|
|
|
|
|
23 ([1/5] km/h = [1/18] m/s)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
=== "Before"
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
40771 [8/125] m
|
|
|
|
|
|
108167 [4/125] m
|
|
|
|
|
|
23 [1/18] m/s
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Thanks to the above, it might be easier for the user to reason about the magnitude of the resulting
|
|
|
|
|
|
unit and its impact on the value stored in the quantity.
|
|
|
|
|
|
|
|
|
|
|
|
!!! info
|
|
|
|
|
|
|
|
|
|
|
|
In order to provide `common_unit` strong type unit wrapper we had to rename all the
|
|
|
|
|
|
`common_XXX()` functions to `get_common_XXX()` (:boom: **breaking change** :boom:).
|
|
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
|
|
|
|
|
|
## 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) { /* ... */ }
|
2024-09-26 20:48:20 +02:00
|
|
|
|
|
2024-09-24 09:03:06 +02:00
|
|
|
|
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
|
2024-09-26 20:48:20 +02:00
|
|
|
|
corresponding namespace from `iec80000` to `iec` (:boom: **breaking change** :boom:). This involves
|
|
|
|
|
|
renaming of a defining header file and of the namespace it provides.
|
2024-09-24 09:03:06 +02:00
|
|
|
|
|
2024-09-30 18:57:42 +02:00
|
|
|
|
With this change it should be easier to type the namespace name. This name is also 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`).
|
2024-09-26 20:48:20 +02:00
|
|
|
|
|
|
|
|
|
|
!!! note
|
|
|
|
|
|
|
|
|
|
|
|
The old `iec80000` namespace in _iec8000.h_ is marked as deprecated and will be removed in
|
|
|
|
|
|
future releases.
|
2024-09-24 09:03:06 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 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.
|