From 2dd8c90250bf49da616396210a57803010992991 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Wed, 7 Oct 2020 12:02:08 +0200 Subject: [PATCH] feat: :sparkles: interoperability with `std::chrono::duration` and other units libraries Resolves #33 --- docs/CHANGELOG.md | 1 + docs/reference/core/concepts.rst | 9 ++- docs/reference/core/customization_points.rst | 3 + src/include/units/bits/basic_concepts.h | 17 ++++- src/include/units/chrono.h | 39 +++++++++++ src/include/units/customization_points.h | 13 ++++ src/include/units/quantity.h | 33 +++++++--- src/include/units/quantity_cast.h | 69 +++++++++++--------- test/unit_test/static/CMakeLists.txt | 1 + test/unit_test/static/chrono_test.cpp | 57 ++++++++++++++++ test/unit_test/static/concepts_test.cpp | 8 +++ 11 files changed, 206 insertions(+), 44 deletions(-) create mode 100644 src/include/units/chrono.h create mode 100644 test/unit_test/static/chrono_test.cpp diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 714b3463..2c9f7024 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,7 @@ - **0.7.0 WIP** - (!) refactor: `ScalableNumber` renamed to `QuantityValue` - refactor: basic concepts, `quantity` and `quantity_cast` refactored + - feat: interoperability with `std::chrono::duration` and other units libraries - feat: CTAD for dimensionless quantity added - feat: value initialization for quantity value removed (left with a default initialization) - perf: preconditions check do not influence the runtime performance of a Release build diff --git a/docs/reference/core/concepts.rst b/docs/reference/core/concepts.rst index ea22df6b..7b7d62b6 100644 --- a/docs/reference/core/concepts.rst +++ b/docs/reference/core/concepts.rst @@ -57,16 +57,21 @@ Concepts A concept matching all quantities in the library. Satisfied by all instantiations of :class:`quantity`. +.. concept:: template QuantityLike + + A concept matching all quantity-like types. Satisfied by all types for which a correct specialization + of :class:`quantity_traits` type trait is provided. + .. concept:: template WrappedQuantity A concept matching types that wrap quantity objects. Satisfied by all wrapper types that - satisfy :expr:`Quantity` recursively + satisfy :expr:`QuantityLike` recursively (i.e. ``std::optional>``). .. concept:: template QuantityValue A concept matching types that can be used as a `Quantity` representation type. Satisfied - by types that match :expr:`(!Quantity) && (!WrappedQuantity) && std::regular` and + by types that match :expr:`(!QuantityLike) && (!WrappedQuantity) && std::regular` and satisfy one of the following: - if :expr:`common_type_with` is ``true``, then :expr:`std::common_type_t` diff --git a/docs/reference/core/customization_points.rst b/docs/reference/core/customization_points.rst index 0651f991..eeb3813b 100644 --- a/docs/reference/core/customization_points.rst +++ b/docs/reference/core/customization_points.rst @@ -5,3 +5,6 @@ Customization Points .. doxygenstruct:: units::quantity_values :members: + +.. doxygenstruct:: units::quantity_traits + :members: diff --git a/src/include/units/bits/basic_concepts.h b/src/include/units/bits/basic_concepts.h index 242f5a8a..da8a889e 100644 --- a/src/include/units/bits/basic_concepts.h +++ b/src/include/units/bits/basic_concepts.h @@ -229,6 +229,19 @@ concept Quantity = detail::is_quantity; template concept QuantityPoint = detail::is_quantity_point; +/** + * @brief A concept matching all quantity-like types + * + * Satisfied by all types for which a correct specialization of `quantity_traits` + * type trait is provided. + */ +template +concept QuantityLike = requires(T q) { + typename quantity_traits::dimension; + typename quantity_traits::unit; + typename quantity_traits::rep; + { quantity_traits::count(q) } -> std::convertible_to::rep>; +}; // QuantityValue @@ -266,7 +279,7 @@ inline constexpr bool is_wrapped_quantity = false; template requires requires { typename T::value_type; } -inline constexpr bool is_wrapped_quantity = Quantity || is_wrapped_quantity; +inline constexpr bool is_wrapped_quantity = QuantityLike || is_wrapped_quantity; } // namespace detail @@ -287,7 +300,7 @@ concept wrapped_quantity_ = // exposition only */ template concept QuantityValue = - (!Quantity) && + (!QuantityLike) && (!wrapped_quantity_) && std::regular && scalable_; diff --git a/src/include/units/chrono.h b/src/include/units/chrono.h new file mode 100644 index 00000000..81e7a295 --- /dev/null +++ b/src/include/units/chrono.h @@ -0,0 +1,39 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include + +namespace units { + +template +struct quantity_traits> { + using dimension = physical::si::dim_time; + using unit = downcast_unit; + using rep = Rep; + static constexpr rep count(const std::chrono::duration& q) { return q.count(); } +}; + +} // namespace units diff --git a/src/include/units/customization_points.h b/src/include/units/customization_points.h index 27b97fc5..fd4d8117 100644 --- a/src/include/units/customization_points.h +++ b/src/include/units/customization_points.h @@ -60,4 +60,17 @@ struct quantity_values { static constexpr Rep max() noexcept { return std::numeric_limits::max(); } }; +/** + * @brief Provides support for external quantity-like types + * + * The type trait should provide the following nested type aliases: @c dimension, @c unit, @c rep, + * and a static member function @c count(T) that will return the raw value of the quantity. + * + * Usage example can be found in units/chrono.h header file. + * + * @tparam T the type to provide support for + */ +template +struct quantity_traits; + } // namespace units diff --git a/src/include/units/quantity.h b/src/include/units/quantity.h index 2787ab8d..d167e12c 100644 --- a/src/include/units/quantity.h +++ b/src/include/units/quantity.h @@ -36,8 +36,8 @@ namespace units { template concept floating_point_ = // exposition only - (Quantity && treat_as_floating_point) || - (!Quantity && treat_as_floating_point); + (QuantityLike && treat_as_floating_point::rep>) || + (!QuantityLike && treat_as_floating_point); template concept safe_convertible_to_ = // exposition only @@ -49,15 +49,15 @@ concept safe_convertible_to_ = // exposition only // QFrom ratio is an exact multiple of QTo template concept harmonic_ = // exposition only - Quantity && + QuantityLike && Quantity && requires(QFrom from, QTo to) { requires is_integral(detail::quantity_ratio(from) / detail::quantity_ratio(to)); }; template concept safe_castable_to_ = // exposition only - Quantity && - QuantityOf && - scalable_with_ && + QuantityLike && + QuantityOf::dimension> && + scalable_with_::rep, typename QTo::rep> && (floating_point_ || (!floating_point_ && harmonic_)); template @@ -133,8 +133,8 @@ public: template Value> explicit(!(equivalent>)) constexpr quantity(const Value& v) : value_(static_cast(v)) {} - template Q2> - constexpr quantity(const Q2& q) : value_(quantity_cast(q).count()) {} + template Q> + explicit(!Quantity) constexpr quantity(const Q& q) : value_(quantity_cast(q).count()) {} quantity& operator=(const quantity&) = default; quantity& operator=(quantity&&) = default; @@ -325,9 +325,13 @@ public: // CTAD template -/* implicit */ quantity(V) -> quantity; +explicit(false) quantity(V) -> quantity; -template Q2> +template +explicit(!Quantity) quantity(Q) -> quantity::dimension, typename quantity_traits::unit, typename quantity_traits::rep>; + +// non-member binary operators +template Q2> requires quantity_value_for_, typename Q1::rep, typename Q2::rep> [[nodiscard]] constexpr Quantity auto operator+(const Q1& lhs, const Q2& rhs) { @@ -399,6 +403,15 @@ template Q2> return cq(lhs).count() == cq(rhs).count(); } +// type traits +template +struct quantity_traits> { + using dimension = D; + using unit = U; + using rep = Rep; + static constexpr rep count(const quantity& q) { return q.count(); } +}; + namespace detail { template diff --git a/src/include/units/quantity_cast.h b/src/include/units/quantity_cast.h index 2d9bacc6..e41ca65d 100644 --- a/src/include/units/quantity_cast.h +++ b/src/include/units/quantity_cast.h @@ -44,14 +44,17 @@ class quantity_point; namespace detail { -template -constexpr auto quantity_ratio(const quantity&) +template +constexpr auto quantity_ratio(const Q&) { - if constexpr(BaseDimension) { - return U::ratio; + using traits = quantity_traits; + using dim = TYPENAME traits::dimension; + using unit = TYPENAME traits::unit; + if constexpr(BaseDimension) { + return unit::ratio; } else { - return D::base_units_ratio * U::ratio / D::coherent_unit::ratio; + return dim::base_units_ratio * unit::ratio / dim::coherent_unit::ratio; } } @@ -102,29 +105,34 @@ struct cast_traits { * * @tparam To a target quantity type to cast to */ -template Rep> - requires QuantityOf -[[nodiscard]] constexpr auto quantity_cast(const quantity& q) +template + requires QuantityOf::dimension> && + scalable_with_::rep, typename To::rep> +[[nodiscard]] constexpr auto quantity_cast(const From& q) { + using traits = quantity_traits; + using from_dim = TYPENAME traits::dimension; + using from_unit = TYPENAME traits::unit; + using from_rep = TYPENAME traits::rep; using ret_unit = downcast_unit; using ret = quantity; - using traits = detail::cast_traits; - using ratio_type = TYPENAME traits::ratio_type; - using rep_type = TYPENAME traits::rep_type; - constexpr auto c_ratio = detail::cast_ratio(quantity(), To()); + using cast_traits = detail::cast_traits; + using ratio_type = TYPENAME cast_traits::ratio_type; + using rep_type = TYPENAME cast_traits::rep_type; + constexpr auto c_ratio = detail::cast_ratio(quantity(), To()); if constexpr (treat_as_floating_point) { - return ret(static_cast(static_cast(q.count()) * + return ret(static_cast(static_cast(traits::count(q)) * (static_cast(c_ratio.num) * detail::fpow10(c_ratio.exp) / static_cast(c_ratio.den)))); } else { if constexpr (c_ratio.exp > 0) { - return ret(static_cast(static_cast(q.count()) * + return ret(static_cast(static_cast(traits::count(q)) * (static_cast(c_ratio.num) * static_cast(detail::ipow10(c_ratio.exp))) / static_cast(c_ratio.den))); } else { - return ret(static_cast(static_cast(q.count()) * + return ret(static_cast(static_cast(traits::count(q)) * static_cast(c_ratio.num) / (static_cast(c_ratio.den) * static_cast(detail::ipow10(-c_ratio.exp))))); } @@ -143,11 +151,11 @@ template R * * @tparam ToD a dimension type to use for a target quantity */ -template - requires equivalent -[[nodiscard]] constexpr auto quantity_cast(const quantity& q) +template + requires equivalent::dimension> +[[nodiscard]] constexpr auto quantity_cast(const From& q) { - return quantity_cast, Rep>>(q); + return quantity_cast, typename quantity_traits::rep>>(q); } /** @@ -162,11 +170,11 @@ template * * @tparam ToU a unit type to use for a target quantity */ -template - requires UnitOf -[[nodiscard]] constexpr auto quantity_cast(const quantity& q) +template + requires UnitOf::dimension> +[[nodiscard]] constexpr auto quantity_cast(const From& q) { - return quantity_cast>(q); + return quantity_cast::dimension, ToU, typename quantity_traits::rep>>(q); } /** @@ -185,11 +193,11 @@ template * @tparam ToD a dimension type to use for a target quantity * @tparam ToU a unit type to use for a target quantity */ -template - requires equivalent && UnitOf -[[nodiscard]] constexpr auto quantity_cast(const quantity& q) +template + requires equivalent::dimension> && UnitOf +[[nodiscard]] constexpr auto quantity_cast(const From& q) { - return quantity_cast>(q); + return quantity_cast::rep>>(q); } /** @@ -204,10 +212,11 @@ template * * @tparam ToRep a representation type to use for a target quantity */ -template Rep> -[[nodiscard]] constexpr auto quantity_cast(const quantity& q) +template + requires scalable_with_::rep, ToRep> +[[nodiscard]] constexpr auto quantity_cast(const From& q) { - return quantity_cast>(q); + return quantity_cast::dimension, typename quantity_traits::unit, ToRep>>(q); } /** diff --git a/test/unit_test/static/CMakeLists.txt b/test/unit_test/static/CMakeLists.txt index f9f245e4..dfc5aaef 100644 --- a/test/unit_test/static/CMakeLists.txt +++ b/test/unit_test/static/CMakeLists.txt @@ -24,6 +24,7 @@ cmake_minimum_required(VERSION 3.12) add_library(unit_tests_static cgs_test.cpp + chrono_test.cpp concepts_test.cpp custom_rep_test_min_expl.cpp custom_rep_test_min_impl.cpp diff --git a/test/unit_test/static/chrono_test.cpp b/test/unit_test/static/chrono_test.cpp new file mode 100644 index 00000000..6be21279 --- /dev/null +++ b/test/unit_test/static/chrono_test.cpp @@ -0,0 +1,57 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "test_tools.h" +#include +#include + +namespace { + +using namespace units; +using namespace units::physical; +using namespace units::physical::si::literals; +using namespace std::chrono_literals; + + +// construction +static_assert(std::constructible_from, std::chrono::seconds>); +static_assert(!std::convertible_to>); +static_assert(std::constructible_from, std::chrono::seconds>); +static_assert(!std::convertible_to>); +static_assert(std::constructible_from, std::chrono::hours>); +static_assert(!std::convertible_to>); +static_assert(!std::constructible_from, std::chrono::seconds>); +static_assert(!std::convertible_to>); + +// quantity_cast +static_assert(quantity_cast>(7200s).count() == 2); + +// CTAD +static_assert(std::is_same_v>); +static_assert(std::is_same_v>); + +// operators +static_assert(quantity{1s} + 1_q_s == 2_q_s); +static_assert(quantity{1s} + 1_q_min == 61_q_s); +static_assert(10_q_m / quantity{2s} == 5_q_m_per_s); + +} // namespace diff --git a/test/unit_test/static/concepts_test.cpp b/test/unit_test/static/concepts_test.cpp index fe4986e6..fcdf1518 100644 --- a/test/unit_test/static/concepts_test.cpp +++ b/test/unit_test/static/concepts_test.cpp @@ -24,6 +24,7 @@ #include "units/physical/si/derived/speed.h" #include "units/physical/si/fps/derived/speed.h" #include "units/quantity_point.h" +#include "units/chrono.h" #include #include #include @@ -113,6 +114,13 @@ static_assert(QuantityPoint>); static_assert(!QuantityPoint>); static_assert(!QuantityPoint); +// QuantityLike + +static_assert(QuantityLike); +static_assert(QuantityLike); +static_assert(QuantityLike>); +static_assert(!QuantityLike); + // WrappedQuantity static_assert(wrapped_quantity_>>);