forked from mpusz/mp-units
Custom Represetnation Types chapter done
This commit is contained in:
@@ -15,76 +15,250 @@ A `Scalar` concept
|
|||||||
To support a minimum set of `quantity` operations all custom representation types have to
|
To support a minimum set of `quantity` operations all custom representation types have to
|
||||||
satisfy at least the `Scalar` concept. Which means that they:
|
satisfy at least the `Scalar` concept. Which means that they:
|
||||||
|
|
||||||
- cannot be quantities or be wrappers over the `quantity` type
|
- cannot be quantities by themselves,
|
||||||
(i.e. ``std::optional<si::length<si::metre>>``),
|
- cannot be wrappers over the `quantity` type (i.e. ``std::optional<si::length<si::metre>>``),
|
||||||
- have to be regular types (e.g. they have to provide equality operators)
|
- 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
|
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
|
dimension, and compare them for equality.
|
||||||
custom representation type have to satisfy more requirements.
|
|
||||||
|
|
||||||
|
|
||||||
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
|
class my_rep {
|
||||||
not plan to use a specific quantity's operation his/her custom representation type can
|
int value_{};
|
||||||
ignore (not implement/satisfy) the requirements for it.
|
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<si::metre, my_rep>(2'000) == si::length<si::kilometre, my_rep>(2));
|
||||||
|
|
||||||
|
|
||||||
Construction of Quantities with Custom Representation Types
|
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::
|
.. code-block::
|
||||||
:emphasize-lines: 6, 15
|
:emphasize-lines: 5, 13
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
class impl {
|
class impl {
|
||||||
T value_{};
|
int value_{};
|
||||||
public:
|
public:
|
||||||
impl() = default;
|
impl() = default;
|
||||||
constexpr impl(T v): value_(v) {}
|
constexpr impl(int v): value_(v) {}
|
||||||
// the rest of the representation type implementation
|
// the rest of the representation type implementation
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
class expl {
|
class expl {
|
||||||
T value_{};
|
int value_{};
|
||||||
public:
|
public:
|
||||||
expl() = default;
|
expl() = default;
|
||||||
constexpr explicit expl(T v): value_(v) {}
|
constexpr explicit expl(int v): value_(v) {}
|
||||||
// the rest of the representation type implementation
|
// the rest of the representation type implementation
|
||||||
};
|
};
|
||||||
|
|
||||||
The difference between the above types is that ``impl`` class is implicitly constructible
|
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::
|
representation types we have to obey similar rules::
|
||||||
|
|
||||||
si::length<si::metre, impl<int>> d1(123); // OK
|
si::length<si::metre, impl> d1(123); // OK
|
||||||
si::length<si::metre, expl<int>> d2(123); // Compile-time error
|
si::length<si::metre, expl> d2(123); // Compile-time error
|
||||||
si::length<si::metre, expl<int>> d3(expl(123)); // OK
|
si::length<si::metre, expl> d3(expl(123)); // OK
|
||||||
|
|
||||||
This also applies when we want to create a quantity with a custom representation type
|
This also applies when we want to create a quantity with a custom representation type
|
||||||
from a regular quantity value::
|
from a regular quantity value::
|
||||||
|
|
||||||
Length auto d = 123q_m;
|
Length auto d = 123q_m;
|
||||||
si::length<si::metre, impl<int>> d1(d); // OK
|
si::length<si::metre, impl> d1(d); // OK
|
||||||
si::length<si::metre, expl<int>> d2(d); // Compile-time error
|
si::length<si::metre, expl> d2(d); // Compile-time error
|
||||||
si::length<si::metre, expl<int>> d3(quantity_cast<expl<int>>(d)); // OK
|
si::length<si::metre, expl> d3(quantity_cast<expl>(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<si::metre, my_rep>(2'000) < si::length<si::kilometre, my_rep>(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<si::metre, my_rep>(2'000) + si::length<si::kilometre, my_rep>(1) ==
|
||||||
|
si::length<si::kilometre, my_rep>(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<Scalar Rep>
|
||||||
|
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<Rep>::lowest(); }
|
||||||
|
static constexpr Rep max() noexcept { return std::numeric_limits<Rep>::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<Scalar Rep>
|
||||||
|
inline constexpr bool treat_as_floating_point = std::is_floating_point_v<Rep>;
|
||||||
|
|
||||||
|
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<typename T>
|
||||||
|
class my_rep {
|
||||||
|
T value_{};
|
||||||
|
public:
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace custom
|
||||||
|
|
||||||
|
namespace units {
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline constexpr bool treat_as_floating_point<custom::my_rep<T>> = std::is_floating_point_v<T>;
|
||||||
|
|
||||||
|
} // 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
|
Conversions of Quantities with Custom Representation Types
|
||||||
----------------------------------------------------------
|
----------------------------------------------------------
|
||||||
|
|
||||||
In case we want to mix quantities of our Custom Representation Type with the quantities using
|
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:
|
than on constructors:
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
@@ -114,7 +288,7 @@ If we have instances of the above types we can construct quantities in the follo
|
|||||||
si::length<si::metre, int> d2(v_expl); // Compile-time error
|
si::length<si::metre, int> d2(v_expl); // Compile-time error
|
||||||
si::length<si::metre, int> d3(int(v_expl); // OK
|
si::length<si::metre, int> 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::
|
representation types with::
|
||||||
|
|
||||||
si::length<si::metre, impl<int>> d_impl(1);
|
si::length<si::metre, impl<int>> d_impl(1);
|
||||||
@@ -124,21 +298,7 @@ representation types with::
|
|||||||
si::length<si::metre, int> d3(quantity_cast<int>(d_expl)); // OK
|
si::length<si::metre, int> d3(quantity_cast<int>(d_expl)); // OK
|
||||||
|
|
||||||
|
|
||||||
Tricky cases
|
|
||||||
------------
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Customization points
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
treat_as_floating_point
|
|
||||||
|
|
||||||
quantity_value
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
For more examples of custom representation types usage please refer to :ref:`measurement`
|
For more examples of custom representation types usage please refer to the
|
||||||
example.
|
:ref:`Linear Algebra of Quantities` chapter and :ref:`measurement` example.
|
||||||
|
Reference in New Issue
Block a user