From e9c5f7236ebfe1b287a5a4a49aaf57fbefa87233 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 11 Feb 2025 21:28:15 +0100 Subject: [PATCH] feat: `ConvertibleWithNumber` introduced to improve convertibility of unit `one` with raw numbers Resolves #675 --- .../dimensionless_quantities.md | 7 +-- .../include/mp-units/framework/quantity.h | 51 +++++++++++-------- test/static/quantity_test.cpp | 24 ++++++--- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/docs/users_guide/framework_basics/dimensionless_quantities.md b/docs/users_guide/framework_basics/dimensionless_quantities.md index 6d7b8117..b5c3fca2 100644 --- a/docs/users_guide/framework_basics/dimensionless_quantities.md +++ b/docs/users_guide/framework_basics/dimensionless_quantities.md @@ -166,7 +166,8 @@ inline constexpr auto ppm = parts_per_million; ### Superpowers of the unit `one` -Quantities of the unit `one` are the only ones that are: +Quantities implicitly convertible to `dimensionless` with the unit equivalent to `one` are the only +ones that are: - implicitly constructible from the raw value, - explicitly convertible to a raw value, @@ -185,8 +186,8 @@ This property also expands to usual arithmetic operators. !!! note Those rules do not apply to all the dimensionless quantities. It would be unsafe and misleading - to allow such operations on units with a magnitude different than `1` (e.g., `percent` or - `radian`). + to allow such operations on units with a magnitude different than `1` (e.g., `percent`) or + for quantities that are not implicitly convertible to `dimensionless` (e.g., `angular_measure`). ## Angular quantities diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index c5bc36ca..914e72f4 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -128,6 +128,11 @@ using quantity_like_type = quantity::reference, typename template> concept Mutable = (!std::is_const_v) && std::derived_from; +template +concept ConvertibleWithNumber = + Reference && (implicitly_convertible(get_quantity_spec(R), dimensionless)) && + (equivalent(get_unit(R), one)); + } // namespace detail MP_UNITS_EXPORT_BEGIN @@ -192,7 +197,7 @@ public: } template FwdValue> - requires(unit == ::mp_units::one) + requires detail::ConvertibleWithNumber constexpr explicit(false) quantity(FwdValue&& val) : numerical_value_is_an_implementation_detail_(std::forward(val)) { @@ -218,7 +223,7 @@ public: quantity& operator=(quantity&&) = default; template FwdValue> - requires(unit == ::mp_units::one) + requires detail::ConvertibleWithNumber constexpr quantity& operator=(FwdValue&& val) { numerical_value_is_an_implementation_detail_ = std::forward(val); @@ -308,7 +313,7 @@ public: // conversion operators template Value = std::remove_cvref_t> - requires(unit == ::mp_units::one) + requires detail::ConvertibleWithNumber [[nodiscard]] explicit operator V_() const& noexcept { return numerical_value_is_an_implementation_detail_; @@ -436,10 +441,10 @@ public: } template Q1, QuantityOf Q2> - requires(Q2::unit == ::mp_units::one) && detail::ValuePreservingTo && - requires(rep& a, const Q2::rep b) { - { a *= b } -> std::same_as; - } + requires detail::ConvertibleWithNumber && detail::ValuePreservingTo && + requires(rep& a, const Q2::rep b) { + { a *= b } -> std::same_as; + } friend constexpr decltype(auto) operator*=(Q1&& lhs, const Q2& rhs) { return std::forward(lhs) *= rhs.numerical_value_is_an_implementation_detail_; @@ -459,10 +464,10 @@ public: } template Q1, QuantityOf Q2> - requires(Q2::unit == ::mp_units::one) && detail::ValuePreservingTo && - requires(rep& a, const Q2::rep b) { - { a /= b } -> std::same_as; - } + requires detail::ConvertibleWithNumber && detail::ValuePreservingTo && + requires(rep& a, const Q2::rep b) { + { a /= b } -> std::same_as; + } friend constexpr decltype(auto) operator/=(Q1&& lhs, const Q2& rhs) { return std::forward(lhs) /= rhs.numerical_value_is_an_implementation_detail_; @@ -481,14 +486,16 @@ public: } template Q, Representation Value> - requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> + requires detail::ConvertibleWithNumber && + detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator+(const Q& lhs, const Value& rhs) { return lhs + ::mp_units::quantity{rhs}; } template Q, Representation Value> - requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> + requires detail::ConvertibleWithNumber && + detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator+(const Value& lhs, const Q& rhs) { return ::mp_units::quantity{lhs} + rhs; @@ -506,14 +513,16 @@ public: } template Q, Representation Value> - requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> + requires detail::ConvertibleWithNumber && + detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator-(const Q& lhs, const Value& rhs) { return lhs - ::mp_units::quantity{rhs}; } template Q, Representation Value> - requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> + requires detail::ConvertibleWithNumber && + detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator-(const Value& lhs, const Q& rhs) { return ::mp_units::quantity{lhs} - rhs; @@ -533,14 +542,16 @@ public: } template Q, Representation Value> - requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> + requires detail::ConvertibleWithNumber && + detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator%(const Q& lhs, const Value& rhs) { return lhs % ::mp_units::quantity{rhs}; } template Q, Representation Value> - requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf, Rep, const Value&> + requires detail::ConvertibleWithNumber && + detail::InvokeResultOf, Rep, const Value&> [[nodiscard]] friend constexpr Quantity auto operator%(const Value& lhs, const Q& rhs) { return ::mp_units::quantity{lhs} % rhs; @@ -592,7 +603,7 @@ public: [[nodiscard]] friend constexpr Quantity auto operator/(const Value& val, const Q& q) { MP_UNITS_EXPECTS_DEBUG(is_neq_zero(q)); - return ::mp_units::quantity{val / q.numerical_value_ref_in(unit), ::mp_units::one / R}; + return ::mp_units::quantity{val / q.numerical_value_ref_in(unit), one / R}; } template Q, auto R2, typename Rep2> @@ -607,7 +618,7 @@ public: } template Q, Representation Value> - requires(Q::unit == ::mp_units::one) && std::equality_comparable_with + requires detail::ConvertibleWithNumber && std::equality_comparable_with [[nodiscard]] friend constexpr bool operator==(const Q& lhs, const Value& rhs) { return lhs.numerical_value_ref_in(unit) == rhs; @@ -625,7 +636,7 @@ public: } template Q, Representation Value> - requires(Q::unit == ::mp_units::one) && std::three_way_comparable_with + requires detail::ConvertibleWithNumber && std::three_way_comparable_with [[nodiscard]] friend constexpr auto operator<=>(const Q& lhs, const Value& rhs) { return lhs.numerical_value_ref_in(unit) <=> rhs; diff --git a/test/static/quantity_test.cpp b/test/static/quantity_test.cpp index 8d9a1e0c..5a41d0dc 100644 --- a/test/static/quantity_test.cpp +++ b/test/static/quantity_test.cpp @@ -279,16 +279,16 @@ static_assert(quantity(1500 * m).numerical_value_in(km) == 1.5) static_assert(!std::convertible_to, double>); static_assert(std::constructible_from>); -static_assert(!std::convertible_to, double>); -static_assert(std::constructible_from>); static_assert(!std::convertible_to, int>); static_assert(std::constructible_from>); -static_assert(!std::convertible_to, int>); -static_assert(std::constructible_from>); static_assert(!std::convertible_to, double>); static_assert(std::constructible_from>); -static_assert(!std::convertible_to, double>); -static_assert(std::constructible_from>); +static_assert(!std::convertible_to, double>); +static_assert(std::constructible_from>); +static_assert(!std::convertible_to, int>); +static_assert(std::constructible_from>); +static_assert(!std::convertible_to, double>); +static_assert(std::constructible_from>); #if MP_UNITS_HOSTED static_assert(!std::convertible_to>, std::complex>); static_assert(std::constructible_from, quantity>>); @@ -300,6 +300,18 @@ static_assert(!std::convertible_to, cartesian_vector, quantity>); #endif +static_assert(!std::convertible_to, double>); +static_assert(!std::constructible_from>); +static_assert(!std::convertible_to, int>); +static_assert(!std::constructible_from>); +static_assert(!std::convertible_to, double>); +static_assert(!std::constructible_from>); +static_assert(!std::convertible_to, double>); +static_assert(!std::constructible_from>); +static_assert(!std::convertible_to, int>); +static_assert(!std::constructible_from>); +static_assert(!std::convertible_to, double>); +static_assert(!std::constructible_from>); /////////////////////////////////// // converting to a different unit