diff --git a/docs/users_guide/framework_basics/basic_concepts.md b/docs/users_guide/framework_basics/basic_concepts.md index 478ae14f9..47ab42f0d 100644 --- a/docs/users_guide/framework_basics/basic_concepts.md +++ b/docs/users_guide/framework_basics/basic_concepts.md @@ -427,7 +427,12 @@ for which an instantiation of `quantity_like_traits` type trait yields a valid t - Static data member `reference` that matches the [`Reference`](#Reference) concept, - `rep` type that matches [`RepresentationOf`](#RepresentationOf) concept with the character provided in `reference`, -- `value(T)` static member function returning a raw value of the quantity. +- `to_numerical_value(T)` static member function returning a raw value of the quantity packed in + either `convert_explicitly` or `convert_implicitly` wrapper that enables implicit conversion in + the latter case, +- `from_numerical_value(rep)` static member function returning `T` packed in either `convert_explicitly` + or `convert_implicitly` wrapper that enables implicit conversion in the latter case. + ??? abstract "Examples" @@ -438,10 +443,20 @@ for which an instantiation of `quantity_like_traits` type trait yields a valid t struct mp_units::quantity_like_traits { static constexpr auto reference = si::second; using rep = std::chrono::seconds::rep; - [[nodiscard]] static constexpr rep value(const std::chrono::seconds& q) { return q.count(); } + + [[nodiscard]] static constexpr convert_implicitly to_numerical_value(const std::chrono::seconds& q) + { + return q.count(); + } + + [[nodiscard]] static constexpr convert_implicitly from_numerical_value(const rep& v) + { + return std::chrono::seconds(v); + } }; - quantity q(42s); + quantity q = 42s; + std::chrono::seconds dur = 42 * s; ``` @@ -454,8 +469,13 @@ for which an instantiation of `quantity_point_like_traits` type trait yields a v - Static data member `point_origin` that matches the [`PointOrigin`](#PointOrigin) concept - `rep` type that matches [`RepresentationOf`](#RepresentationOf) concept with the character provided in `reference` -- `quantity_from_origin(T)` static member function returning the `quantity` being the offset of the point - from the origin +- `to_quantity(T)` static member function returning the `quantity` being the offset of the point + from the origin packed in either `convert_explicitly` or `convert_implicitly` wrapper that enables + implicit conversion in the latter case, +- `from_quantity(quantity)` static member function returning `T` packed in either + `convert_explicitly` or `convert_implicitly` wrapper that enables implicit conversion in the latter + case. + ??? abstract "Examples" @@ -464,14 +484,22 @@ for which an instantiation of `quantity_point_like_traits` type trait yields a v ```cpp template struct mp_units::quantity_point_like_traits> { + using T = std::chrono::time_point; static constexpr auto reference = si::second; - static constexpr auto point_origin = chrono_point_origin; + static constexpr struct point_origin : absolute_point_origin {} point_origin{}; using rep = std::chrono::seconds::rep; - [[nodiscard]] static constexpr auto quantity_from_origin(const std::chrono::time_point& qp) + + [[nodiscard]] static constexpr convert_implicitly> to_quantity(const T& qp) + { + return quantity{qp.time_since_epoch()}; + } + + [[nodiscard]] static constexpr convert_implicitly from_quantity(const quantity& q) { - return quantity{std::chrono::duration_cast(qp.time_since_epoch())}; + return T(q); } }; - quantity_point qp(time_point_cast(std::chrono::system_clock::now())); + quantity_point qp = time_point_cast(std::chrono::system_clock::now()); + std::chrono::sys_seconds q = qp + 42 * s; ``` diff --git a/src/core/include/mp-units/bits/quantity_concepts.h b/src/core/include/mp-units/bits/quantity_concepts.h index 437d64e30..56c652b9c 100644 --- a/src/core/include/mp-units/bits/quantity_concepts.h +++ b/src/core/include/mp-units/bits/quantity_concepts.h @@ -74,15 +74,19 @@ concept QuantityOf = Quantity && ReferenceOf -concept QuantityLike = requires(T q) { +concept QuantityLike = requires { quantity_like_traits::reference; requires Reference::reference)>>; typename quantity_like_traits::rep; requires RepresentationOf::rep, get_quantity_spec(quantity_like_traits::reference).character>; +} && requires(T q, typename quantity_like_traits::rep v) { { - make_quantity::reference>(quantity_like_traits::value(q)) - } -> std::same_as::reference, typename quantity_like_traits::rep>>; + quantity_like_traits::to_numerical_value(q) + } -> detail::ConversionSpecOf::rep>; + { + quantity_like_traits::from_numerical_value(v) + } -> detail::ConversionSpecOf; }; } // namespace mp_units diff --git a/src/core/include/mp-units/bits/quantity_point_concepts.h b/src/core/include/mp-units/bits/quantity_point_concepts.h index df4b1a245..b2d1091cb 100644 --- a/src/core/include/mp-units/bits/quantity_point_concepts.h +++ b/src/core/include/mp-units/bits/quantity_point_concepts.h @@ -173,7 +173,7 @@ concept QuantityPointOf = * all quantity_point-specific information. */ template -concept QuantityPointLike = requires(T qp) { +concept QuantityPointLike = requires { quantity_point_like_traits::reference; requires Reference::reference)>>; quantity_point_like_traits::point_origin; @@ -181,13 +181,15 @@ concept QuantityPointLike = requires(T qp) { typename quantity_point_like_traits::rep; requires RepresentationOf::rep, get_quantity_spec(quantity_point_like_traits::reference).character>; - requires Quantity::quantity_from_origin(qp))>>; +} && requires(T qp, quantity::reference, typename quantity_point_like_traits::rep> q) { { - make_quantity_point::point_origin>( - quantity_point_like_traits::quantity_from_origin(qp)) - } - -> std::same_as::reference, quantity_point_like_traits::point_origin, - typename quantity_point_like_traits::rep>>; + quantity_point_like_traits::to_quantity(qp) + } -> detail::ConversionSpecOf< + quantity::reference, typename quantity_point_like_traits::rep>>; + + { + quantity_point_like_traits::from_quantity(q) + } -> detail::ConversionSpecOf; }; } // namespace mp_units diff --git a/src/core/include/mp-units/customization_points.h b/src/core/include/mp-units/customization_points.h index e4c169169..18f2b594f 100644 --- a/src/core/include/mp-units/customization_points.h +++ b/src/core/include/mp-units/customization_points.h @@ -132,11 +132,42 @@ struct quantity_values { } }; +template +struct convert_explicitly { + using value_type = T; + T value; + constexpr explicit(false) convert_explicitly(T v) noexcept(std::is_nothrow_constructible_v) : value(std::move(v)) + { + } +}; + +template +struct convert_implicitly { + using value_type = T; + T value; + constexpr explicit(false) convert_implicitly(T v) noexcept(std::is_nothrow_constructible_v) : value(std::move(v)) + { + } +}; + +namespace detail { + +template +concept ConversionSpec = is_specialization_of || is_specialization_of; + +template +concept ConversionSpecOf = ConversionSpec && std::same_as; + +} // namespace detail + /** * @brief Provides support for external quantity-like types * * The type trait should provide the @c reference object, a type alias @c rep, - * and a static member function @c value(T) that returns the raw value of the quantity. + * and static member functions @c to_numerical_value(T) that returns the raw value + * of the quantity and @c from_numerical_value(rep) that returns @c T from @c rep. + * Both return types should be encapsulated in either @c convert_explicitly or + * @c convert_implicitly to specify if the conversion is allowed to happen implicitly. * * Usage example can be found in @c units/chrono.h header file. * @@ -149,8 +180,11 @@ struct quantity_like_traits; * @brief Provides support for external quantity point-like types * * The type trait should provide nested @c reference and @c origin objects, - * a type alias @c rep, and a static member function @c quantity_from_origin(T) that will - * return the quantity being the offset of the point from the origin. + * a type alias @c rep, and static member functions @c to_quantity(T) that returns + * the quantity being the offset of the point from the origin and + * @c from_quantity(quantity) that returns @c T form this quantity. + * Both return types should be encapsulated in either @c convert_explicitly or + * @c convert_implicitly to specify if the conversion is allowed to happen implicitly. * * Usage example can be found in @c units/chrono.h header file. * diff --git a/src/core/include/mp-units/quantity.h b/src/core/include/mp-units/quantity.h index 23a1f6c38..afc1ad2b9 100644 --- a/src/core/include/mp-units/quantity.h +++ b/src/core/include/mp-units/quantity.h @@ -133,15 +133,17 @@ class quantity { template requires detail::QuantityConvertibleTo< quantity::reference, typename quantity_like_traits::rep>, quantity> - constexpr explicit quantity(const Q& q) : - quantity(make_quantity::reference>(quantity_like_traits::value(q))) + constexpr explicit( + is_specialization_of::to_numerical_value(std::declval())), convert_explicitly>) + quantity(const Q& q) : + quantity(make_quantity::reference>(quantity_like_traits::to_numerical_value(q).value)) { } quantity& operator=(const quantity&) = default; quantity& operator=(quantity&&) = default; - // conversions + // unit conversions template U> requires detail::QuantityConvertibleTo> [[nodiscard]] constexpr Quantity auto in(U) const @@ -189,6 +191,27 @@ class quantity { return (*this).force_in(U{}).numerical_value_; } + // conversion operators + template + requires QuantityLike> + [[nodiscard]] explicit(is_specialization_of::from_numerical_value(numerical_value_)), + convert_explicitly>) constexpr + operator Q() const& noexcept(noexcept(quantity_like_traits::from_numerical_value(numerical_value_)) && + std::is_nothrow_copy_constructible_v) + { + return quantity_like_traits::from_numerical_value(numerical_value_).value; + } + + template + requires QuantityLike> + [[nodiscard]] explicit(is_specialization_of::from_numerical_value(numerical_value_)), + convert_explicitly>) constexpr + operator Q() && noexcept(noexcept(quantity_like_traits::from_numerical_value(numerical_value_)) && + std::is_nothrow_move_constructible_v) + { + return quantity_like_traits::from_numerical_value(std::move(numerical_value_)).value; + } + // member unary operators [[nodiscard]] constexpr Quantity auto operator+() const requires requires(rep v) { @@ -365,7 +388,9 @@ class quantity { // CTAD template -explicit quantity(Q) -> quantity::reference, typename quantity_like_traits::rep>; +explicit( + is_specialization_of::to_numerical_value(std::declval())), convert_explicitly>) + quantity(Q) -> quantity::reference, typename quantity_like_traits::rep>; // binary operators on quantities template diff --git a/src/core/include/mp-units/quantity_point.h b/src/core/include/mp-units/quantity_point.h index 3e1587702..3095d0fbd 100644 --- a/src/core/include/mp-units/quantity_point.h +++ b/src/core/include/mp-units/quantity_point.h @@ -128,8 +128,10 @@ class quantity_point { std::convertible_to< quantity::reference, typename quantity_point_like_traits::rep>, quantity_type> - constexpr explicit quantity_point(const QP& qp) : - quantity_from_origin_(quantity_point_like_traits::quantity_from_origin(qp)) + constexpr explicit( + is_specialization_of::to_quantity(std::declval())), convert_explicitly>) + quantity_point(const QP& qp) : + quantity_from_origin_(quantity_point_like_traits::to_quantity(qp).value) { } @@ -168,6 +170,7 @@ class quantity_point { return *this - PO2{}; } + // unit conversions template U> requires detail::QuantityConvertibleTo> [[nodiscard]] constexpr QuantityPoint auto in(U) const @@ -182,6 +185,29 @@ class quantity_point { return make_quantity_point(quantity_ref_from(PO).force_in(U{})); } + // conversion operators + template + requires QuantityPointLike> + [[nodiscard]] explicit( + is_specialization_of::from_quantity(quantity_from_origin_)), + convert_explicitly>) constexpr + operator QP() const& noexcept(noexcept(quantity_point_like_traits::from_quantity(quantity_from_origin_)) && + std::is_nothrow_copy_constructible_v) + { + return quantity_point_like_traits::from_quantity(quantity_from_origin_).value; + } + + template + requires QuantityPointLike> + [[nodiscard]] explicit( + is_specialization_of::from_quantity(quantity_from_origin_)), + convert_explicitly>) constexpr + operator QP() && noexcept(noexcept(quantity_point_like_traits::from_quantity(quantity_from_origin_)) && + std::is_nothrow_move_constructible_v) + { + return quantity_point_like_traits::from_quantity(std::move(quantity_from_origin_)).value; + } + // member unary operators template friend constexpr decltype(auto) operator++(QP&& qp) @@ -249,9 +275,11 @@ class quantity_point { // CTAD template -explicit quantity_point(QP) - -> quantity_point::reference, quantity_point_like_traits::point_origin, - typename quantity_point_like_traits::rep>; +explicit( + is_specialization_of::to_quantity(std::declval())), convert_explicitly>) + quantity_point(QP) + -> quantity_point::reference, quantity_point_like_traits::point_origin, + typename quantity_point_like_traits::rep>; template // TODO simplify when gcc catches up diff --git a/src/utility/include/mp-units/chrono.h b/src/utility/include/mp-units/chrono.h index f868d84b0..d71372e3b 100644 --- a/src/utility/include/mp-units/chrono.h +++ b/src/utility/include/mp-units/chrono.h @@ -64,7 +64,18 @@ template struct quantity_like_traits> { static constexpr auto reference = detail::time_unit_from_chrono_period(); using rep = Rep; - [[nodiscard]] static constexpr rep value(const std::chrono::duration& q) { return q.count(); } + + [[nodiscard]] static constexpr convert_implicitly to_numerical_value( + const std::chrono::duration& q) noexcept(std::is_nothrow_copy_constructible_v) + { + return q.count(); + } + + [[nodiscard]] static constexpr convert_implicitly> from_numerical_value( + const rep& v) noexcept(std::is_nothrow_copy_constructible_v) + { + return std::chrono::duration(v); + } }; template @@ -77,14 +88,22 @@ inline constexpr chrono_point_origin_ chrono_point_origin; template struct quantity_point_like_traits>> { + using T = std::chrono::time_point>; static constexpr auto reference = detail::time_unit_from_chrono_period(); static constexpr auto point_origin = chrono_point_origin; using rep = Rep; - [[nodiscard]] static constexpr quantity quantity_from_origin( - const std::chrono::time_point>& qp) + + [[nodiscard]] static constexpr convert_implicitly> to_quantity(const T& qp) noexcept( + std::is_nothrow_copy_constructible_v) { return quantity{qp.time_since_epoch()}; } + + [[nodiscard]] static constexpr convert_implicitly from_quantity(const quantity& q) noexcept( + std::is_nothrow_copy_constructible_v) + { + return T(q); + } }; template Q> @@ -92,7 +111,7 @@ template Q> { constexpr auto canonical = detail::get_canonical_unit(Q::unit); constexpr ratio r = as_ratio(canonical.mag); - return std::chrono::duration>{q.numerical_value_ref_in(Q::unit)}; + return std::chrono::duration>{q}; } template QP> diff --git a/test/unit_test/static/chrono_test.cpp b/test/unit_test/static/chrono_test.cpp index 75d87128e..3b4059bb9 100644 --- a/test/unit_test/static/chrono_test.cpp +++ b/test/unit_test/static/chrono_test.cpp @@ -52,40 +52,40 @@ static_assert(!QuantityPoint); // construction - same rep type static_assert( std::constructible_from, std::chrono::seconds>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(std::constructible_from, std::chrono::hours>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(std::constructible_from, std::chrono::hours>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(!std::constructible_from, std::chrono::seconds>); static_assert(!std::convertible_to>); static_assert( std::constructible_from, sys_seconds>); static_assert( !std::constructible_from, sys_seconds>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(std::constructible_from, sys_days>); static_assert(!std::constructible_from, sys_days>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(std::constructible_from, sys_days>); static_assert(!std::constructible_from, sys_days>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(!std::constructible_from, sys_seconds>); static_assert(!std::convertible_to>); // construction - different rep type (integral to a floating-point) static_assert(std::constructible_from, std::chrono::seconds>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(std::constructible_from, std::chrono::hours>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(std::constructible_from, std::chrono::seconds>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(std::constructible_from, sys_seconds>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(std::constructible_from, sys_days>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(std::constructible_from, sys_seconds>); -static_assert(!std::convertible_to>); +static_assert(std::convertible_to>); static_assert(quantity{1s} == 1 * s); static_assert(quantity{1s} == 1 * s); diff --git a/test/unit_test/static/quantity_point_test.cpp b/test/unit_test/static/quantity_point_test.cpp index 41acddebe..46e0b6d11 100644 --- a/test/unit_test/static/quantity_point_test.cpp +++ b/test/unit_test/static/quantity_point_test.cpp @@ -525,7 +525,7 @@ static_assert(!std::convertible_to, static_assert( std::constructible_from>, sys_seconds>); static_assert( - !std::convertible_to>>); + std::convertible_to>>); // incompatible origin static_assert(