Files
mp-units/docs/framework/conversions_and_casting.rst

192 lines
7.3 KiB
ReStructuredText
Raw Normal View History

2020-03-09 18:55:41 +01:00
.. namespace:: units
Conversions and Casting
=======================
2020-03-18 11:21:54 +01:00
Programmers who are not full-time programming language geeks **hate lack of
implicit conversions** that they consider reasonable. Violate their expectations
for reasons they consider hypothetical problems and you get disuse. In type
terms, I usually **translate "reasonable" to "not narrowing" aka "not loosing
information."** That can reasonably easily be explained to non-language-experts.
2020-03-09 18:55:41 +01:00
2020-03-18 11:21:54 +01:00
-- *Bjarne Stroustrup*
2020-03-09 18:55:41 +01:00
2020-03-18 11:21:54 +01:00
The library tries to follow the above principle and at the same time is also consistent
with conversions of ``std::chrono::duration``.
2020-03-09 18:55:41 +01:00
2020-03-26 17:09:26 +01:00
No Conversions
2020-03-18 11:21:54 +01:00
--------------
No conversions (either implicit or explicit) are available across quantities of
different dimensions::
si::length<si::metre> d1 = 1_q_s; // Compile-time error
si::length<si::metre> d2(1_q_s); // Compile-time error
auto d3 = quantity_cast<si::metre>(1_q_s); // Compile-time error
2020-03-18 11:21:54 +01:00
Implicit
--------
Implicit conversions are allowed only across quantities of the same dimension:
- for integral types with ratios that guarantee no precision loss::
si::length<si::metre, int> d1 = 1_q_km + 1_q_m; // OK
si::length<si::millimetre, int> d2 = 1_q_km + 1_q_m; // OK
si::length<si::kilometre, int> d3 = 1_q_km + 1_q_m; // Compile-time error
si::length<si::kilometre, int> d4(1_q_km + 1_q_m); // Compile-time error
si::length<si::metre, int> d5 = 1_q_m + 1_q_ft; // Compile-time error
si::length<si::metre, int> d6(1_q_m + 1_q_ft); // Compile-time error
2020-03-18 11:21:54 +01:00
- from an integral to a floating-point representation even in case of a truncating
ratio::
si::length<si::kilometre, double> d7 = 1_q_km + 1_q_m; // OK
si::length<si::metre, double> d8 = 1_q_m + 1_q_ft; // OK
2020-03-18 11:21:54 +01:00
- when both sides use a floating-point representation::
si::length<si::metre, int> d9 = 1.23_q_m; // Compile-time error
si::length<si::metre, double> d10 = 1.23_q_m; // OK
2020-03-18 11:21:54 +01:00
Explicit
--------
2021-02-16 16:19:57 +01:00
Explicit conversions are available with the `quantity_cast`, `quantity_point_cast`,
`quantity_kind_cast`, and `quantity_point_kind_cast` function templates.
They are especially useful to force a truncating conversion across quantities of the same
2020-03-18 11:21:54 +01:00
dimension for integral representation types and ratios that may cause precision loss::
si::length<si::kilometre, int> d1 = quantity_cast<kilometre>(1km + 1m); // OK
si::length<si::kilometre, int> d2 = quantity_cast<kilometre>(1s); // Error
.. seealso::
2021-02-16 16:19:57 +01:00
Explicit casts are also really useful when working with legacy interfaces.
More information on this subject can be found in the
:ref:`use_cases/legacy_interfaces:Working with Legacy Interfaces` chapter.
2020-03-18 11:21:54 +01:00
Quantity Cast Overloads
^^^^^^^^^^^^^^^^^^^^^^^
`quantity_cast` comes with several overloads:
.. code-block::
:linenos:
std::cout << "Distance: " << quantity_cast<si::length<si::metre, int>>(d) << '\n';
std::cout << "Distance: " << quantity_cast<si::dim_length>(d) << '\n';
2020-03-18 11:21:54 +01:00
std::cout << "Distance: " << quantity_cast<si::metre>(d) << '\n';
std::cout << "Distance: " << quantity_cast<int>(d) << '\n';
std::cout << "Distance: " << quantity_cast<si::dim_length, si::metre>(d) << '\n';
2020-03-18 11:21:54 +01:00
`quantity_cast` in line #1 takes a specific target `quantity` type to which an explicit
cast should be performed. This option will change multiple quantity properties at once
(unit, representation, etc). However, it is also possible to force only some properties at
2020-03-18 11:21:54 +01:00
once and leave the rest intact:
- line #2 forces only a specific destination dimension type,
- line #3 forces only a specific destination unit type,
- line #4 sets only a representation type to the type provided by the user,
- line #5 forces both a specific dimension and a unit while preserving the original
representation type.
2020-03-18 11:21:54 +01:00
`quantity_point_cast` takes anything that works for `quantity_point`
or a specific target `quantity_point`::
std::cout << "Point: " << quantity_point_cast<decltype(quantity_point{0_q_m})>(d) << '\n';
For a single explicit template argument,
`quantity_point_kind_cast` is a superset of `quantity_kind_cast`,
which is also a superset of `quantity_cast`.
For the (dimension, unit) pair of explicit arguments case of `quantity_cast`,
`quantity_point_kind_cast` and `quantity_kind_cast`
accept a `PointKind` and `Kind` instead of a `Dimension`, respectively.
2020-03-18 11:21:54 +01:00
.. seealso::
2021-02-16 16:19:57 +01:00
For more information on conversion and casting and on how to extend the above
"integral" vs "floating-point" logic please refer to the
:ref:`use_cases/custom_representation_types:Using Custom Representation Types` chapter.
Implicit conversions of dimensionless quantities
------------------------------------------------
2021-02-16 16:19:57 +01:00
As noted in the :ref:`framework/quantities:Dimensionless Quantities` chapter,
:term:`quantity of dimension one` is somehow special but still obey most of the rules defined
for quantities. However, as they represent numbers it would be highly uncomfortable to every
time type::
const auto d1 = 10_q_km;
const auto d2 = 3_q_km;
if(d1 / d2 > dimensionless<one>(2)) {
// ...
}
or::
const auto fill_time_left = (box.height / box.fill_level(measured_mass) -
dimensionless<one>(1)) * fill_time;
This is why it was decided to allow the ``dimensionless<one>`` quantity of any
representation type to be implicitly constructible from this representation type.
With that the above examples can be rewritten as follows::
const auto d1 = 10_q_km;
const auto d2 = 3_q_km;
if(d1 / d2 > 2) {
// ...
}
and::
const auto fill_time_left = (box.height / box.fill_level(measured_mass) - 1) * fill_time;
The above is true only for dimensionless quantities of `one` unit. If our quantity have a unit with
ratio different than ``1`` the implicit conversion will not happen. This is to prevent cases were the code
could be ambiguous. For example::
Dimensionless auto foo(Length auto d1, Length auto d2)
{
return d1 / d2 + 1;
}
As long as we can reason about what such code means for ``foo(10_q_km, 2_q_km)`` it is not that obvious
at all in the case of ``foo(10_q_cm, 2_q_ft)``. To make such code to compile for every case we have to
either change the type of the resulting unit to the one having ``ratio(1)`` (:term:`coherent derived unit`)::
Dimensionless auto foo(Length auto d1, Length auto d2)
{
return quantity_cast<one>(d1 / d2) + 1;
}
or to explicitly state what is the unit of our dimensionless value, e.g. `one`, `percent`, etc::
Dimensionless auto foo(Length auto d1, Length auto d2)
{
return d1 / d2 + dimensionless<one>(1);
}
There is one more important point to note here. As the the dimensionless quantity is more than just
a number, it is never implicitly converted back to the representation type. This means that the following
code will not compile::
auto v = std::exp(10_q_m / 5_q_m);
To make it compile fine we have to either explicitly get the value stored in the quantity::
auto v = std::exp(quantity_cast<one>(10_q_m / 5_q_m).number());
or use a mathematical wrapper function from `units` namespace::
auto v = units::exp(10_q_m / 5_q_m);
.. important::
Always remember to explicitly cast the quantity to the destination unit with `quantity_cast` before
calling `quantity::number()`!