diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index 9703bb77..c4b85cdf 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -35,6 +35,7 @@ #include #include #include +#include #ifndef MP_UNITS_IN_MODULE_INTERFACE #include @@ -67,7 +68,8 @@ concept ValuePreservingTo = Representation> && Repr Unit && Unit && std::assignable_from && (IsFloatingPoint || (!IsFloatingPoint> && - (integral_conversion_factor(FromUnit, ToUnit)))); + integral_conversion_factor(FromUnit, ToUnit) && + might_store_converted_value(FromUnit, ToUnit))); template concept QuantityConvertibleTo = @@ -100,12 +102,21 @@ template using common_quantity_for = quantity>; +template +[[nodiscard]] consteval bool might_store_converted_common_value(U1 u1, U2 u2) +{ + constexpr Unit auto cu = get_common_unit(u1, u2); + return might_store_converted_value(u1, cu) && might_store_converted_value(u2, cu); +} + template concept CommonlyInvocableQuantities = Quantity && Quantity && HaveCommonReference && std::convertible_to> && std::convertible_to> && - InvocableQuantities; + InvocableQuantities && + might_store_converted_common_value>(Q1::unit, + Q2::unit); template concept SameValueAs = (equivalent(get_unit(R1), get_unit(R2))) && std::convertible_to; @@ -639,6 +650,8 @@ template requires requires { { mp_units::get_common_reference(Q1::reference, Q2::reference) } -> mp_units::Reference; typename std::common_type_t; + requires( + might_store_converted_common_value>(Q1::unit, Q2::unit)); requires mp_units::RepresentationOf, mp_units::get_common_quantity_spec(Q1::quantity_spec, Q2::quantity_spec)>; } diff --git a/src/core/include/mp-units/framework/value_cast.h b/src/core/include/mp-units/framework/value_cast.h index 064533a6..c5bece13 100644 --- a/src/core/include/mp-units/framework/value_cast.h +++ b/src/core/include/mp-units/framework/value_cast.h @@ -25,6 +25,7 @@ // IWYU pragma: private, include #include #include +#include #include #include #include @@ -34,6 +35,29 @@ MP_UNITS_EXPORT namespace mp_units { +namespace detail { + +template +[[nodiscard]] consteval bool might_store_converted_value(UFrom from, UTo to) +{ + if constexpr (is_same_v || treat_as_floating_point) + return true; + else if constexpr (std::totally_ordered_with && + requires(Rep v) { representation_values::max(); }) { + constexpr auto factor = + get_value(numerator(get_canonical_unit(from).mag / get_canonical_unit(to).mag)); + if constexpr (std::is_integral_v) + return std::in_range(factor); + else + return factor <= representation_values::max(); + } else + // if the representation is not totally ordered with std::uintmax_t or does not have max() defined + // then we assume that it might store any value + return true; +} + +} // namespace detail + /** * @brief Explicit cast of a quantity's unit * @@ -45,7 +69,8 @@ namespace mp_units { * @tparam ToU a unit to use for a target quantity */ template> - requires detail::UnitCompatibleWith + requires detail::UnitCompatibleWith && + (detail::might_store_converted_value(Q::unit, ToU)) [[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) { return detail::sudo_cast>( @@ -82,6 +107,7 @@ template> requires detail::UnitCompatibleWith && + (detail::might_store_converted_value(Q::unit, ToU)) && RepresentationOf && std::constructible_from [[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) { @@ -90,6 +116,7 @@ template> requires detail::UnitCompatibleWith && + (detail::might_store_converted_value(Q::unit, ToU)) && RepresentationOf && std::constructible_from [[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) { @@ -113,6 +140,7 @@ template> requires detail::UnitCompatibleWith && + (detail::might_store_converted_value(Q::unit, ToQ::unit)) && (ToQ::quantity_spec == Q::quantity_spec) && std::constructible_from [[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) { @@ -130,7 +158,8 @@ template> * @tparam ToU a unit to use for a target quantity point */ template> - requires detail::UnitCompatibleWith + requires detail::UnitCompatibleWith && + (detail::might_store_converted_value(QP::unit, ToU)) [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { return quantity_point{value_cast(std::forward(qp).quantity_from_origin_is_an_implementation_detail_), @@ -168,6 +197,7 @@ template> requires detail::UnitCompatibleWith && + (detail::might_store_converted_value(QP::unit, ToU)) && RepresentationOf && std::constructible_from [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { @@ -178,6 +208,7 @@ template> requires detail::UnitCompatibleWith && + (detail::might_store_converted_value(QP::unit, ToU)) && RepresentationOf && std::constructible_from [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { @@ -202,6 +233,7 @@ template> requires detail::UnitCompatibleWith && + (detail::might_store_converted_value(QP::unit, ToQ::unit)) && (ToQ::quantity_spec == QP::quantity_spec) && std::constructible_from [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { @@ -239,6 +271,7 @@ template> requires detail::UnitCompatibleWith && + (detail::might_store_converted_value(QP::unit, ToQP::unit)) && (ToQP::quantity_spec == QP::quantity_spec) && (detail::same_absolute_point_origins(ToQP::point_origin, QP::point_origin)) && std::constructible_from diff --git a/src/core/include/mp-units/math.h b/src/core/include/mp-units/math.h index 1bfb3b7e..de1dc2e3 100644 --- a/src/core/include/mp-units/math.h +++ b/src/core/include/mp-units/math.h @@ -405,16 +405,16 @@ template */ template [[nodiscard]] constexpr Quantity auto inverse(const quantity& q) - requires requires { + requires(detail::might_store_converted_value(one / get_unit(R), To)) && requires { representation_values::one(); value_cast(representation_values::one() / q); } { if constexpr (AssociatedUnit) { constexpr QuantitySpec auto qs = get_quantity_spec(To) * quantity::quantity_spec; - return qs(representation_values::one() * one).force_in(To * quantity::unit) / q; + return qs(representation_values::one() * one).force_in(To * q.unit) / q; } else - return (representation_values::one() * one).force_in(To * quantity::unit) / q; + return (representation_values::one() * one).force_in(To * q.unit) / q; } /** diff --git a/test/static/math_test.cpp b/test/static/math_test.cpp index a033514a..a424c2c7 100644 --- a/test/static/math_test.cpp +++ b/test/static/math_test.cpp @@ -278,4 +278,9 @@ static_assert(compare(kind_of(inverse(1. * kHz)), 0.001 * s)); // check if constraints work properly for a derived unit of a narrowed kind static_assert(compare(kind_of(inverse(1 * s)), 1 * Hz)); +// overflow in conversion +template +concept overflowing_inverse = requires { requires !requires { inverse(Q); }; }; +static_assert(overflowing_inverse<10'000'000 * si::femto>); + } // namespace diff --git a/test/static/quantity_test.cpp b/test/static/quantity_test.cpp index 7ae481be..c15322f1 100644 --- a/test/static/quantity_test.cpp +++ b/test/static/quantity_test.cpp @@ -1364,4 +1364,20 @@ static_assert(!QuantityOf); static_assert(QuantityOf); static_assert(QuantityOf); // derived unnamed quantity +// overflowing unit conversions +template +concept overflowing_unit_conversion = requires { + requires !requires { quantity(Q); }; + requires !requires { quantity, std::int16_t>(Q); }; + requires !requires { Q.in(si::metre); }; + requires !requires { Q.force_in(si::metre); }; + requires !requires { Q + std::int8_t(1) * nm; }; // promotion to int + requires !requires { Q - std::int8_t(1) * nm; }; // promotion to int + requires !requires { Q % std::int8_t(1) * m; }; + requires !requires { Q == std::int8_t(1) * m; }; + requires !requires { Q < std::int8_t(1) * m; }; + requires !requires { typename std::common_type_t>; }; +}; +static_assert(overflowing_unit_conversion); + } // namespace