mp-units 2.4.0 released!¶
A new product version can be obtained from GitHub and Conan.
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.
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 ( breaking change ).
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 ( breaking change ).
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 ofJ
),V A
should be used for apparent power (instead ofW
).
This is why, starting from this release units like J
, N m
, and kg m²/s²
will not compare equal
( breaking change ). However, they are deemed equivalent:
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 ( breaking change ).
From now on, we will provide the following:
text_encoding::utf8
,symbol_text<N, M>::utf8()
, andU
formatting option,text_encoding::portable
,symbol_text<N, M>::portable()
, andP
formatting option.
Note
The old identifiers and formatting options are now deprecated and will be removed in future releases.
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
( breaking change ).
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:
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:
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:
prints:
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:
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 ( breaking change ):
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:
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 ( breaking change ).
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.
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);
}
};
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. ( breaking change ).
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 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.