feat: ConvertibleWithNumber introduced to improve convertibility of unit one with raw numbers

Resolves #675
This commit is contained in:
Mateusz Pusz
2025-02-11 21:28:15 +01:00
parent 836880c3a1
commit e9c5f7236e
3 changed files with 53 additions and 29 deletions

View File

@ -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

View File

@ -128,6 +128,11 @@ using quantity_like_type = quantity<quantity_like_traits<T>::reference, typename
template<typename T, typename U, typename TT = std::remove_reference_t<T>>
concept Mutable = (!std::is_const_v<TT>) && std::derived_from<TT, U>;
template<auto R>
concept ConvertibleWithNumber =
Reference<MP_UNITS_REMOVE_CONST(decltype(R))> && (implicitly_convertible(get_quantity_spec(R), dimensionless)) &&
(equivalent(get_unit(R), one));
} // namespace detail
MP_UNITS_EXPORT_BEGIN
@ -192,7 +197,7 @@ public:
}
template<detail::ValuePreservingTo<Rep> FwdValue>
requires(unit == ::mp_units::one)
requires detail::ConvertibleWithNumber<reference>
constexpr explicit(false) quantity(FwdValue&& val) :
numerical_value_is_an_implementation_detail_(std::forward<FwdValue>(val))
{
@ -218,7 +223,7 @@ public:
quantity& operator=(quantity&&) = default;
template<detail::ValuePreservingTo<Rep> FwdValue>
requires(unit == ::mp_units::one)
requires detail::ConvertibleWithNumber<reference>
constexpr quantity& operator=(FwdValue&& val)
{
numerical_value_is_an_implementation_detail_ = std::forward<FwdValue>(val);
@ -308,7 +313,7 @@ public:
// conversion operators
template<typename V_, std::constructible_from<Rep> Value = std::remove_cvref_t<V_>>
requires(unit == ::mp_units::one)
requires detail::ConvertibleWithNumber<reference>
[[nodiscard]] explicit operator V_() const& noexcept
{
return numerical_value_is_an_implementation_detail_;
@ -436,10 +441,10 @@ public:
}
template<detail::Mutable<quantity> Q1, QuantityOf<dimensionless> Q2>
requires(Q2::unit == ::mp_units::one) && detail::ValuePreservingTo<typename Q2::rep, Rep> &&
requires(rep& a, const Q2::rep b) {
{ a *= b } -> std::same_as<rep&>;
}
requires detail::ConvertibleWithNumber<Q2::reference> && detail::ValuePreservingTo<typename Q2::rep, Rep> &&
requires(rep& a, const Q2::rep b) {
{ a *= b } -> std::same_as<rep&>;
}
friend constexpr decltype(auto) operator*=(Q1&& lhs, const Q2& rhs)
{
return std::forward<Q1>(lhs) *= rhs.numerical_value_is_an_implementation_detail_;
@ -459,10 +464,10 @@ public:
}
template<detail::Mutable<quantity> Q1, QuantityOf<dimensionless> Q2>
requires(Q2::unit == ::mp_units::one) && detail::ValuePreservingTo<typename Q2::rep, Rep> &&
requires(rep& a, const Q2::rep b) {
{ a /= b } -> std::same_as<rep&>;
}
requires detail::ConvertibleWithNumber<Q2::reference> && detail::ValuePreservingTo<typename Q2::rep, Rep> &&
requires(rep& a, const Q2::rep b) {
{ a /= b } -> std::same_as<rep&>;
}
friend constexpr decltype(auto) operator/=(Q1&& lhs, const Q2& rhs)
{
return std::forward<Q1>(lhs) /= rhs.numerical_value_is_an_implementation_detail_;
@ -481,14 +486,16 @@ public:
}
template<std::derived_from<quantity> Q, Representation Value>
requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf<quantity_spec, std::plus<>, Rep, const Value&>
requires detail::ConvertibleWithNumber<Q::reference> &&
detail::InvokeResultOf<quantity_spec, std::plus<>, Rep, const Value&>
[[nodiscard]] friend constexpr Quantity auto operator+(const Q& lhs, const Value& rhs)
{
return lhs + ::mp_units::quantity{rhs};
}
template<std::derived_from<quantity> Q, Representation Value>
requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf<quantity_spec, std::plus<>, Rep, const Value&>
requires detail::ConvertibleWithNumber<Q::reference> &&
detail::InvokeResultOf<quantity_spec, std::plus<>, 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<std::derived_from<quantity> Q, Representation Value>
requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf<quantity_spec, std::minus<>, Rep, const Value&>
requires detail::ConvertibleWithNumber<Q::reference> &&
detail::InvokeResultOf<quantity_spec, std::minus<>, Rep, const Value&>
[[nodiscard]] friend constexpr Quantity auto operator-(const Q& lhs, const Value& rhs)
{
return lhs - ::mp_units::quantity{rhs};
}
template<std::derived_from<quantity> Q, Representation Value>
requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf<quantity_spec, std::minus<>, Rep, const Value&>
requires detail::ConvertibleWithNumber<Q::reference> &&
detail::InvokeResultOf<quantity_spec, std::minus<>, 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<std::derived_from<quantity> Q, Representation Value>
requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf<quantity_spec, std::modulus<>, Rep, const Value&>
requires detail::ConvertibleWithNumber<Q::reference> &&
detail::InvokeResultOf<quantity_spec, std::modulus<>, Rep, const Value&>
[[nodiscard]] friend constexpr Quantity auto operator%(const Q& lhs, const Value& rhs)
{
return lhs % ::mp_units::quantity{rhs};
}
template<std::derived_from<quantity> Q, Representation Value>
requires(Q::unit == ::mp_units::one) && detail::InvokeResultOf<quantity_spec, std::modulus<>, Rep, const Value&>
requires detail::ConvertibleWithNumber<Q::reference> &&
detail::InvokeResultOf<quantity_spec, std::modulus<>, 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<std::derived_from<quantity> Q, auto R2, typename Rep2>
@ -607,7 +618,7 @@ public:
}
template<std::derived_from<quantity> Q, Representation Value>
requires(Q::unit == ::mp_units::one) && std::equality_comparable_with<Rep, Value>
requires detail::ConvertibleWithNumber<Q::reference> && std::equality_comparable_with<Rep, Value>
[[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<std::derived_from<quantity> Q, Representation Value>
requires(Q::unit == ::mp_units::one) && std::three_way_comparable_with<Rep, Value>
requires detail::ConvertibleWithNumber<Q::reference> && std::three_way_comparable_with<Rep, Value>
[[nodiscard]] friend constexpr auto operator<=>(const Q& lhs, const Value& rhs)
{
return lhs.numerical_value_ref_in(unit) <=> rhs;

View File

@ -279,16 +279,16 @@ static_assert(quantity<isq::length[km]>(1500 * m).numerical_value_in(km) == 1.5)
static_assert(!std::convertible_to<quantity<one>, double>);
static_assert(std::constructible_from<double, quantity<one>>);
static_assert(!std::convertible_to<quantity<isq::angular_measure[one]>, double>);
static_assert(std::constructible_from<double, quantity<isq::angular_measure[one]>>);
static_assert(!std::convertible_to<quantity<one>, int>);
static_assert(std::constructible_from<int, quantity<one>>);
static_assert(!std::convertible_to<quantity<isq::angular_measure[one]>, int>);
static_assert(std::constructible_from<int, quantity<isq::angular_measure[one]>>);
static_assert(!std::convertible_to<quantity<one, int>, double>);
static_assert(std::constructible_from<double, quantity<one, int>>);
static_assert(!std::convertible_to<quantity<isq::angular_measure[one], int>, double>);
static_assert(std::constructible_from<double, quantity<isq::angular_measure[one], int>>);
static_assert(!std::convertible_to<quantity<isq::rotation[one]>, double>);
static_assert(std::constructible_from<double, quantity<isq::rotation[one]>>);
static_assert(!std::convertible_to<quantity<isq::rotation[one]>, int>);
static_assert(std::constructible_from<int, quantity<isq::rotation[one]>>);
static_assert(!std::convertible_to<quantity<isq::rotation[one], int>, double>);
static_assert(std::constructible_from<double, quantity<isq::rotation[one], int>>);
#if MP_UNITS_HOSTED
static_assert(!std::convertible_to<quantity<one, std::complex<double>>, std::complex<double>>);
static_assert(std::constructible_from<std::complex<double>, quantity<one, std::complex<double>>>);
@ -300,6 +300,18 @@ static_assert(!std::convertible_to<quantity<one, double>, cartesian_vector<doubl
static_assert(std::constructible_from<cartesian_vector<double>, quantity<one, double>>);
#endif
static_assert(!std::convertible_to<quantity<rad>, double>);
static_assert(!std::constructible_from<double, quantity<rad>>);
static_assert(!std::convertible_to<quantity<rad>, int>);
static_assert(!std::constructible_from<int, quantity<rad>>);
static_assert(!std::convertible_to<quantity<rad, int>, double>);
static_assert(!std::constructible_from<double, quantity<rad, int>>);
static_assert(!std::convertible_to<quantity<isq::angular_measure[one]>, double>);
static_assert(!std::constructible_from<double, quantity<isq::angular_measure[one]>>);
static_assert(!std::convertible_to<quantity<isq::angular_measure[one]>, int>);
static_assert(!std::constructible_from<int, quantity<isq::angular_measure[one]>>);
static_assert(!std::convertible_to<quantity<isq::angular_measure[one], int>, double>);
static_assert(!std::constructible_from<double, quantity<isq::angular_measure[one], int>>);
///////////////////////////////////
// converting to a different unit