From ab1cc4b8e730b3cd3bf955f33cbfb14d9d5f4738 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Wed, 25 Mar 2020 15:04:33 +0100 Subject: [PATCH] Overconstrained quantity operations relaxed --- .../use_cases/custom_representation_types.rst | 48 ++++++- src/include/units/concepts.h | 21 ++- src/include/units/quantity.h | 52 ++++--- src/include/units/quantity_cast.h | 6 +- test/unit_test/static/CMakeLists.txt | 2 +- ...p_test.cpp => custom_rep_test_min_req.cpp} | 133 +++++++++++------- 6 files changed, 167 insertions(+), 95 deletions(-) rename test/unit_test/static/{custom_rep_test.cpp => custom_rep_test_min_req.cpp} (53%) diff --git a/docs/use_cases/custom_representation_types.rst b/docs/use_cases/custom_representation_types.rst index 7c02ea1b..290893a7 100644 --- a/docs/use_cases/custom_representation_types.rst +++ b/docs/use_cases/custom_representation_types.rst @@ -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>``), +- 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 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:`Linear Algebra of Quantities` chapter and :ref:`measurement` example. \ No newline at end of file + For more examples of custom representation types usage please refer to :ref:`measurement` + example. diff --git a/src/include/units/concepts.h b/src/include/units/concepts.h index f1a5b9cf..9365f6cf 100644 --- a/src/include/units/concepts.h +++ b/src/include/units/concepts.h @@ -31,17 +31,6 @@ namespace units { -namespace detail { - -template -concept basic_arithmetic = // exposition only - std::magma && - std::magma && - std::magma && - std::magma; - -} // namespace detail - // PrefixFamily struct prefix_family; @@ -265,6 +254,14 @@ concept WrappedQuantity = detail::is_wrapped_quantity; * Satisfied by types that satisfy `(!Quantity) && (!WrappedQuantity) && std::regular`. */ template -concept Scalar = (!Quantity) && (!WrappedQuantity) && std::regular; // TODO: && std::totally_ordered;// && detail::basic_arithmetic; +concept Scalar = + (!Quantity) && + (!WrappedQuantity) && + std::regular && + // construction from an integral type + std::constructible_from && + // unit scaling + std::regular_invocable, T, T> && + std::regular_invocable, T, T>; } // namespace units diff --git a/src/include/units/quantity.h b/src/include/units/quantity.h index 1cd91127..a5199efc 100644 --- a/src/include/units/quantity.h +++ b/src/include/units/quantity.h @@ -228,8 +228,7 @@ public: template [[nodiscard]] friend constexpr auto operator<=>(const quantity& lhs, const quantity& rhs) requires equivalent_dim && - detail::basic_arithmetic && - std::totally_ordered_with + std::totally_ordered_with { using cq = common_quantity>; return cq(lhs).count() <=> cq(rhs).count(); @@ -238,8 +237,7 @@ public: template [[nodiscard]] friend constexpr auto operator==(const quantity& lhs, const quantity& rhs) requires equivalent_dim && - detail::basic_arithmetic && - std::equality_comparable_with + std::equality_comparable_with { using cq = common_quantity>; return cq(lhs).count() == cq(rhs).count(); @@ -250,8 +248,7 @@ public: template [[nodiscard]] friend constexpr bool operator==(const quantity& lhs, const quantity& rhs) requires equivalent_dim && - detail::basic_arithmetic && - std::equality_comparable_with + std::equality_comparable_with { using cq = common_quantity>; return cq(lhs).count() == cq(rhs).count(); @@ -260,8 +257,7 @@ public: template [[nodiscard]] friend constexpr bool operator!=(const quantity& lhs, const quantity& rhs) requires equivalent_dim && - detail::basic_arithmetic && - std::equality_comparable_with + std::equality_comparable_with { return !(lhs == rhs); } @@ -269,8 +265,7 @@ public: template [[nodiscard]] friend constexpr bool operator<(const quantity& lhs, const quantity& rhs) requires equivalent_dim && - detail::basic_arithmetic && - std::totally_ordered_with + std::totally_ordered_with { using cq = common_quantity>; return cq(lhs).count() < cq(rhs).count(); @@ -279,8 +274,7 @@ public: template [[nodiscard]] friend constexpr bool operator<=(const quantity& lhs, const quantity& rhs) requires equivalent_dim && - detail::basic_arithmetic && - std::totally_ordered_with + std::totally_ordered_with { return !(rhs < lhs); } @@ -288,8 +282,7 @@ public: template [[nodiscard]] friend constexpr bool operator>(const quantity& lhs, const quantity& rhs) requires equivalent_dim && - detail::basic_arithmetic && - std::totally_ordered_with + std::totally_ordered_with { return rhs < lhs; } @@ -297,8 +290,7 @@ public: template [[nodiscard]] friend constexpr bool operator>=(const quantity& lhs, const quantity& rhs) requires equivalent_dim && - detail::basic_arithmetic && - std::totally_ordered_with + std::totally_ordered_with { return !(lhs < rhs); } @@ -314,7 +306,7 @@ public: template [[nodiscard]] constexpr Quantity AUTO operator+(const quantity& lhs, const quantity& rhs) - requires detail::basic_arithmetic + requires std::regular_invocable, Rep1, Rep2> { using common_rep = decltype(lhs.count() + rhs.count()); using ret = common_quantity, quantity, common_rep>; @@ -323,7 +315,7 @@ template template [[nodiscard]] constexpr Quantity AUTO operator-(const quantity& lhs, const quantity& rhs) - requires detail::basic_arithmetic + requires std::regular_invocable, Rep1, Rep2> { using common_rep = decltype(lhs.count() - rhs.count()); using ret = common_quantity, quantity, common_rep>; @@ -332,7 +324,7 @@ template template [[nodiscard]] constexpr Quantity AUTO operator*(const quantity& q, const Value& v) - requires std::magma + requires std::regular_invocable, Rep, Value> { using common_rep = decltype(q.count() * v); using ret = quantity; @@ -341,14 +333,15 @@ template template [[nodiscard]] constexpr Quantity AUTO operator*(const Value& v, const quantity& q) - requires std::magma + requires std::regular_invocable, Value, Rep> { return q * v; } template [[nodiscard]] constexpr Scalar AUTO operator*(const quantity& lhs, const quantity& rhs) - requires detail::basic_arithmetic && equivalent_dim> + requires std::regular_invocable, Rep1, Rep2> && + equivalent_dim> { using common_rep = decltype(lhs.count() * rhs.count()); using ratio = ratio_multiply; @@ -361,7 +354,8 @@ template [[nodiscard]] constexpr Quantity AUTO operator*(const quantity& lhs, const quantity& rhs) - requires detail::basic_arithmetic && (!equivalent_dim>) + requires std::regular_invocable, Rep1, Rep2> && + (!equivalent_dim>) { using dim = dimension_multiply; using ratio1 = ratio_divide::ratio>; @@ -375,7 +369,7 @@ template [[nodiscard]] constexpr Quantity AUTO operator/(const Value& v, const quantity& q) - requires std::magma + requires std::regular_invocable, Value, Rep> { Expects(q.count() != 0); @@ -389,7 +383,7 @@ template template [[nodiscard]] constexpr Quantity AUTO operator/(const quantity& q, const Value& v) - requires std::magma + requires std::regular_invocable, Rep, Value> { Expects(v != Value{0}); @@ -400,7 +394,8 @@ template template [[nodiscard]] constexpr Scalar AUTO operator/(const quantity& lhs, const quantity& rhs) - requires detail::basic_arithmetic && equivalent_dim + requires std::regular_invocable, Rep1, Rep2> && + equivalent_dim { Expects(rhs.count() != 0); @@ -411,7 +406,8 @@ template [[nodiscard]] constexpr Quantity AUTO operator/(const quantity& lhs, const quantity& rhs) - requires detail::basic_arithmetic && (!equivalent_dim) + requires std::regular_invocable, Rep1, Rep2> && + (!equivalent_dim) { Expects(rhs.count() != 0); @@ -429,7 +425,7 @@ template [[nodiscard]] constexpr Quantity AUTO operator%(const quantity& q, const Value& v) requires (!treat_as_floating_point) && (!treat_as_floating_point) && - std::magma + std::regular_invocable, Rep, Value> { using common_rep = decltype(q.count() % v); using ret = quantity; @@ -440,7 +436,7 @@ template [[nodiscard]] constexpr Quantity AUTO operator%(const quantity& lhs, const quantity& rhs) requires (!treat_as_floating_point) && (!treat_as_floating_point) && - std::magma + std::regular_invocable, Rep1, Rep2> { using common_rep = decltype(lhs.count() % rhs.count()); using ret = common_quantity, quantity, common_rep>; diff --git a/src/include/units/quantity_cast.h b/src/include/units/quantity_cast.h index d88c278a..fcb5296e 100644 --- a/src/include/units/quantity_cast.h +++ b/src/include/units/quantity_cast.h @@ -163,11 +163,10 @@ struct cast_ratio { */ template [[nodiscard]] constexpr auto quantity_cast(const quantity& q) - requires QuantityOf && - detail::basic_arithmetic> + requires QuantityOf { using c_ratio = detail::cast_ratio::type; - using c_rep = std::common_type_t; + using c_rep = std::common_type_t; using ret_unit = downcast_unit; using ret = quantity; using cast = detail::quantity_cast_impl; @@ -226,7 +225,6 @@ template */ template [[nodiscard]] constexpr auto quantity_cast(const quantity& q) - requires detail::basic_arithmetic> { return quantity_cast>(q); } diff --git a/test/unit_test/static/CMakeLists.txt b/test/unit_test/static/CMakeLists.txt index 65688088..0b8c036b 100644 --- a/test/unit_test/static/CMakeLists.txt +++ b/test/unit_test/static/CMakeLists.txt @@ -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 diff --git a/test/unit_test/static/custom_rep_test.cpp b/test/unit_test/static/custom_rep_test_min_req.cpp similarity index 53% rename from test/unit_test/static/custom_rep_test.cpp rename to test/unit_test/static/custom_rep_test_min_req.cpp index 1909dcb6..10ffaa01 100644 --- a/test/unit_test/static/custom_rep_test.cpp +++ b/test/unit_test/static/custom_rep_test_min_req.cpp @@ -32,37 +32,48 @@ using namespace units; namespace { template -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 +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 -struct impl_constructible_impl_convertible : arithmetic_ops> { +struct scalar_ops : equality_ops, scaling_ops {}; + +template +struct impl_constructible : scalar_ops> { + T value_{}; + impl_constructible() = default; + constexpr impl_constructible(T v) : value_(std::move(v)) {} + // no conversion to fundamental arithmetic types +}; + +template +using impl = impl_constructible; + +template +struct expl_constructible : scalar_ops> { + T value_{}; + expl_constructible() = default; + constexpr expl_constructible(T v) : value_(std::move(v)) {} + // no conversion to fundamental arithmetic types +}; + +template +using expl = expl_constructible; + +template +struct impl_constructible_impl_convertible : scalar_ops> /*, int_scaling_ops> */ { 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, float>); static_assert(units::Scalar>); template -struct expl_constructible_impl_convertible : arithmetic_ops> { +struct expl_constructible_impl_convertible : scalar_ops> { 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, float>); static_assert(units::Scalar>); template -struct impl_constructible_expl_convertible : arithmetic_ops> { +struct impl_constructible_expl_convertible : scalar_ops> { 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, float>); static_assert(units::Scalar>); template -struct expl_constructible_expl_convertible : arithmetic_ops> { +struct expl_constructible_expl_convertible : scalar_ops> { 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>); namespace units { +template +inline constexpr bool treat_as_floating_point> = std::is_floating_point_v; + +template +inline constexpr bool treat_as_floating_point> = std::is_floating_point_v; + template inline constexpr bool treat_as_floating_point> = std::is_floating_point_v; @@ -138,27 +155,14 @@ template inline constexpr bool treat_as_floating_point> = std::is_floating_point_v; template -struct quantity_values> { - static constexpr impl_impl zero() { return impl_impl(0); } - static constexpr impl_impl max() { return std::numeric_limits::max(); } - static constexpr impl_impl min() { return std::numeric_limits::lowest(); } +struct quantity_values> { + static constexpr impl zero() { return 0; } + static constexpr impl max() { return std::numeric_limits::max(); } + static constexpr impl min() { return std::numeric_limits::lowest(); } }; } // namespace units -namespace std { - -// template -// struct common_type, my_value> : std::type_identity>> {}; - -// template -// struct common_type, U> : common_type {}; - -// template -// struct common_type> : common_type {}; - -} // namespace std - namespace { using namespace units::si; @@ -225,15 +229,50 @@ static_assert(length>(length(1)).count() == // unit conversions +static_assert(length>(length>(1)).count() == impl(1000)); +static_assert(length>(length>(expl(1))).count() == expl(1000)); +static_assert(length>(length>(1)).count() == impl_impl(1000)); static_assert(length>(length>(1)).count() == impl_expl(1000)); +static_assert(length>(length>(expl_impl(1))).count() == expl_impl(1000)); +static_assert(length>(length>(expl_expl(1))).count() == expl_expl(1000)); + +// static_assert(length>(length>(2000)).count() == impl(2)); // should not compile (truncating conversion) +static_assert(length>(quantity_cast(length>(2000))).count() == impl(2)); +// static_assert(length>(length>(expl(2000))).count() == expl(2)); // should not compile (truncating conversion) +static_assert(length>(quantity_cast(length>(expl(2000)))).count() == expl(2)); +// static_assert(length>(length>(2000)).count() == impl_impl(2)); // should not compile (truncating conversion) +static_assert(length>(quantity_cast(length>(2000))).count() == impl_impl(2)); // static_assert(length>(length>(2000)).count() == impl_expl(2)); // should not compile (truncating conversion) static_assert(length>(quantity_cast(length>(2000))).count() == impl_expl(2)); +// static_assert(length>(length>(expl_impl(2000))).count() == expl_impl(2)); // should not compile (truncating conversion) +static_assert(length>(quantity_cast(length>(expl_impl(2000)))).count() == expl_impl(2)); +// static_assert(length>(length>(expl_expl(2000))).count() == expl_expl(2)); // should not compile (truncating conversion) +static_assert(length>(quantity_cast(length>(expl_expl(2000)))).count() == expl_expl(2)); -static_assert(length>::zero().count() == impl_impl{0}); -static_assert(length>::min().count() == impl_impl{std::numeric_limits::lowest()}); -static_assert(length>::max().count() == impl_impl{std::numeric_limits::max()}); -static_assert(length>::zero().count() == impl_impl{0.0}); -static_assert(length>::min().count() == impl_impl{std::numeric_limits::lowest()}); -static_assert(length>::max().count() == impl_impl{std::numeric_limits::max()}); +// static_assert(velocity>(velocity>(72)).count() == impl(20)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(72))).count() == impl(20)); +// static_assert(velocity>(velocity>(expl(72))).count() == expl(20)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(expl(72)))).count() == expl(20)); +// static_assert(velocity>(velocity>(72)).count() == impl_impl(20)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(72))).count() == impl_impl(20)); +// static_assert(velocity>(velocity>(72)).count() == impl_expl(20)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(72))).count() == impl_expl(20)); +// static_assert(velocity>(velocity>(expl_impl(72))).count() == expl_impl(20)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(expl_impl(72)))).count() == expl_impl(20)); +// static_assert(velocity>(velocity>(expl_expl(72))).count() == expl_expl(20)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(expl_expl(72)))).count() == expl_expl(20)); + +// static_assert(velocity>(velocity>(20)).count() == impl(72)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(20))).count() == impl(72)); +// static_assert(velocity>(velocity>(expl(20))).count() == expl(72)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(expl(20)))).count() == expl(72)); +// static_assert(velocity>(velocity>(20)).count() == impl_impl(72)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(20))).count() == impl_impl(72)); +// static_assert(velocity>(velocity>(20)).count() == impl_expl(72)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(20))).count() == impl_expl(72)); +// static_assert(velocity>(velocity>(expl_impl(20))).count() == expl_impl(72)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(expl_impl(20)))).count() == expl_impl(72)); +// static_assert(velocity>(velocity>(expl_expl(20))).count() == expl_expl(72)); // should not compile (truncating conversion) +static_assert(velocity>(quantity_cast(velocity>(expl_expl(20)))).count() == expl_expl(72)); } // namespace