diff --git a/docs/blog/posts/2.3.0-released.md b/docs/blog/posts/2.3.0-released.md index ab1e87d7..4ce6bd87 100644 --- a/docs/blog/posts/2.3.0-released.md +++ b/docs/blog/posts/2.3.0-released.md @@ -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). +## 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(q)` +non-member function while a change of unit was supported by all `value_cast(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 q) { return q.in(km); } +auto g(quantity q) { return value_cast(q); } + +// dependent name +auto h(QuantityOf auto q) { return q.template in(km); } +auto i(QuantityOf auto q) { return value_cast(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()` | | +| No | `T` | `u` | `x.in(u)` | | +| Yes | Same | `u` | `x.force_in(u)` | `value_cast(x)` | +| Yes | `T` | Same | `x.force_in()` | `value_cast(x)` | +| Yes | `T` | `u` | `x.force_in(u)` | `value_cast(x)` or `value_cast(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(28.0); @@ -104,7 +169,7 @@ to explicitly create a: quantity_point Temperature(delta(28.0)); ``` -- `quantity` (an incorrect abstraction in this example) with: +- a `quantity` (an incorrect abstraction in this example) with: ```cpp quantity Temperature = delta(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 { + static constexpr auto reference = si::second; + static constexpr auto point_origin = default_point_origin(reference); + using rep = decltype(Timestamp::seconds); + + static constexpr convert_implicitly to_numerical_value(Timestamp ts) + { + return ts.seconds; + } + + static constexpr convert_explicitly from_numerical_value(rep v) + { + return Timestamp(v); + } + }; + ``` + +=== "Before" + + ```cpp + template<> + struct mp_units::quantity_point_like_traits { + static constexpr auto reference = si::second; + static constexpr auto point_origin = default_point_origin(reference); + using rep = decltype(Timestamp::seconds); + + static constexpr convert_implicitly> to_quantity(Timestamp ts) + { + return ts.seconds * si::second; + } + + static constexpr convert_explicitly from_quantity(quantity q) + { + return Timestamp(q.numerical_value_ref_in(si::second)); + } + }; + ``` + +## `mag` + +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` 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 / 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 inc(quantity q) { return q + 1; } + void legacy(double) { /* ... */ } + + if (auto q = inc(42); q != 0) + legacy(static_cast(q)); + ``` + +=== "Before" + + ```cpp + quantity inc(quantity 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 auto in(ToU) const + | ^ + note: because 'detail::UnitCompatibleWith' evaluated to false + 219 | template ToU> + | ^ + note: because '!AssociatedUnit' evaluated to false + 164 | (!AssociatedUnit || UnitOf) && detail::UnitConvertibleTo; + | ^ + note: and 'UnitOf, per > >{}>' evaluated to false + 164 | (!AssociatedUnit || UnitOf) && detail::UnitConvertibleTo; + | ^ + note: because 'detail::QuantitySpecConvertibleTo, per > >{}>' evaluated to false + 141 | detail::QuantitySpecConvertibleTo && + | ^ + note: because 'implicitly_convertible(kind_of_{}, kind_of_, per > >{})' 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 auto in(U) const + | ^ + note: because 'detail::UnitCompatibleWith' evaluated to false + 183 | template U> + | ^ + note: because '!AssociatedUnit' evaluated to false + 207 | (!AssociatedUnit || UnitOf)&&(detail::have_same_canonical_reference_unit(U{}, U2)); + | ^ + note: and 'UnitOf, per > >{}>' evaluated to false + 207 | (!AssociatedUnit || UnitOf)&&(detail::have_same_canonical_reference_unit(U{}, U2)); + | ^ + note: because 'implicitly_convertible(get_quantity_spec(si::second{}), kind_of_, per