diff --git a/docs/getting_started/look_and_feel.md b/docs/getting_started/look_and_feel.md index a8bfb824..1c1050e5 100644 --- a/docs/getting_started/look_and_feel.md +++ b/docs/getting_started/look_and_feel.md @@ -24,7 +24,7 @@ Here is a small example of operations possible on scalar quantities: static_assert(2 * m * (3 * m) == 6 * m2); - static_assert(10 * km / (5 * km) == 2 * one); + static_assert(10 * km / (5 * km) == 2); static_assert(1000 / (1 * s) == 1 * kHz); ``` @@ -51,7 +51,7 @@ Here is a small example of operations possible on scalar quantities: static_assert(2 * m * (3 * m) == 6 * m2); - static_assert(10 * km / (5 * km) == 2 * one); + static_assert(10 * km / (5 * km) == 2); static_assert(1000 / (1 * s) == 1 * kHz); ``` diff --git a/docs/users_guide/framework_basics/dimensionless_quantities.md b/docs/users_guide/framework_basics/dimensionless_quantities.md index 33bfc9b9..3cb3d089 100644 --- a/docs/users_guide/framework_basics/dimensionless_quantities.md +++ b/docs/users_guide/framework_basics/dimensionless_quantities.md @@ -164,6 +164,21 @@ inline constexpr struct parts_per_million final : named_unit<"ppm", mag_ratio<1, inline constexpr auto ppm = parts_per_million; ``` +### Superpowers of the unit `one` + +Quantities of the unit `one` are the only ones that are implicitly convertible from a raw value +and explicitly convertible to it. This property also expands to usual arithmetic operators. + +Thanks to the above, we can type: + +```cpp +quantity inc(quantity q) { return q + 1; } +void legacy(double) { /* ... */ } + +if (auto q = inc(42); q != 0) + legacy(static_cast(q)); +``` + ## Angular quantities diff --git a/example/kalman_filter/kalman_filter-example_1.cpp b/example/kalman_filter/kalman_filter-example_1.cpp index 7bfb370a..da718a98 100644 --- a/example/kalman_filter/kalman_filter-example_1.cpp +++ b/example/kalman_filter/kalman_filter-example_1.cpp @@ -64,7 +64,7 @@ int main() state next = initial_guess; for (int index = 1; const auto& measurement : measurements) { const state& previous = next; - const quantity gain = 1. / index * one; + const quantity gain = 1. / index; const state current = state_update(previous, measurement, gain); next = current; print(index++, gain, measurement, current, next); diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index 279e81a7..b601c307 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -177,6 +177,12 @@ public: { } + template Value> + requires(unit == ::mp_units::one) + constexpr explicit(false) quantity(Value&& v) : numerical_value_is_an_implementation_detail_(std::forward(v)) + { + } + template Q> // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) constexpr explicit(!std::convertible_to) quantity(const Q& q) : @@ -199,6 +205,14 @@ public: quantity& operator=(const quantity&) = default; quantity& operator=(quantity&&) = default; + template Value> + requires(unit == ::mp_units::one) + constexpr quantity& operator=(Value&& v) + { + numerical_value_is_an_implementation_detail_ = std::forward(v); + return *this; + } + // unit conversions template ToU> requires detail::QuantityConvertibleTo> @@ -281,6 +295,13 @@ public: } // conversion operators + template Value = std::remove_cvref_t> + requires(unit == ::mp_units::one) + [[nodiscard]] explicit operator V_() const& noexcept + { + return numerical_value_is_an_implementation_detail_; + } + template> requires detail::QuantityConvertibleTo> [[nodiscard]] explicit(is_specialization_of::from_numerical_value( @@ -480,6 +501,22 @@ public: ret::reference}; } + template Q, RepresentationOf Value> + requires(Q::unit == ::mp_units::one) && + 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, RepresentationOf Value> + requires(Q::unit == ::mp_units::one) && + detail::InvokeResultOf, Rep, const Value&> + [[nodiscard]] friend constexpr Quantity auto operator+(const Value& lhs, const Q& rhs) + { + return ::mp_units::quantity{lhs} + rhs; + } + template Q, auto R2, typename Rep2> requires detail::CommonlyInvocableQuantities, quantity, quantity> [[nodiscard]] friend constexpr Quantity auto operator-(const Q& lhs, const quantity& rhs) @@ -491,6 +528,22 @@ public: ret::reference}; } + template Q, RepresentationOf Value> + requires(Q::unit == ::mp_units::one) && + 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, RepresentationOf Value> + requires(Q::unit == ::mp_units::one) && + detail::InvokeResultOf, Rep, const Value&> + [[nodiscard]] friend constexpr Quantity auto operator-(const Value& lhs, const Q& rhs) + { + return ::mp_units::quantity{lhs} - rhs; + } + template Q, auto R2, typename Rep2> requires(!treat_as_floating_point) && (!treat_as_floating_point) && detail::CommonlyInvocableQuantities, quantity, quantity> @@ -504,6 +557,22 @@ public: ret::reference}; } + template Q, RepresentationOf Value> + requires(Q::unit == ::mp_units::one) && + 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, RepresentationOf Value> + requires(Q::unit == ::mp_units::one) && + detail::InvokeResultOf, Rep, const Value&> + [[nodiscard]] friend constexpr Quantity auto operator%(const Value& lhs, const Q& rhs) + { + return ::mp_units::quantity{lhs} % rhs; + } + template Q, auto R2, typename Rep2> requires detail::InvocableQuantities, quantity, quantity> [[nodiscard]] friend constexpr Quantity auto operator*(const Q& lhs, const quantity& rhs) @@ -563,6 +632,13 @@ public: return ct_lhs.numerical_value_ref_in(ct::unit) == ct_rhs.numerical_value_ref_in(ct::unit); } + template Q, RepresentationOf Value> + requires(Q::unit == ::mp_units::one) && std::equality_comparable_with + [[nodiscard]] friend constexpr bool operator==(const Q& lhs, const Value& rhs) + { + return lhs.numerical_value_ref_in(unit) == rhs; + } + template Q, auto R2, typename Rep2> requires requires { typename std::common_type_t>; } && std::three_way_comparable>::rep> @@ -573,6 +649,13 @@ public: const ct ct_rhs(rhs); return ct_lhs.numerical_value_ref_in(ct::unit) <=> ct_rhs.numerical_value_ref_in(ct::unit); } + + template Q, RepresentationOf Value> + requires(Q::unit == ::mp_units::one) && std::three_way_comparable_with + [[nodiscard]] friend constexpr auto operator<=>(const Q& lhs, const Value& rhs) + { + return lhs.numerical_value_ref_in(unit) <=> rhs; + } }; // CTAD @@ -599,7 +682,16 @@ template typename std::common_type_t; } struct std::common_type { -public: using type = mp_units::quantity>; }; + +template Value> + requires(Q::unit == mp_units::one) && requires { typename std::common_type_t; } +struct std::common_type { + using type = mp_units::quantity>; +}; + +template Value> + requires(Q::unit == mp_units::one) && requires { typename std::common_type_t; } +struct std::common_type : std::common_type {}; diff --git a/test/static/quantity_test.cpp b/test/static/quantity_test.cpp index 8d3b68f7..d609faea 100644 --- a/test/static/quantity_test.cpp +++ b/test/static/quantity_test.cpp @@ -126,9 +126,9 @@ static_assert(quantity::min().numerical_value_in(m) == s static_assert(quantity::max().numerical_value_in(m) == std::numeric_limits::max()); -////////////////////////////// -// construction from a value -////////////////////////////// +///////////////////////////////////////////////// +// no construction from value (unless unit one) +///////////////////////////////////////////////// // construction from a value is private static_assert(!std::constructible_from, double>); @@ -137,11 +137,17 @@ static_assert(!std::convertible_to>); static_assert(!std::constructible_from, int>); static_assert(!std::convertible_to>); -static_assert(!std::constructible_from, double>); -static_assert(!std::convertible_to>); +static_assert(std::constructible_from, double>); +static_assert(std::convertible_to>); -static_assert(!std::constructible_from, int>); -static_assert(!std::convertible_to>); +static_assert(std::constructible_from, int>); +static_assert(std::convertible_to>); + +static_assert(std::constructible_from, double>); +static_assert(std::convertible_to>); + +static_assert(std::constructible_from, int>); +static_assert(std::convertible_to>); /////////////////////////////////////// @@ -200,6 +206,24 @@ static_assert(quantity(2 * km).numerical_value_in(km) == 2 static_assert(quantity(1500 * m).numerical_value_in(km) == 1.5); +//////////////////////////////////////////////////////// +// explicit conversion to a number (when unit is one) +//////////////////////////////////////////////////////// + +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>); + + /////////////////////////////////// // converting to a different unit /////////////////////////////////// @@ -320,6 +344,10 @@ static_assert(quantity{123. * m}.unit == si::metre); static_assert(quantity{123. * m}.quantity_spec == kind_of); static_assert(quantity{123. * h}.unit == si::hour); static_assert(quantity{123. * h}.quantity_spec == kind_of); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(quantity{123}.unit == one); +static_assert(quantity{123}.quantity_spec == kind_of); #if MP_UNITS_HOSTED using namespace std::chrono_literals; @@ -350,6 +378,11 @@ static_assert([]() { }() .numerical_value_in(m) == 1); +static_assert([]() { + quantity q(1); + return q = 2; +}() + .numerical_value_in(one) == 2); //////////////////// // unary operators @@ -752,6 +785,17 @@ static_assert(4 / (2 * one) == 2 * one); static_assert(4 * one / 2 == 2 * one); static_assert(4 * one % (2 * one) == 0 * one); +static_assert(1 * one + 1 == 2); +static_assert(1 + 1 * one == 2); +static_assert(2 * one - 1 == 1); +static_assert(2 - 1 * one == 1); +static_assert(1 * one + 1.23 == 2.23); +static_assert(1 + 1.23 * one == 2.23); +static_assert(2.23 * one - 1 == 1.23); +static_assert(2.23 - 1 * one == 1.23); +static_assert(4 * one % (2) == 0); +static_assert(4 % (2 * one) == 0); + static_assert(2 * rad * (2 * rad) == 4 * pow<2>(rad)); // modulo arithmetics @@ -912,6 +956,9 @@ static_assert(!(123 * km > 321'000 * m)); static_assert(!(123 * km > 123'000 * m)); static_assert(!(123 * km >= 321'000 * m)); +static_assert(1 * one < 2); +static_assert(1 < 2 * one); + ////////////////// // dimensionless @@ -925,6 +972,32 @@ static_assert(50. * m / (100. * m) == 50 * percent); static_assert((50. * percent).numerical_value_in(one) == 0.5); +////////////////// +// common_type +////////////////// + +static_assert( + is_same_v, quantity<(isq::length / isq::time)[m / s], double>>, + quantity>); +static_assert(is_same_v(isq::length / isq::time))[J], double>, + quantity>, + quantity>); + +#if MP_UNITS_HOSTED +static_assert(is_same_v, std::chrono::seconds>, std::chrono::seconds>); +#endif + +static_assert(is_same_v, int>, quantity>); +// static_assert(is_same_v, double>, quantity>); +// static_assert(is_same_v, int>, quantity>); +static_assert(is_same_v, int>, + quantity>); +// static_assert(is_same_v, double>, +// quantity>); +// static_assert(is_same_v, int>, +// quantity>); + + ////////////////// // value_cast //////////////////