From d8d3dcf33912ea5b90bd89646468f7dd1bde3aef Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Thu, 26 Mar 2020 16:58:37 +0100 Subject: [PATCH] Custom Represetnation Types chapter done --- .../use_cases/custom_representation_types.rst | 252 ++++++++++++++---- 1 file changed, 206 insertions(+), 46 deletions(-) diff --git a/docs/use_cases/custom_representation_types.rst b/docs/use_cases/custom_representation_types.rst index 290893a7..f5d39476 100644 --- a/docs/use_cases/custom_representation_types.rst +++ b/docs/use_cases/custom_representation_types.rst @@ -15,76 +15,250 @@ A `Scalar` concept To support a minimum set of `quantity` operations all custom representation types have to satisfy at least the `Scalar` concept. Which means that they: -- cannot be quantities or be wrappers over the `quantity` type - (i.e. ``std::optional>``), +- cannot be quantities by themselves, +- cannot be wrappers over the `quantity` type (i.e. ``std::optional>``), - have to be regular types (e.g. they have to provide equality operators) -- must be constructible from a fundamental integral type (to +- if they are constructible from a fundamental integral type, they have to provide multiplication + and division operators for their types, +- otherwise, their values need to support the multiplication or division by the values of the + ``std::int64_t`` type which is the type used to store elements of a `ratio`. With the above we will be able to construct quantities, convert between the units of the same -dimension and compare them for equality. To provide additional `quantity` operations the -custom representation type have to satisfy more requirements. +dimension, and compare them for equality. -Additional requirements ------------------------ +The Simplest Custom Representation Type +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. important:: +The simplest representation type that fullfills the above requirements can look as follows:: - The requirements described in the chapter are optional in a meaning that if someone does - not plan to use a specific quantity's operation his/her custom representation type can - ignore (not implement/satisfy) the requirements for it. + class my_rep { + int value_{}; + public: + my_rep() = default; + constexpr my_rep(int v) : value_(v) {} + [[nodiscard]] friend constexpr bool operator==(my_rep lhs, my_rep rhs) = default; + [[nodiscard]] friend constexpr my_rep operator*(my_rep lhs, my_rep rhs) + { + return my_rep(lhs.value_ * rhs.value_); + } + [[nodiscard]] friend constexpr my_rep operator/(my_rep lhs, my_rep rhs) + { + return my_rep(lhs.value_ / rhs.value_); + } + }; + +Now we can put ``my_rep`` as the last parameter of the `quantity` class template and the following +code will work just fine:: + + static_assert(si::length(2'000) == si::length(2)); Construction of Quantities with Custom Representation Types ------------------------------------------------------------ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Let's assume two types: +Assuming that our custom representation type is constructible from a fundamental integral type, +let's assume two types: .. code-block:: - :emphasize-lines: 6, 15 + :emphasize-lines: 5, 13 - template class impl { - T value_{}; + int value_{}; public: impl() = default; - constexpr impl(T v): value_(v) {} + constexpr impl(int v): value_(v) {} // the rest of the representation type implementation }; - template class expl { - T value_{}; + int value_{}; public: expl() = default; - constexpr explicit expl(T v): value_(v) {} + constexpr explicit expl(int v): value_(v) {} // the rest of the representation type implementation }; The difference between the above types is that ``impl`` class is implicitly constructible -from values of type ``T`` while ``expl`` is not. To create quantities using those types as +from values of type ``int`` while ``expl`` is not. To create quantities using those as their representation types we have to obey similar rules:: - si::length> d1(123); // OK - si::length> d2(123); // Compile-time error - si::length> d3(expl(123)); // OK + si::length d1(123); // OK + si::length d2(123); // Compile-time error + si::length d3(expl(123)); // OK This also applies when we want to create a quantity with a custom representation type from a regular quantity value:: Length auto d = 123q_m; - si::length> d1(d); // OK - si::length> d2(d); // Compile-time error - si::length> d3(quantity_cast>(d)); // OK + si::length d1(d); // OK + si::length d2(d); // Compile-time error + si::length d3(quantity_cast(d)); // OK + +The only difference here is that in this case we have to explicitly cast the `quantity` with +`quantity_cast` overload that scopes only on changing the representation type. + +Additional Requirements +----------------------- + +As noted in the previous chapter, the `Scalar` concept guarantees us the possibility to +construct quantities, convert between the units of the same dimension, and compare them +for equality. To provide additional `quantity` operations the custom representation type +have to satisfy more requirements. + +.. important:: + + The requirements described in this chapter are optional in a meaning that if someone does + not plan to use a specific quantity's operation his/her custom representation type can + ignore (not implement/satisfy) the requirements for it. + + +Relational Quantity Operators +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In case we want to to compare the values of `quantity` type not only for equality but +also for ordering, we have to provide a corresponding operators to our ``my_rep`` class. +With C++20 it is really easy to do:: + + class my_rep { + public: + [[nodiscard]] friend constexpr auto operator<=>(my_rep lhs, my_rep rhs) = default; + + // ... + }; + +With the above the following code will compile fine:: + + static_assert(si::length(2'000) < si::length(3)); + + +Arithmetic Quantity Operators +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In case we plan to perform arithmetic operations on our `quantity` type we have to provide +at least the following:: + + class my_rep { + public: + [[nodiscard]] friend constexpr my_rep operator+(my_rep lhs, my_rep rhs) + { + return my_rep(lhs.value_ + rhs.value_); + } + [[nodiscard]] friend constexpr my_rep operator-(my_rep lhs, my_rep rhs) + { + return my_rep(lhs.value_ - rhs.value_); + } + + // ... + }; + +Thanks to it the following code will run as expected:: + + static_assert(si::length(2'000) + si::length(1) == + si::length(3)); + +Of course, the above operators are the smallest possible set to provide support for basic +arithmetic operations. In case the user wants to use faster or more sofisticated operators +the following ones can be provided:: + + class my_rep { + public: + [[nodiscard]] constexpr my_rep operator+() const; + [[nodiscard]] constexpr my_rep operator-() const; + + constexpr my_rep& operator++(); + constexpr my_rep operator++(int); + constexpr my_rep& operator--(); + constexpr my_rep operator--(int); + constexpr my_rep& operator+=(my_rep q); + constexpr my_rep& operator-=(my_rep q); + constexpr my_rep& operator*=(my_rep rhs); + constexpr my_rep& operator/=(my_rep rhs); + constexpr my_rep& operator%=(my_rep rhs); + + [[nodiscard]] friend constexpr my_rep operator%(my_rep lhs, my_rep rhs); + + // ... + }; + +Each of the above operators will enable a respective operator in the `quantity` +type. + + +Customization Points +-------------------- + +Up to now we were enabling new functionalities by adding new operations to the custom representation +type. However, we can also enable more operations and customize the engine behavior through a few +customization points. + +`quantity_value` +^^^^^^^^^^^^^^^^ + +The `quantity` class template has a few static member functions: `quantity::zero`, `quantity::one`, +`quantity::min`, and `quantity::max`. Those return the respective quantity values for a specific +representation type. The default implementation is provided through the `quantity_values` class +template:: + + template + struct quantity_values { + static constexpr Rep zero() noexcept { return Rep(0); } + static constexpr Rep one() noexcept { return Rep(1); } + static constexpr Rep min() noexcept { return std::numeric_limits::lowest(); } + static constexpr Rep max() noexcept { return std::numeric_limits::max(); } + }; + +The user can provide an explicit/partial class template specialization for his/her custom +representation type and provide values that should be returned by the respective `quantity` +operations. + +`treat_as_floating_point` +^^^^^^^^^^^^^^^^^^^^^^^^^ + +In the :ref:`Conversions and Casting` chapter we learned that the conversions provided by the +library's framework treat floating-point representation types differently than the integral +ones. This behavior can also be extended to the custom representation types with +`treat_as_floating_point` customization point which default definition is:: + + template + inline constexpr bool treat_as_floating_point = std::is_floating_point_v; + +If our representation type should have a floating-point semantics or if it is a class +template, in which case we may not know exactly what is the final representation type, +we can specialize this variable template as follows:: + + namespace custom { + + template + class my_rep { + T value_{}; + public: + // ... + }; + + } // namespace custom + + namespace units { + + template + inline constexpr bool treat_as_floating_point> = std::is_floating_point_v; + + } // namespace units + +.. important:: + + Please remember that by the C++ language rules all template specializations have to be put + always in the same namespace as the primary template definition. Conversions of Quantities with Custom Representation Types ---------------------------------------------------------- In case we want to mix quantities of our Custom Representation Type with the quantities using -fundamental arithmetic types as their representation we have to provide conversion operators. +fundamental arithmetic types as their representation we have to provide conversion operators +in our representation type. -Again let's assume two types but this time let's scope on conversion operators rather +Again let's assume two types, but this time let's scope on conversion operators rather than on constructors: .. code-block:: @@ -114,7 +288,7 @@ If we have instances of the above types we can construct quantities in the follo si::length d2(v_expl); // Compile-time error si::length d3(int(v_expl); // OK -Similarly, when we have quantities of above types we can create quantities of other +Similarly, when we have quantities of the above types we can create quantities of other representation types with:: si::length> d_impl(1); @@ -124,21 +298,7 @@ representation types with:: si::length d3(quantity_cast(d_expl)); // OK -Tricky cases ------------- - - - -Customization points --------------------- - -treat_as_floating_point - -quantity_value - - - .. seealso:: - For more examples of custom representation types usage please refer to :ref:`measurement` - example. + For more examples of custom representation types usage please refer to the + :ref:`Linear Algebra of Quantities` chapter and :ref:`measurement` example.