.. namespace:: units Conversions and Casting ======================= 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. -- *Bjarne Stroustrup* The library tries to follow the above principle and at the same time is also consistent with conversions of ``std::chrono::duration``. No Conversions -------------- No conversions (either implicit or explicit) are available across quantities of different dimensions:: si::length d1 = 1_q_s; // Compile-time error si::length d2(1_q_s); // Compile-time error auto d3 = quantity_cast(1_q_s); // Compile-time error Implicit -------- Implicit conversions are allowed only across quantities of the same dimension: - for integral types with ratios that guarantee no precision loss:: si::length d1 = 1_q_km + 1_q_m; // OK si::length d2 = 1_q_km + 1_q_m; // OK si::length d3 = 1_q_km + 1_q_m; // Compile-time error si::length d4(1_q_km + 1_q_m); // Compile-time error si::length d5 = 1_q_m + 1_q_ft; // Compile-time error si::length d6(1_q_m + 1_q_ft); // Compile-time error - from an integral to a floating-point representation even in case of a truncating ratio:: si::length d7 = 1_q_km + 1_q_m; // OK si::length d8 = 1_q_m + 1_q_ft; // OK - when both sides use a floating-point representation:: si::length d9 = 1.23_q_m; // Compile-time error si::length d10 = 1.23_q_m; // OK Explicit -------- 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 dimension for integral representation types and ratios that may cause precision loss:: si::length d1 = quantity_cast(1km + 1m); // OK si::length d2 = quantity_cast(1s); // Error .. seealso:: 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. Quantity Cast Overloads ^^^^^^^^^^^^^^^^^^^^^^^ `quantity_cast` comes with several overloads: .. code-block:: :linenos: std::cout << "Distance: " << quantity_cast>(d) << '\n'; std::cout << "Distance: " << quantity_cast(d) << '\n'; std::cout << "Distance: " << quantity_cast(d) << '\n'; std::cout << "Distance: " << quantity_cast(d) << '\n'; std::cout << "Distance: " << quantity_cast(d) << '\n'; `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 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. `quantity_point_cast` takes anything that works for `quantity_point` or a specific target `quantity_point`:: std::cout << "Point: " << quantity_point_cast(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. .. seealso:: 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 ------------------------------------------------ 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(2)) { // ... } or:: const auto fill_time_left = (box.height / box.fill_level(measured_mass) - dimensionless(1)) * fill_time; This is why it was decided to allow the ``dimensionless`` 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(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(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(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()`!