diff --git a/src/include/units/bits/common_quantity.h b/src/include/units/bits/common_quantity.h index 30d27193..7278426c 100644 --- a/src/include/units/bits/common_quantity.h +++ b/src/include/units/bits/common_quantity.h @@ -29,6 +29,9 @@ namespace units { template U, Scalar Rep> class quantity; +template U, Scalar Rep> +class quantity_point; + namespace detail { template @@ -57,12 +60,20 @@ struct common_quantity_impl, quantity, Rep> using type = quantity>, Rep>; }; +template +quantity_point common_quantity_point_impl(quantity); + } // namespace detail template> requires equivalent_dim using common_quantity = detail::common_quantity_impl::type; +template + requires requires { typename common_quantity; } +using common_quantity_point = decltype( + detail::common_quantity_point_impl(common_quantity{})); + } // namespace units #if COMP_GCC >= 10 @@ -81,4 +92,10 @@ struct common_type { using type = units::common_quantity; }; +template + requires requires { typename units::common_quantity_point; } +struct common_type { + using type = units::common_quantity_point; +}; + } diff --git a/src/include/units/concepts.h b/src/include/units/concepts.h index 435292f3..a45aadb4 100644 --- a/src/include/units/concepts.h +++ b/src/include/units/concepts.h @@ -221,6 +221,9 @@ namespace detail { template inline constexpr bool is_quantity = false; +template +inline constexpr bool is_quantity_point = false; + } // namespace detail /** @@ -231,6 +234,14 @@ inline constexpr bool is_quantity = false; template concept Quantity = detail::is_quantity; +/** + * @brief A concept matching all quantity points in the library. + * + * Satisfied by all instantiations of :class:`quantity_point`. + */ +template +concept QuantityPoint = detail::is_quantity_point; + // WrappedQuantity namespace detail { diff --git a/src/include/units/quantity_cast.h b/src/include/units/quantity_cast.h index b0a7f77d..5169e84a 100644 --- a/src/include/units/quantity_cast.h +++ b/src/include/units/quantity_cast.h @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace units { @@ -392,4 +393,31 @@ template return quantity_cast>(q); } +/** + * @brief Explcit cast of a quantity point + * + * Implicit conversions between quantity points of different types are allowed only for "safe" + * (i.e. non-truncating) conversion. In other cases an explicit cast has to be used. + * + * This cast gets the target quantity point type to cast to or anything that works for quantity_cast. For example: + * + * auto q1 = units::quantity_point_cast(quantity_point{1q_ms}); + * auto q1 = units::quantity_point_cast>(quantity_point{1q_ms}); + * auto q1 = units::quantity_point_cast(quantity_point{200q_Gal}); + * auto q1 = units::quantity_point_cast(quantity_point{1q_ms}); + * auto q1 = units::quantity_point_cast(quantity_point{1q_ms}); + * + * @tparam CastSpec a target quantity point type to cast to or anything that works for quantity_cast + */ +template + requires is_instantiation || + requires(quantity q) { quantity_cast(q); } +[[nodiscard]] constexpr auto quantity_point_cast(const quantity_point& qp) +{ + if constexpr (is_instantiation) + return quantity_point(quantity_cast(qp.relative())); + else + return quantity_point(quantity_cast(qp.relative())); +} + } // namespace units diff --git a/src/include/units/quantity_point.h b/src/include/units/quantity_point.h new file mode 100644 index 00000000..a881d49e --- /dev/null +++ b/src/include/units/quantity_point.h @@ -0,0 +1,250 @@ + +// 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 + +namespace units { + +/** + * @brief A quantity point + * + * Property of a phenomenon, body, or substance, where the property has a magnitude that can be + * expressed by means of a number and a measurement unit. + * + * @tparam D a dimension of the quantity point (can be either a BaseDimension or a DerivedDimension) + * @tparam U a measurement unit of the quantity point + * @tparam Rep a type to be used to represent values of a quantity point + */ +template U, Scalar Rep = double> +class quantity_point { +public: + using quantity_type = quantity; + using dimension = typename quantity_type::dimension; + using unit = typename quantity_type::unit; + using rep = typename quantity_type::rep; + +private: + quantity_type q_{}; + +public: + quantity_point() = default; + quantity_point(const quantity_point&) = default; + quantity_point(quantity_point&&) = default; + + template + requires std::is_convertible_v + constexpr explicit quantity_point(const Q& q) : q_{q} {} + + template + requires std::is_convertible_v + constexpr quantity_point(const QP2& qp) : q_{qp.relative()} {} + + quantity_point& operator=(const quantity_point&) = default; + quantity_point& operator=(quantity_point&&) = default; + + [[nodiscard]] constexpr quantity_type relative() const noexcept { return q_; } + + template + [[nodiscard]] static constexpr quantity_point min() noexcept + requires requires { Q::min(); } + // requires requires { quantity_type::min(); } // TODO gated by gcc-9 (fixed in gcc-10) + { + return quantity_point(quantity_type::min()); + } + + template + [[nodiscard]] static constexpr quantity_point max() noexcept + requires requires { Q::max(); } + // requires requires { quantity_type::max(); } // TODO gated by gcc-9 (fixed in gcc-10) + { + return quantity_point(quantity_type::max()); + } + + template + requires requires(Q q) { ++q; } + constexpr quantity_point& operator++() + // requires requires(quantity_type) { ++q; } // TODO gated by gcc-9 (fixed in gcc-10) + { + ++q_; + return *this; + } + + template + requires requires(Q q) { q++; } + constexpr quantity_point operator++(int) + // requires requires(quantity_type q) { q++; } // TODO gated by gcc-9 (fixed in gcc-10) + { + return quantity_point(q_++); + } + + template + requires requires(Q q) { --q; } + constexpr quantity_point& operator--() + // requires requires(quantity_type q) { --q; } // TODO gated by gcc-9 (fixed in gcc-10) + { + --q_; + return *this; + } + + template + requires requires(Q q) { q--; } + constexpr quantity_point operator--(int) + // requires requires(quantity_type q) { q--; } // TODO gated by gcc-9 (fixed in gcc-10) + { + return quantity_point(q_--); + } + + template + requires requires(Q q1, Q q2) { q1 += q2; } + constexpr quantity_point& operator+=(const quantity_type& q) + // requires requires(quantity_type q) { q += q; } // TODO gated by gcc-9 (fixed in gcc-10) + { + q_ += q; + return *this; + } + + template + requires requires(Q q1, Q q2) { q1 -= q2; } + constexpr quantity_point& operator-=(const quantity_type& q) + // requires requires(quantity_type q) { q1 -= q2; } // TODO gated by gcc-9 (fixed in gcc-10) + { + q_ -= q; + return *this; + } + + // Hidden Friends + // Below friend functions are to be found via argument-dependent lookup only +#if __GNUC__ >= 10 + + template + [[nodiscard]] friend constexpr auto operator<=>(const quantity_point& lhs, const quantity_point& rhs) + requires requires { lhs.q_ <=> rhs.relative(); } + { + return lhs.q_ <=> rhs.relative(); + } + + template + [[nodiscard]] friend constexpr auto operator==(const quantity_point& lhs, const quantity_point& rhs) + requires requires { lhs.q_ == rhs.relative(); } + { + return lhs.q_ == rhs.relative(); + } + +#else + + template + [[nodiscard]] friend constexpr bool operator==(const quantity_point& lhs, const quantity_point& rhs) + requires equivalent_dim && + std::equality_comparable_with + { + return lhs.q_ == rhs.relative(); + } + + template + [[nodiscard]] friend constexpr bool operator!=(const quantity_point& lhs, const quantity_point& rhs) + requires equivalent_dim && + std::equality_comparable_with + { + return !(lhs == rhs); + } + + template + [[nodiscard]] friend constexpr bool operator<(const quantity_point& lhs, const quantity_point& rhs) + requires equivalent_dim && + std::totally_ordered_with + { + return lhs.q_ < rhs.relative(); + } + + template + [[nodiscard]] friend constexpr bool operator<=(const quantity_point& lhs, const quantity_point& rhs) + requires equivalent_dim && + std::totally_ordered_with + { + return !(rhs < lhs); + } + + template + [[nodiscard]] friend constexpr bool operator>(const quantity_point& lhs, const quantity_point& rhs) + requires equivalent_dim && + std::totally_ordered_with + { + return rhs < lhs; + } + + template + [[nodiscard]] friend constexpr bool operator>=(const quantity_point& lhs, const quantity_point& rhs) + requires equivalent_dim && + std::totally_ordered_with + { + return !(lhs < rhs); + } + +#endif +}; + +template U, Scalar Rep> +quantity_point(quantity) -> quantity_point; + +template +[[nodiscard]] constexpr QuantityPoint AUTO operator+(const quantity_point& lhs, + const quantity& rhs) + requires requires { lhs.relative() + rhs; } +{ + return quantity_point(lhs.relative() + rhs); +} + +template +[[nodiscard]] constexpr QuantityPoint AUTO operator+(const quantity& lhs, + const quantity_point& rhs) + requires requires { rhs + lhs; } +{ + return rhs + lhs; +} + +template +[[nodiscard]] constexpr QuantityPoint AUTO operator-(const quantity_point& lhs, + const quantity& rhs) + requires requires { lhs.relative() - rhs; } +{ + return quantity_point(lhs.relative() - rhs); +} + +template +[[nodiscard]] constexpr Quantity AUTO operator-(const quantity_point& lhs, + const quantity_point& rhs) + requires requires { lhs.relative() - rhs.relative(); } +{ + return lhs.relative() - rhs.relative(); +} + +namespace detail { + +template +inline constexpr bool is_quantity_point> = true; + +} // namespace detail + +} // namespace units diff --git a/test/unit_test/static/CMakeLists.txt b/test/unit_test/static/CMakeLists.txt index 640ecc9b..a5fe55e3 100644 --- a/test/unit_test/static/CMakeLists.txt +++ b/test/unit_test/static/CMakeLists.txt @@ -30,6 +30,7 @@ add_library(unit_tests_static fixed_string_test.cpp fps_test.cpp math_test.cpp + quantity_point_test.cpp quantity_test.cpp ratio_test.cpp si_test.cpp diff --git a/test/unit_test/static/quantity_point_test.cpp b/test/unit_test/static/quantity_point_test.cpp new file mode 100644 index 00000000..b555c26c --- /dev/null +++ b/test/unit_test/static/quantity_point_test.cpp @@ -0,0 +1,254 @@ +// 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 "units/math.h" +#include "units/physical/si/area.h" +#include "units/physical/si/frequency.h" +#include "units/physical/si/speed.h" +#include "units/physical/si/volume.h" +#include "units/physical/us/length.h" +#include "units/quantity_point.h" +#include +#include + +namespace { + +using namespace units; +using namespace units::physical::si; + +// class invariants + +template +concept invalid_types = requires +{ + !requires { typename quantity_point; }; // unit of a different dimension + !requires { typename quantity_point>; }; // quantity used as Rep + // quantity point used as Rep + !requires { typename quantity_point>; }; + !requires { typename quantity; }; // reordered arguments +}; + +static_assert(invalid_types); + +// member types + +static_assert(std::is_same_v::rep, int>); +static_assert(std::is_same_v::rep, double>); +static_assert(std::is_same_v::unit, metre>); +static_assert(std::is_same_v::unit, kilometre>); + +// constructors + +static_assert(quantity_point().relative() == 0q_m); +constexpr quantity_point km{1000q_m}; +static_assert(km.relative() == 1000q_m); +static_assert(quantity_point(km).relative() == km.relative()); + +static_assert(quantity_point(1q_m).relative() == 1q_m); +static_assert(!std::is_constructible_v, double>); // truncating conversion +static_assert(quantity_point(1.0q_m).relative() == 1.0q_m); +static_assert(quantity_point(1q_m).relative() == 1q_m); +static_assert(quantity_point(3.14q_m).relative() == 3.14q_m); + +static_assert(quantity_point(km).relative() == 1000q_m); +static_assert(!std::is_constructible_v, + quantity_point>); // truncating conversion +static_assert(quantity_point(quantity_point(1000.0q_m)).relative() == 1000.0q_m); +static_assert(quantity_point(km).relative() == 1000.0q_m); +static_assert(quantity_point(quantity_point(1q_km)).relative() == 1000q_m); +static_assert(!std::is_constructible_v, + quantity_point>); // different dimensions +static_assert(!std::is_constructible_v, + quantity_point>); // truncating conversion + +// assignment operator + +static_assert([]() { quantity_point l1(1q_m), l2{}; return l2 = l1; }().relative() == 1q_m); + +// static member functions + +static_assert(quantity_point::min().relative().count() == std::numeric_limits::lowest()); +static_assert(quantity_point::max().relative().count() == std::numeric_limits::max()); +static_assert(quantity_point::min().relative().count() == + std::numeric_limits::lowest()); +static_assert(quantity_point::max().relative().count() == + std::numeric_limits::max()); + +// unary member operators + +static_assert([km=km]() mutable { return ++km; }().relative().count() == 1001); +static_assert([km=km]() mutable { return --km; }().relative().count() == 999); +static_assert([km=km]() mutable { return km++; }().relative().count() == 1000); +static_assert([km=km]() mutable { return km--; }().relative().count() == 1000); + +// binary member operators + +static_assert([](auto v) { + auto vv = v++; + return std::pair(v, vv); +}(km) == std::pair(quantity_point(1001q_m), quantity_point(1000q_m))); +static_assert([](auto v) { + auto vv = ++v; + return std::pair(v, vv); +}(km) == std::pair(quantity_point(1001q_m), quantity_point(1001q_m))); +static_assert([](auto v) { + auto vv = v--; + return std::pair(v, vv); +}(km) == std::pair(quantity_point(999q_m), quantity_point(1000q_m))); +static_assert([](auto v) { + auto vv = --v; + return std::pair(v, vv); +}(km) == std::pair(quantity_point(999q_m), quantity_point(999q_m))); + +// compound assignment + +static_assert((quantity_point(1q_m) += 1q_m).relative().count() == 2); +static_assert((quantity_point(2q_m) -= 1q_m).relative().count() == 1); + +// non-member arithmetic operators + +static_assert(std::is_same_v() + length()), + quantity_point>); +static_assert(std::is_same_v() + quantity_point()), + quantity_point>); +static_assert(std::is_same_v() + length()), + quantity_point>); +static_assert(std::is_same_v() + quantity_point()), + quantity_point>); +static_assert(std::is_same_v() - length()), + quantity_point>); +static_assert(std::is_same_v() - length()), + quantity_point>); +static_assert( + std::is_same_v() - quantity_point()), + length>); +static_assert( + std::is_same_v() - quantity_point()), + length>); + +static_assert((1q_m + km).relative().count() == 1001); +static_assert((quantity_point(1q_m) + 1q_km).relative().count() == 1001); +static_assert((km - 1q_m).relative().count() == 999); +static_assert((quantity_point(1q_km) - quantity_point(1q_m)).count() == 999); + +// comparators + +static_assert(quantity_point(2q_m) + 1q_m == quantity_point(3q_m)); +static_assert(!(2q_m + quantity_point(2q_m) == quantity_point(3q_m))); +static_assert(quantity_point(2q_m) + 2q_m != quantity_point(3q_m)); +static_assert(!(2q_m + quantity_point(2q_m) != quantity_point(4q_m))); +static_assert(quantity_point(2q_m) > quantity_point(1q_m)); +static_assert(!(quantity_point(1q_m) > quantity_point(1q_m))); +static_assert(quantity_point(1q_m) < quantity_point(2q_m)); +static_assert(!(quantity_point(2q_m) < quantity_point(2q_m))); +static_assert(quantity_point(2q_m) >= quantity_point(1q_m)); +static_assert(quantity_point(2q_m) >= quantity_point(2q_m)); +static_assert(!(quantity_point(2q_m) >= quantity_point(3q_m))); +static_assert(quantity_point(1q_m) <= quantity_point(2q_m)); +static_assert(quantity_point(2q_m) <= quantity_point(2q_m)); +static_assert(!(quantity_point(3q_m) <= quantity_point(2q_m))); + +static_assert(quantity_point(3q_m) == quantity_point(3.0q_m)); +static_assert(quantity_point(3q_m) != quantity_point(3.14q_m)); +static_assert(quantity_point(2q_m) > quantity_point(1.0q_m)); +static_assert(quantity_point(1.0q_m) < quantity_point(2q_m)); +static_assert(quantity_point(2.0q_m) >= quantity_point(1q_m)); +static_assert(quantity_point(1q_m) <= quantity_point(2.0q_m)); + +static_assert(quantity_point(1000q_m) == quantity_point(1q_km)); +static_assert(quantity_point(1001q_m) != quantity_point(1q_km)); +static_assert(quantity_point(1001q_m) > quantity_point(1q_km)); +static_assert(quantity_point(999q_m) < quantity_point(1q_km)); +static_assert(quantity_point(1000q_m) >= quantity_point(1q_km)); +static_assert(quantity_point(1000q_m) <= quantity_point(1q_km)); + +// alias units + +static_assert(quantity_point(2q_l) + 2q_ml == quantity_point(2002q_ml)); +static_assert(2q_l + quantity_point(2q_ml) == quantity_point(2002q_cm3)); +static_assert(quantity_point(2q_l) + 2q_cm3 == quantity_point(2002q_ml)); +static_assert(2q_dm3 + quantity_point(2q_cm3) == quantity_point(2002q_ml)); + +// is_quantity_point + +static_assert(QuantityPoint>); + +// common_quantity_point + +static_assert(std::is_same_v< + common_quantity_point, quantity_point>, + quantity_point>); +static_assert(std::is_same_v, + quantity_point>, + quantity_point>); +static_assert(std::is_same_v, + quantity_point>, + quantity_point>); + +// common_type + +using namespace units::physical::us::literals; + +static_assert(std::equality_comparable); +static_assert(std::equality_comparable_with); +static_assert(quantity_point(0q_m) == quantity_point(0q_ft_us)); +static_assert(std::equality_comparable_with); + +// quantity_cast + +static_assert( + std::is_same_v, metre>>(quantity_point(2q_km)))::unit, metre>); + +static_assert(quantity_point_cast>(quantity_point(2q_km)).relative().count() == + 2000); +static_assert( + quantity_point_cast>(quantity_point(2000q_m)).relative().count() == 2); +static_assert(quantity_point_cast>(quantity_point(1.23q_m)).relative().count() == + 1); +static_assert(quantity_point_cast>(quantity_point(2q_km)).relative().count() == 2000); +static_assert(quantity_point_cast>(quantity_point(2000q_m)).relative().count() == 2); +static_assert(quantity_point_cast>(quantity_point(1.23q_m)).relative().count() == 1); +static_assert(quantity_point_cast(quantity_point(2q_km)).relative().count() == 2000); +static_assert(quantity_point_cast(quantity_point(2000q_m)).relative().count() == 2); +static_assert(quantity_point_cast(quantity_point(1.23q_m)).relative().count() == 1); + +// time + +static_assert(!std::equality_comparable_with, + quantity_point>); // different dimensions +static_assert(quantity_point{1q_h} == quantity_point{3600q_s}); + +// length + +static_assert(quantity_point(1q_km) == quantity_point(1000q_m)); +static_assert(quantity_point(1q_km) + 1q_m == quantity_point(1001q_m)); +static_assert(1q_km + quantity_point(1q_m) == quantity_point(1001q_m)); + +template +concept dimensional_analysis = requires(T t) +{ + pow<2>(t); +}; + +static_assert(!dimensional_analysis>); + +} // namespace