Overconstrained quantity operations relaxed

This commit is contained in:
Mateusz Pusz
2020-03-25 15:04:33 +01:00
parent 2a5baff160
commit ab1cc4b8e7
6 changed files with 167 additions and 95 deletions

View File

@@ -3,6 +3,38 @@
Using Custom Representation Types
=================================
A custom representation type can be provided as the last `quantity` class template parameter.
With this a user is able to change how a quantity's value is being represented and provide
its own custom logic for it (i.e. use a complex number or a measurement class that will handle
not only a value but also a measurement error).
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<si::length<si::metre>>``),
- have to be regular types (e.g. they have to provide equality operators)
- must be constructible from a fundamental integral type (to
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.
Additional requirements
-----------------------
.. important::
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.
Construction of Quantities with Custom Representation Types
-----------------------------------------------------------
@@ -49,7 +81,10 @@ from a regular quantity value::
Conversions of Quantities with Custom Representation Types
----------------------------------------------------------
Again let's assume two types but this time let's scope on converting operators rather
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.
Again let's assume two types but this time let's scope on conversion operators rather
than on constructors:
.. code-block::
@@ -89,14 +124,21 @@ representation types with::
si::length<si::metre, int> d3(quantity_cast<int>(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:`Linear Algebra of Quantities` chapter and :ref:`measurement` example.
For more examples of custom representation types usage please refer to :ref:`measurement`
example.

View File

@@ -31,17 +31,6 @@
namespace units {
namespace detail {
template<typename T, typename U = T>
concept basic_arithmetic = // exposition only
std::magma<std::ranges::plus, T, U> &&
std::magma<std::ranges::minus, T, U> &&
std::magma<std::ranges::times, T, U> &&
std::magma<std::ranges::divided_by, T, U>;
} // namespace detail
// PrefixFamily
struct prefix_family;
@@ -265,6 +254,14 @@ concept WrappedQuantity = detail::is_wrapped_quantity<T>;
* Satisfied by types that satisfy `(!Quantity<T>) && (!WrappedQuantity<T>) && std::regular<T>`.
*/
template<typename T>
concept Scalar = (!Quantity<T>) && (!WrappedQuantity<T>) && std::regular<T>; // TODO: && std::totally_ordered<T>;// && detail::basic_arithmetic<T>;
concept Scalar =
(!Quantity<T>) &&
(!WrappedQuantity<T>) &&
std::regular<T> &&
// construction from an integral type
std::constructible_from<T, std::int64_t> &&
// unit scaling
std::regular_invocable<std::multiplies<>, T, T> &&
std::regular_invocable<std::divides<>, T, T>;
} // namespace units

View File

@@ -228,8 +228,7 @@ public:
template<typename D2, typename U2, typename Rep2>
[[nodiscard]] friend constexpr auto operator<=>(const quantity& lhs, const quantity<D2, U2, Rep2>& rhs)
requires equivalent_dim<D, D2> &&
detail::basic_arithmetic<Rep, Rep2> &&
std::totally_ordered_with<Rep, Rep2>
std::totally_ordered_with<Rep, Rep2>
{
using cq = common_quantity<quantity, quantity<D2, U2, Rep2>>;
return cq(lhs).count() <=> cq(rhs).count();
@@ -238,8 +237,7 @@ public:
template<typename D2, typename U2, typename Rep2>
[[nodiscard]] friend constexpr auto operator==(const quantity& lhs, const quantity<D2, U2, Rep2>& rhs)
requires equivalent_dim<D, D2> &&
detail::basic_arithmetic<Rep, Rep2> &&
std::equality_comparable_with<Rep, Rep2>
std::equality_comparable_with<Rep, Rep2>
{
using cq = common_quantity<quantity, quantity<D2, U2, Rep2>>;
return cq(lhs).count() == cq(rhs).count();
@@ -250,8 +248,7 @@ public:
template<typename D2, typename U2, typename Rep2>
[[nodiscard]] friend constexpr bool operator==(const quantity& lhs, const quantity<D2, U2, Rep2>& rhs)
requires equivalent_dim<D, D2> &&
detail::basic_arithmetic<Rep, Rep2> &&
std::equality_comparable_with<Rep, Rep2>
std::equality_comparable_with<Rep, Rep2>
{
using cq = common_quantity<quantity, quantity<D2, U2, Rep2>>;
return cq(lhs).count() == cq(rhs).count();
@@ -260,8 +257,7 @@ public:
template<typename D2, typename U2, typename Rep2>
[[nodiscard]] friend constexpr bool operator!=(const quantity& lhs, const quantity<D2, U2, Rep2>& rhs)
requires equivalent_dim<D, D2> &&
detail::basic_arithmetic<Rep, Rep2> &&
std::equality_comparable_with<Rep, Rep2>
std::equality_comparable_with<Rep, Rep2>
{
return !(lhs == rhs);
}
@@ -269,8 +265,7 @@ public:
template<typename D2, typename U2, typename Rep2>
[[nodiscard]] friend constexpr bool operator<(const quantity& lhs, const quantity<D2, U2, Rep2>& rhs)
requires equivalent_dim<D, D2> &&
detail::basic_arithmetic<Rep, Rep2> &&
std::totally_ordered_with<Rep, Rep2>
std::totally_ordered_with<Rep, Rep2>
{
using cq = common_quantity<quantity, quantity<D2, U2, Rep2>>;
return cq(lhs).count() < cq(rhs).count();
@@ -279,8 +274,7 @@ public:
template<typename D2, typename U2, typename Rep2>
[[nodiscard]] friend constexpr bool operator<=(const quantity& lhs, const quantity<D2, U2, Rep2>& rhs)
requires equivalent_dim<D, D2> &&
detail::basic_arithmetic<Rep, Rep2> &&
std::totally_ordered_with<Rep, Rep2>
std::totally_ordered_with<Rep, Rep2>
{
return !(rhs < lhs);
}
@@ -288,8 +282,7 @@ public:
template<typename D2, typename U2, typename Rep2>
[[nodiscard]] friend constexpr bool operator>(const quantity& lhs, const quantity<D2, U2, Rep2>& rhs)
requires equivalent_dim<D, D2> &&
detail::basic_arithmetic<Rep, Rep2> &&
std::totally_ordered_with<Rep, Rep2>
std::totally_ordered_with<Rep, Rep2>
{
return rhs < lhs;
}
@@ -297,8 +290,7 @@ public:
template<typename D2, typename U2, typename Rep2>
[[nodiscard]] friend constexpr bool operator>=(const quantity& lhs, const quantity<D2, U2, Rep2>& rhs)
requires equivalent_dim<D, D2> &&
detail::basic_arithmetic<Rep, Rep2> &&
std::totally_ordered_with<Rep, Rep2>
std::totally_ordered_with<Rep, Rep2>
{
return !(lhs < rhs);
}
@@ -314,7 +306,7 @@ public:
template<typename D, typename U1, typename Rep1, typename U2, typename Rep2>
[[nodiscard]] constexpr Quantity AUTO operator+(const quantity<D, U1, Rep1>& lhs, const quantity<D, U2, Rep2>& rhs)
requires detail::basic_arithmetic<Rep1, Rep2>
requires std::regular_invocable<std::plus<>, Rep1, Rep2>
{
using common_rep = decltype(lhs.count() + rhs.count());
using ret = common_quantity<quantity<D, U1, Rep1>, quantity<D, U2, Rep2>, common_rep>;
@@ -323,7 +315,7 @@ template<typename D, typename U1, typename Rep1, typename U2, typename Rep2>
template<typename D, typename U1, typename Rep1, typename U2, typename Rep2>
[[nodiscard]] constexpr Quantity AUTO operator-(const quantity<D, U1, Rep1>& lhs, const quantity<D, U2, Rep2>& rhs)
requires detail::basic_arithmetic<Rep1, Rep2>
requires std::regular_invocable<std::minus<>, Rep1, Rep2>
{
using common_rep = decltype(lhs.count() - rhs.count());
using ret = common_quantity<quantity<D, U1, Rep1>, quantity<D, U2, Rep2>, common_rep>;
@@ -332,7 +324,7 @@ template<typename D, typename U1, typename Rep1, typename U2, typename Rep2>
template<typename D, typename U, typename Rep, Scalar Value>
[[nodiscard]] constexpr Quantity AUTO operator*(const quantity<D, U, Rep>& q, const Value& v)
requires std::magma<std::ranges::times, Rep, Value>
requires std::regular_invocable<std::multiplies<>, Rep, Value>
{
using common_rep = decltype(q.count() * v);
using ret = quantity<D, U, common_rep>;
@@ -341,14 +333,15 @@ template<typename D, typename U, typename Rep, Scalar Value>
template<Scalar Value, typename D, typename U, typename Rep>
[[nodiscard]] constexpr Quantity AUTO operator*(const Value& v, const quantity<D, U, Rep>& q)
requires std::magma<std::ranges::times, Value, Rep>
requires std::regular_invocable<std::multiplies<>, Value, Rep>
{
return q * v;
}
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
[[nodiscard]] constexpr Scalar AUTO operator*(const quantity<D1, U1, Rep1>& lhs, const quantity<D2, U2, Rep2>& rhs)
requires detail::basic_arithmetic<Rep1, Rep2> && equivalent_dim<D1, dim_invert<D2>>
requires std::regular_invocable<std::multiplies<>, Rep1, Rep2> &&
equivalent_dim<D1, dim_invert<D2>>
{
using common_rep = decltype(lhs.count() * rhs.count());
using ratio = ratio_multiply<typename U1::ratio, typename U2::ratio>;
@@ -361,7 +354,8 @@ template<typename D1, typename U1, typename Rep1, typename D2, typename U2, type
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
[[nodiscard]] constexpr Quantity AUTO operator*(const quantity<D1, U1, Rep1>& lhs, const quantity<D2, U2, Rep2>& rhs)
requires detail::basic_arithmetic<Rep1, Rep2> && (!equivalent_dim<D1, dim_invert<D2>>)
requires std::regular_invocable<std::multiplies<>, Rep1, Rep2> &&
(!equivalent_dim<D1, dim_invert<D2>>)
{
using dim = dimension_multiply<D1, D2>;
using ratio1 = ratio_divide<typename U1::ratio, typename dimension_unit<D1>::ratio>;
@@ -375,7 +369,7 @@ template<typename D1, typename U1, typename Rep1, typename D2, typename U2, type
template<Scalar Value, typename D, typename U, typename Rep>
[[nodiscard]] constexpr Quantity AUTO operator/(const Value& v, const quantity<D, U, Rep>& q)
requires std::magma<std::ranges::divided_by, Value, Rep>
requires std::regular_invocable<std::divides<>, Value, Rep>
{
Expects(q.count() != 0);
@@ -389,7 +383,7 @@ template<Scalar Value, typename D, typename U, typename Rep>
template<typename D, typename U, typename Rep, Scalar Value>
[[nodiscard]] constexpr Quantity AUTO operator/(const quantity<D, U, Rep>& q, const Value& v)
requires std::magma<std::ranges::divided_by, Rep, Value>
requires std::regular_invocable<std::divides<>, Rep, Value>
{
Expects(v != Value{0});
@@ -400,7 +394,8 @@ template<typename D, typename U, typename Rep, Scalar Value>
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
[[nodiscard]] constexpr Scalar AUTO operator/(const quantity<D1, U1, Rep1>& lhs, const quantity<D2, U2, Rep2>& rhs)
requires detail::basic_arithmetic<Rep1, Rep2> && equivalent_dim<D1, D2>
requires std::regular_invocable<std::divides<>, Rep1, Rep2> &&
equivalent_dim<D1, D2>
{
Expects(rhs.count() != 0);
@@ -411,7 +406,8 @@ template<typename D1, typename U1, typename Rep1, typename D2, typename U2, type
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
[[nodiscard]] constexpr Quantity AUTO operator/(const quantity<D1, U1, Rep1>& lhs, const quantity<D2, U2, Rep2>& rhs)
requires detail::basic_arithmetic<Rep1, Rep2> && (!equivalent_dim<D1, D2>)
requires std::regular_invocable<std::divides<>, Rep1, Rep2> &&
(!equivalent_dim<D1, D2>)
{
Expects(rhs.count() != 0);
@@ -429,7 +425,7 @@ template<typename D, typename U, typename Rep, Scalar Value>
[[nodiscard]] constexpr Quantity AUTO operator%(const quantity<D, U, Rep>& q, const Value& v)
requires (!treat_as_floating_point<Rep>) &&
(!treat_as_floating_point<Value>) &&
std::magma<std::ranges::modulus, Rep, Value>
std::regular_invocable<std::modulus<>, Rep, Value>
{
using common_rep = decltype(q.count() % v);
using ret = quantity<D, U, common_rep>;
@@ -440,7 +436,7 @@ template<typename D, typename U1, typename Rep1, typename U2, typename Rep2>
[[nodiscard]] constexpr Quantity AUTO operator%(const quantity<D, U1, Rep1>& lhs, const quantity<D, U2, Rep2>& rhs)
requires (!treat_as_floating_point<Rep1>) &&
(!treat_as_floating_point<Rep2>) &&
std::magma<std::ranges::modulus, Rep1, Rep2>
std::regular_invocable<std::modulus<>, Rep1, Rep2>
{
using common_rep = decltype(lhs.count() % rhs.count());
using ret = common_quantity<quantity<D, U1, Rep1>, quantity<D, U2, Rep2>, common_rep>;

View File

@@ -163,11 +163,10 @@ struct cast_ratio<FromD, FromU, ToD, ToU> {
*/
template<Quantity To, typename D, typename U, typename Rep>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q)
requires QuantityOf<To, D> &&
detail::basic_arithmetic<std::common_type_t<typename To::rep, Rep, intmax_t>>
requires QuantityOf<To, D>
{
using c_ratio = detail::cast_ratio<D, U, typename To::dimension, typename To::unit>::type;
using c_rep = std::common_type_t<typename To::rep, Rep, intmax_t>;
using c_rep = std::common_type_t<typename To::rep, Rep>;
using ret_unit = downcast_unit<typename To::dimension, typename To::unit::ratio>;
using ret = quantity<typename To::dimension, ret_unit, typename To::rep>;
using cast = detail::quantity_cast_impl<ret, c_ratio, c_rep, c_ratio::num == 1 && c_ratio::exp == 0, c_ratio::den == 1 && c_ratio::exp == 0>;
@@ -226,7 +225,6 @@ template<Unit ToU, typename D, typename U, typename Rep>
*/
template<Scalar ToRep, typename D, typename U, typename Rep>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q)
requires detail::basic_arithmetic<std::common_type_t<ToRep, Rep, intmax_t>>
{
return quantity_cast<quantity<D, U, ToRep>>(q);
}

View File

@@ -22,7 +22,7 @@
add_library(unit_tests_static
cgs_test.cpp
custom_rep_test.cpp
custom_rep_test_min_req.cpp
custom_unit_test.cpp
data_test.cpp
dimension_op_test.cpp

View File

@@ -32,37 +32,48 @@ using namespace units;
namespace {
template<typename T>
struct arithmetic_ops {
// constexpr T& operator+=(T other) { value_ += other.value_; return *this; }
// constexpr T& operator-=(T other) { value_ -= other.value_; return *this; }
// constexpr T& operator*=(T other) { value_ *= other.value_; return *this; }
// constexpr T& operator/=(T other) { value_ /= other.value_; return *this; }
struct equality_ops {
[[nodiscard]] friend constexpr bool operator==(T lhs, T rhs) { return lhs.value_ == rhs.value_; }
[[nodiscard]] friend constexpr bool operator!=(T lhs, T rhs) { return !(lhs == rhs); }
};
// [[nodiscard]] constexpr T operator-() const { return T(-value_); }
[[nodiscard]] friend constexpr T operator+(T lhs, T rhs) {
return T(lhs.value_ + rhs.value_);
}
[[nodiscard]] friend constexpr T operator-(T lhs, T rhs) {
return T(lhs.value_ - rhs.value_);
}
template<typename T>
struct scaling_ops {
[[nodiscard]] friend constexpr T operator*(T lhs, T rhs) {
return T(lhs.value_ * rhs.value_);
}
[[nodiscard]] friend constexpr T operator/(T lhs, T rhs) {
return T(lhs.value_ / rhs.value_);
}
[[nodiscard]] friend constexpr bool operator==(T lhs, T rhs) { return lhs.value_ == rhs.value_; }
[[nodiscard]] friend constexpr bool operator!=(T lhs, T rhs) { return !(lhs == rhs); }
[[nodiscard]] friend constexpr bool operator<(T lhs, T rhs) { return lhs.value_ < rhs.value_; }
[[nodiscard]] friend constexpr bool operator>(T lhs, T rhs) { return rhs < lhs; }
[[nodiscard]] friend constexpr bool operator<=(T lhs, T rhs) { return !(rhs < lhs); }
[[nodiscard]] friend constexpr bool operator>=(T lhs, T rhs) { return !(lhs < rhs); }
};
template<typename T>
struct impl_constructible_impl_convertible : arithmetic_ops<impl_constructible_impl_convertible<T>> {
struct scalar_ops : equality_ops<T>, scaling_ops<T> {};
template<typename T>
struct impl_constructible : scalar_ops<impl_constructible<T>> {
T value_{};
impl_constructible() = default;
constexpr impl_constructible(T v) : value_(std::move(v)) {}
// no conversion to fundamental arithmetic types
};
template<typename T>
using impl = impl_constructible<T>;
template<typename T>
struct expl_constructible : scalar_ops<expl_constructible<T>> {
T value_{};
expl_constructible() = default;
constexpr expl_constructible(T v) : value_(std::move(v)) {}
// no conversion to fundamental arithmetic types
};
template<typename T>
using expl = expl_constructible<T>;
template<typename T>
struct impl_constructible_impl_convertible : scalar_ops<impl_constructible_impl_convertible<T>> /*, int_scaling_ops<impl_constructible_impl_convertible<T>> */ {
T value_{};
impl_constructible_impl_convertible() = default;
constexpr impl_constructible_impl_convertible(T v) : value_(std::move(v)) {}
@@ -77,7 +88,7 @@ static_assert(std::convertible_to<impl_impl<float>, float>);
static_assert(units::Scalar<impl_impl<float>>);
template<typename T>
struct expl_constructible_impl_convertible : arithmetic_ops<expl_constructible_impl_convertible<T>> {
struct expl_constructible_impl_convertible : scalar_ops<expl_constructible_impl_convertible<T>> {
T value_{};
expl_constructible_impl_convertible() = default;
constexpr explicit expl_constructible_impl_convertible(T v) : value_(std::move(v)) {}
@@ -92,7 +103,7 @@ static_assert(std::convertible_to<expl_impl<float>, float>);
static_assert(units::Scalar<expl_impl<float>>);
template<typename T>
struct impl_constructible_expl_convertible : arithmetic_ops<impl_constructible_expl_convertible<T>> {
struct impl_constructible_expl_convertible : scalar_ops<impl_constructible_expl_convertible<T>> {
T value_{};
impl_constructible_expl_convertible() = default;
constexpr impl_constructible_expl_convertible(T v) : value_(std::move(v)) {}
@@ -107,7 +118,7 @@ static_assert(!std::convertible_to<impl_expl<float>, float>);
static_assert(units::Scalar<impl_expl<float>>);
template<typename T>
struct expl_constructible_expl_convertible : arithmetic_ops<expl_constructible_expl_convertible<T>> {
struct expl_constructible_expl_convertible : scalar_ops<expl_constructible_expl_convertible<T>> {
T value_{};
expl_constructible_expl_convertible() = default;
constexpr explicit expl_constructible_expl_convertible(T v) : value_(std::move(v)) {}
@@ -125,6 +136,12 @@ static_assert(units::Scalar<expl_expl<float>>);
namespace units {
template<typename T>
inline constexpr bool treat_as_floating_point<impl<T>> = std::is_floating_point_v<T>;
template<typename T>
inline constexpr bool treat_as_floating_point<expl_constructible<T>> = std::is_floating_point_v<T>;
template<typename T>
inline constexpr bool treat_as_floating_point<impl_impl<T>> = std::is_floating_point_v<T>;
@@ -138,27 +155,14 @@ template<typename T>
inline constexpr bool treat_as_floating_point<expl_expl<T>> = std::is_floating_point_v<T>;
template<typename T>
struct quantity_values<impl_impl<T>> {
static constexpr impl_impl<T> zero() { return impl_impl<T>(0); }
static constexpr impl_impl<T> max() { return std::numeric_limits<T>::max(); }
static constexpr impl_impl<T> min() { return std::numeric_limits<T>::lowest(); }
struct quantity_values<impl<T>> {
static constexpr impl<T> zero() { return 0; }
static constexpr impl<T> max() { return std::numeric_limits<T>::max(); }
static constexpr impl<T> min() { return std::numeric_limits<T>::lowest(); }
};
} // namespace units
namespace std {
// template<typename T, typename U>
// struct common_type<my_value<T>, my_value<U>> : std::type_identity<my_value<common_type_t<T, U>>> {};
// template<typename T, typename U>
// struct common_type<my_value<T>, U> : common_type<T, U> {};
// template<typename T, typename U>
// struct common_type<T, my_value<U>> : common_type<T, U> {};
} // namespace std
namespace {
using namespace units::si;
@@ -225,15 +229,50 @@ static_assert(length<metre, impl_expl<double>>(length<metre, int>(1)).count() ==
// unit conversions
static_assert(length<metre, impl<int>>(length<kilometre, impl<int>>(1)).count() == impl<int>(1000));
static_assert(length<metre, expl<int>>(length<kilometre, expl<int>>(expl(1))).count() == expl<int>(1000));
static_assert(length<metre, impl_impl<int>>(length<kilometre, impl_impl<int>>(1)).count() == impl_impl<int>(1000));
static_assert(length<metre, impl_expl<int>>(length<kilometre, impl_expl<int>>(1)).count() == impl_expl<int>(1000));
static_assert(length<metre, expl_impl<int>>(length<kilometre, expl_impl<int>>(expl_impl(1))).count() == expl_impl<int>(1000));
static_assert(length<metre, expl_expl<int>>(length<kilometre, expl_expl<int>>(expl_expl(1))).count() == expl_expl<int>(1000));
// static_assert(length<kilometre, impl<int>>(length<metre, impl<int>>(2000)).count() == impl<int>(2)); // should not compile (truncating conversion)
static_assert(length<kilometre, impl<int>>(quantity_cast<kilometre>(length<metre, impl<int>>(2000))).count() == impl<int>(2));
// static_assert(length<kilometre, expl<int>>(length<metre, expl<int>>(expl<int>(2000))).count() == expl<int>(2)); // should not compile (truncating conversion)
static_assert(length<kilometre, expl<int>>(quantity_cast<kilometre>(length<metre, expl<int>>(expl<int>(2000)))).count() == expl<int>(2));
// static_assert(length<kilometre, impl_impl<int>>(length<metre, impl_impl<int>>(2000)).count() == impl_impl<int>(2)); // should not compile (truncating conversion)
static_assert(length<kilometre, impl_impl<int>>(quantity_cast<kilometre>(length<metre, impl_impl<int>>(2000))).count() == impl_impl<int>(2));
// static_assert(length<kilometre, impl_expl<int>>(length<metre, impl_expl<int>>(2000)).count() == impl_expl<int>(2)); // should not compile (truncating conversion)
static_assert(length<kilometre, impl_expl<int>>(quantity_cast<kilometre>(length<metre, impl_expl<int>>(2000))).count() == impl_expl<int>(2));
// static_assert(length<kilometre, expl_impl<int>>(length<metre, expl_impl<int>>(expl_impl<int>(2000))).count() == expl_impl<int>(2)); // should not compile (truncating conversion)
static_assert(length<kilometre, expl_impl<int>>(quantity_cast<kilometre>(length<metre, expl_impl<int>>(expl_impl<int>(2000)))).count() == expl_impl<int>(2));
// static_assert(length<kilometre, expl_expl<int>>(length<metre, expl_expl<int>>(expl_expl<int>(2000))).count() == expl_expl<int>(2)); // should not compile (truncating conversion)
static_assert(length<kilometre, expl_expl<int>>(quantity_cast<kilometre>(length<metre, expl_expl<int>>(expl_expl<int>(2000)))).count() == expl_expl<int>(2));
static_assert(length<metre, impl_impl<int>>::zero().count() == impl_impl<int>{0});
static_assert(length<metre, impl_impl<int>>::min().count() == impl_impl<int>{std::numeric_limits<int>::lowest()});
static_assert(length<metre, impl_impl<int>>::max().count() == impl_impl<int>{std::numeric_limits<int>::max()});
static_assert(length<metre, impl_impl<double>>::zero().count() == impl_impl<double>{0.0});
static_assert(length<metre, impl_impl<double>>::min().count() == impl_impl<double>{std::numeric_limits<double>::lowest()});
static_assert(length<metre, impl_impl<double>>::max().count() == impl_impl<double>{std::numeric_limits<double>::max()});
// static_assert(velocity<metre_per_second, impl<int>>(velocity<kilometre_per_hour, impl<int>>(72)).count() == impl<int>(20)); // should not compile (truncating conversion)
static_assert(velocity<metre_per_second, impl<int>>(quantity_cast<metre_per_second>(velocity<kilometre_per_hour, impl<int>>(72))).count() == impl<int>(20));
// static_assert(velocity<metre_per_second, expl<int>>(velocity<kilometre_per_hour, expl<int>>(expl(72))).count() == expl<int>(20)); // should not compile (truncating conversion)
static_assert(velocity<metre_per_second, expl<int>>(quantity_cast<metre_per_second>(velocity<kilometre_per_hour, expl<int>>(expl(72)))).count() == expl<int>(20));
// static_assert(velocity<metre_per_second, impl_impl<int>>(velocity<kilometre_per_hour, impl_impl<int>>(72)).count() == impl_impl<int>(20)); // should not compile (truncating conversion)
static_assert(velocity<metre_per_second, impl_impl<int>>(quantity_cast<metre_per_second>(velocity<kilometre_per_hour, impl_impl<int>>(72))).count() == impl_impl<int>(20));
// static_assert(velocity<metre_per_second, impl_expl<int>>(velocity<kilometre_per_hour, impl_expl<int>>(72)).count() == impl_expl<int>(20)); // should not compile (truncating conversion)
static_assert(velocity<metre_per_second, impl_expl<int>>(quantity_cast<metre_per_second>(velocity<kilometre_per_hour, impl_expl<int>>(72))).count() == impl_expl<int>(20));
// static_assert(velocity<metre_per_second, expl_impl<int>>(velocity<kilometre_per_hour, expl_impl<int>>(expl_impl(72))).count() == expl_impl<int>(20)); // should not compile (truncating conversion)
static_assert(velocity<metre_per_second, expl_impl<int>>(quantity_cast<metre_per_second>(velocity<kilometre_per_hour, expl_impl<int>>(expl_impl(72)))).count() == expl_impl<int>(20));
// static_assert(velocity<metre_per_second, expl_expl<int>>(velocity<kilometre_per_hour, expl_expl<int>>(expl_expl(72))).count() == expl_expl<int>(20)); // should not compile (truncating conversion)
static_assert(velocity<metre_per_second, expl_expl<int>>(quantity_cast<metre_per_second>(velocity<kilometre_per_hour, expl_expl<int>>(expl_expl(72)))).count() == expl_expl<int>(20));
// static_assert(velocity<kilometre_per_hour, impl<int>>(velocity<metre_per_second, impl<int>>(20)).count() == impl<int>(72)); // should not compile (truncating conversion)
static_assert(velocity<kilometre_per_hour, impl<int>>(quantity_cast<kilometre_per_hour>(velocity<metre_per_second, impl<int>>(20))).count() == impl<int>(72));
// static_assert(velocity<kilometre_per_hour, expl<int>>(velocity<metre_per_second, expl<int>>(expl<int>(20))).count() == expl<int>(72)); // should not compile (truncating conversion)
static_assert(velocity<kilometre_per_hour, expl<int>>(quantity_cast<kilometre_per_hour>(velocity<metre_per_second, expl<int>>(expl<int>(20)))).count() == expl<int>(72));
// static_assert(velocity<kilometre_per_hour, impl_impl<int>>(velocity<metre_per_second, impl_impl<int>>(20)).count() == impl_impl<int>(72)); // should not compile (truncating conversion)
static_assert(velocity<kilometre_per_hour, impl_impl<int>>(quantity_cast<kilometre_per_hour>(velocity<metre_per_second, impl_impl<int>>(20))).count() == impl_impl<int>(72));
// static_assert(velocity<kilometre_per_hour, impl_expl<int>>(velocity<metre_per_second, impl_expl<int>>(20)).count() == impl_expl<int>(72)); // should not compile (truncating conversion)
static_assert(velocity<kilometre_per_hour, impl_expl<int>>(quantity_cast<kilometre_per_hour>(velocity<metre_per_second, impl_expl<int>>(20))).count() == impl_expl<int>(72));
// static_assert(velocity<kilometre_per_hour, expl_impl<int>>(velocity<metre_per_second, expl_impl<int>>(expl_impl<int>(20))).count() == expl_impl<int>(72)); // should not compile (truncating conversion)
static_assert(velocity<kilometre_per_hour, expl_impl<int>>(quantity_cast<kilometre_per_hour>(velocity<metre_per_second, expl_impl<int>>(expl_impl<int>(20)))).count() == expl_impl<int>(72));
// static_assert(velocity<kilometre_per_hour, expl_expl<int>>(velocity<metre_per_second, expl_expl<int>>(expl_expl<int>(20))).count() == expl_expl<int>(72)); // should not compile (truncating conversion)
static_assert(velocity<kilometre_per_hour, expl_expl<int>>(quantity_cast<kilometre_per_hour>(velocity<metre_per_second, expl_expl<int>>(expl_expl<int>(20)))).count() == expl_expl<int>(72));
} // namespace