From 06cbfae3b36b12e3dd6ddb8f3b3b1751730f6a44 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Sat, 28 Dec 2024 14:02:03 +0100 Subject: [PATCH 1/5] feat: `quantity_spec` conversions improved --- docs/users_guide/framework_basics/concepts.md | 43 +- .../dimensionless_quantities.md | 21 + example/include/geographic.h | 4 +- .../mp-units/bits/quantity_spec_hierarchy.h | 40 +- src/core/include/mp-units/bits/type_list.h | 16 + .../include/mp-units/bits/unit_magnitude.h | 27 +- .../include/mp-units/framework/quantity.h | 8 +- .../mp-units/framework/quantity_cast.h | 4 +- .../mp-units/framework/quantity_concepts.h | 8 +- .../framework/quantity_point_concepts.h | 4 +- .../mp-units/framework/quantity_spec.h | 1224 ++++++----------- .../framework/quantity_spec_concepts.h | 95 +- .../include/mp-units/framework/reference.h | 18 - .../mp-units/framework/reference_concepts.h | 8 +- .../mp-units/framework/symbolic_expression.h | 23 +- .../mp-units/framework/system_reference.h | 2 +- src/core/include/mp-units/framework/unit.h | 40 +- .../mp-units/framework/unit_concepts.h | 32 +- .../include/mp-units/framework/value_cast.h | 37 +- test/static/concepts_test.cpp | 31 +- test/static/quantity_point_test.cpp | 3 +- test/static/quantity_spec_test.cpp | 292 +++- test/static/quantity_test.cpp | 20 + test/static/reference_test.cpp | 30 +- test/static/type_list_test.cpp | 11 + test/static/unit_test.cpp | 53 +- 26 files changed, 980 insertions(+), 1114 deletions(-) diff --git a/docs/users_guide/framework_basics/concepts.md b/docs/users_guide/framework_basics/concepts.md index c8177d39d1..e13626e508 100644 --- a/docs/users_guide/framework_basics/concepts.md +++ b/docs/users_guide/framework_basics/concepts.md @@ -52,24 +52,6 @@ All of the above quantity specifications have to be marked as `final`. `QuantitySpecOf` concept is satisfied when both arguments satisfy a [`QuantitySpec`](#QuantitySpec) concept and when `T` is implicitly convertible to `V`. -??? info "More details" - - Additionally: - - - `T` should not be a [nested quantity specification of `V`](dimensionless_quantities.md/#nested-quantity-kinds) - - either `T` is quantity kind or `V` should not be a - [nested quantity specification of `T`](dimensionless_quantities.md/#nested-quantity-kinds) - - Those additional conditions are required to make the following work: - - ```cpp - static_assert(ReferenceOf); - static_assert(!ReferenceOf); - static_assert(!ReferenceOf); - static_assert(ReferenceOf); - static_assert(!ReferenceOf); - ``` - ## `Unit` { #Unit } @@ -123,17 +105,8 @@ Such units can be passed as an argument to a `prefixed_unit` class template. ### `UnitOf` { #UnitOf } -`UnitOf` concept is satisfied for all units `T` matching an [`AssociatedUnit`](#AssociatedUnit) -concept with an associated quantity type implicitly convertible to `V`. - -??? info "More details" - - Additionally, the kind of `V` and the kind of quantity type associated with `T` must be the same, - or the quantity type associated with `T` may not be derived from the kind of `V`. - - This condition is required to make `dimensionless[si::radian]` invalid as `si::radian` should - be only used for `isq::angular_measure`, which is a - [nested quantity kind within the dimensionless quantities tree](dimensionless_quantities.md/#nested-quantity-kinds). +`UnitOf` concept is satisfied for all units `T` for which an associated quantity spec is implicitly +convertible to the provided [`QuantitySpec`](#QuantitySpec) value. ## `Reference` { #Reference } @@ -150,7 +123,7 @@ A `Reference` can either be: ### `ReferenceOf` { #ReferenceOf } `ReferenceOf` concept is satisfied by references `T` which have a quantity specification that satisfies -[`QuantitySpecOf`](#QuantitySpecOf) concept. | +[`QuantitySpecOf`](#QuantitySpecOf) concept. ## `Representation` { #Representation } @@ -205,7 +178,7 @@ satisfied by all types being or deriving from an instantiation of a `quantity` c ### `QuantityOf` { #QuantityOf } -`QuantityOf` concept is satisfied by all the quantities for which a [`QuantitySpecOf`](#QuantitySpecOf) +`QuantityOf` concept is satisfied by all the quantities for which a [`ReferenceOf`](#ReferenceOf) is `true`. @@ -252,10 +225,10 @@ class template. `QuantityPointOf` concept is satisfied by all the quantity points `T` that match the following value `V`: -| `V` | Condition | -|----------------|-----------------------------------------------------------------------------------------------------| -| `QuantitySpec` | The quantity point quantity specification satisfies [`QuantitySpecOf`](#QuantitySpecOf) concept. | -| `PointOrigin` | The _point_ and `V` have the same absolute point origin. | +| `V` | Condition | +|----------------|-----------------------------------------------------------------------------------------------| +| `QuantitySpec` | The quantity point quantity specification satisfies [`ReferenceOf`](#ReferenceOf) concept. | +| `PointOrigin` | The _point_ and `V` have the same absolute point origin. | ## `QuantityLike` { #QuantityLike } diff --git a/docs/users_guide/framework_basics/dimensionless_quantities.md b/docs/users_guide/framework_basics/dimensionless_quantities.md index b7a9555e6f..6d7b811787 100644 --- a/docs/users_guide/framework_basics/dimensionless_quantities.md +++ b/docs/users_guide/framework_basics/dimensionless_quantities.md @@ -311,3 +311,24 @@ inline constexpr struct bit final : named_unit<"bit", one, kind_of q2 = dimensionless(q1.in(one)); + ``` diff --git a/example/include/geographic.h b/example/include/geographic.h index ba26b3f1ae..d182962120 100644 --- a/example/include/geographic.h +++ b/example/include/geographic.h @@ -193,14 +193,14 @@ distance spherical_distance(position from, position to) // const auto central_angle = 2 * asin(sqrt(0.5 - cos(to_lat - from_lat) / 2 + cos(from_lat) * cos(to_lat) * (1 // - cos(lon2_rad - from_lon)) / 2)); - return quantity_cast(earth_radius * central_angle); + return quantity_cast((earth_radius * central_angle).in(earth_radius.unit)); } else { // the haversine formula const quantity sin_lat = sin((to_lat - from_lat) / 2); const quantity sin_lon = sin((to_lon - from_lon) / 2); const quantity central_angle = 2 * asin(sqrt(sin_lat * sin_lat + cos(from_lat) * cos(to_lat) * sin_lon * sin_lon)); - return quantity_cast(earth_radius * central_angle); + return quantity_cast((earth_radius * central_angle).in(earth_radius.unit)); } } diff --git a/src/core/include/mp-units/bits/quantity_spec_hierarchy.h b/src/core/include/mp-units/bits/quantity_spec_hierarchy.h index 46c2006aee..0f05616e9e 100644 --- a/src/core/include/mp-units/bits/quantity_spec_hierarchy.h +++ b/src/core/include/mp-units/bits/quantity_spec_hierarchy.h @@ -58,12 +58,16 @@ template template [[nodiscard]] consteval bool have_common_base(A a, B b) { - constexpr std::size_t a_length = hierarchy_path_length(A{}); - constexpr std::size_t b_length = hierarchy_path_length(B{}); - if constexpr (a_length > b_length) - return have_common_base_in_hierarchy_of_equal_length(hierarchy_path_advance(a), b); - else - return have_common_base_in_hierarchy_of_equal_length(a, hierarchy_path_advance(b)); + if constexpr (is_same_v) + return true; + else { + constexpr std::size_t a_length = hierarchy_path_length(A{}); + constexpr std::size_t b_length = hierarchy_path_length(B{}); + if constexpr (a_length > b_length) + return have_common_base_in_hierarchy_of_equal_length(hierarchy_path_advance(a), b); + else + return have_common_base_in_hierarchy_of_equal_length(a, hierarchy_path_advance(b)); + } } template @@ -91,16 +95,20 @@ template template [[nodiscard]] consteval bool is_child_of(Child ch, Parent p) { - if constexpr (Child{} == Parent{}) - return std::true_type{}; - else { - constexpr std::size_t child_length = hierarchy_path_length(Child{}); - constexpr std::size_t parent_length = hierarchy_path_length(Parent{}); - if constexpr (parent_length > child_length) - return false; - else - return hierarchy_path_advance(ch) == p; - } + constexpr std::size_t child_length = hierarchy_path_length(Child{}); + constexpr std::size_t parent_length = hierarchy_path_length(Parent{}); + if constexpr (parent_length >= child_length) + return false; + else + return hierarchy_path_advance(ch) == p; +} + +[[nodiscard]] consteval QuantitySpec auto get_hierarchy_root(QuantitySpec auto q) +{ + if constexpr (requires { q._parent_; }) + return get_hierarchy_root(q._parent_); + else + return q; } } // namespace mp_units::detail diff --git a/src/core/include/mp-units/bits/type_list.h b/src/core/include/mp-units/bits/type_list.h index f0e6817f13..3399ec8b2f 100644 --- a/src/core/include/mp-units/bits/type_list.h +++ b/src/core/include/mp-units/bits/type_list.h @@ -172,6 +172,22 @@ struct type_list_split_half; template typename List, typename... Types> struct type_list_split_half> : type_list_split, (sizeof...(Types) + 1) / 2> {}; +// extract +template +struct type_list_extract_impl; + +template typename List, typename... Pre, typename Element, typename... Post> +struct type_list_extract_impl, List> { + using element = Element; + using rest = List; +}; + +template + requires(N >= 0) && (type_list_size > N) +struct type_list_extract : + type_list_extract_impl::first_list, + typename type_list_split::second_list> {}; + // merge_sorted template typename Pred> struct type_list_merge_sorted_impl; diff --git a/src/core/include/mp-units/bits/unit_magnitude.h b/src/core/include/mp-units/bits/unit_magnitude.h index 0c263caf13..e84da6eaf7 100644 --- a/src/core/include/mp-units/bits/unit_magnitude.h +++ b/src/core/include/mp-units/bits/unit_magnitude.h @@ -76,35 +76,26 @@ struct power_v { static constexpr ratio exponent{Num, Den...}; }; -template -[[nodiscard]] consteval auto get_base(Element element) +template +[[nodiscard]] consteval auto get_base(T element) { - if constexpr (is_specialization_of_v) - return Element::base; + if constexpr (is_specialization_of_v) + return T::base; else return element; } -template -[[nodiscard]] consteval auto get_base_value(Element element) +template +[[nodiscard]] consteval auto get_base_value(T element) { - if constexpr (is_specialization_of_v) - return get_base_value(Element::base); - else if constexpr (MagConstant) + if constexpr (is_specialization_of_v) + return get_base_value(T::base); + else if constexpr (MagConstant) return element._value_; else return element; } -template -[[nodiscard]] MP_UNITS_CONSTEVAL ratio get_exponent(Element) -{ - if constexpr (is_specialization_of_v) - return Element::exponent; - else - return ratio{1}; -} - template [[nodiscard]] consteval auto power_v_or_T() { diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index 8f4b4ef3b6..00f1821154 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -71,8 +71,8 @@ concept ValuePreservingTo = Representation> && Repr template concept QuantityConvertibleTo = - Quantity && Quantity && QuantitySpecConvertibleTo && - UnitConvertibleTo && + Quantity && Quantity && implicitly_convertible(QFrom::quantity_spec, QTo::quantity_spec) && + (interconvertible(QFrom::unit, QTo::unit)) && ValuePreservingTo && // TODO consider providing constraints of sudo_cast here rather than testing if it can be called (its return type is // deduced thus the function is evaluated here and may emit truncating conversion or other warnings) @@ -641,6 +641,10 @@ MP_UNITS_EXPORT_END } // namespace mp_units +// This specialization overrides `std` defaults for quantities +template +struct std::common_type {}; + template requires requires { { mp_units::get_common_reference(Q1::reference, Q2::reference) } -> mp_units::Reference; diff --git a/src/core/include/mp-units/framework/quantity_cast.h b/src/core/include/mp-units/framework/quantity_cast.h index ba28d193a8..d184456d1a 100644 --- a/src/core/include/mp-units/framework/quantity_cast.h +++ b/src/core/include/mp-units/framework/quantity_cast.h @@ -57,7 +57,7 @@ namespace mp_units { * @tparam ToQS a quantity specification to use for a target quantity */ template> - requires detail::QuantitySpecCastableTo + requires(castable(Q::quantity_spec, ToQS)) && detail::WeakUnitOf [[nodiscard]] constexpr Quantity auto quantity_cast(FwdQ&& q) { return quantity{std::forward(q).numerical_value_is_an_implementation_detail_, make_reference(ToQS, Q::unit)}; @@ -81,7 +81,7 @@ template> - requires detail::QuantitySpecCastableTo + requires(castable(QP::quantity_spec, ToQS)) && detail::WeakUnitOf [[nodiscard]] constexpr QuantityPoint auto quantity_cast(FwdQP&& qp) { return QP{quantity_cast(std::forward(qp).quantity_from_origin_is_an_implementation_detail_), diff --git a/src/core/include/mp-units/framework/quantity_concepts.h b/src/core/include/mp-units/framework/quantity_concepts.h index c4bba5e320..af489c3b94 100644 --- a/src/core/include/mp-units/framework/quantity_concepts.h +++ b/src/core/include/mp-units/framework/quantity_concepts.h @@ -72,13 +72,13 @@ concept QuantityLikeImpl = requires(const T& qty, const Traits::rep& num) { } // namespace detail /** - * @brief A concept matching all quantities with provided quantity spec + * @brief A concept matching all quantities of the provided quantity spec * - * Satisfied by all quantities with a quantity_spec being the instantiation derived from - * the provided quantity_spec type. + * Satisfied by all quantities with the reference satisfying @c ReferenceOf. */ MP_UNITS_EXPORT template -concept QuantityOf = Quantity && QuantitySpecOf; +concept QuantityOf = Quantity && QuantitySpec && + ReferenceOf; /** * @brief A concept matching all external quantities like types diff --git a/src/core/include/mp-units/framework/quantity_point_concepts.h b/src/core/include/mp-units/framework/quantity_point_concepts.h index 0158830854..71c07e620a 100644 --- a/src/core/include/mp-units/framework/quantity_point_concepts.h +++ b/src/core/include/mp-units/framework/quantity_point_concepts.h @@ -117,7 +117,7 @@ concept SameAbsolutePointOriginAs = /** - * @brief A concept matching all quantity points with provided quantity spec + * @brief A concept matching all quantity points of the provided property * * Satisfied by all quantity points with a quantity_spec being the instantiation derived from * the provided quantity_spec type, or quantity points having the origin with the same @@ -125,7 +125,7 @@ concept SameAbsolutePointOriginAs = */ MP_UNITS_EXPORT template concept QuantityPointOf = - QuantityPoint && (QuantitySpecOf || + QuantityPoint && (ReferenceOf || detail::SameAbsolutePointOriginAs); /** diff --git a/src/core/include/mp-units/framework/quantity_spec.h b/src/core/include/mp-units/framework/quantity_spec.h index e7f1b7c24e..b994588869 100644 --- a/src/core/include/mp-units/framework/quantity_spec.h +++ b/src/core/include/mp-units/framework/quantity_spec.h @@ -45,6 +45,7 @@ import std; #else #include #include +#include #include #include #endif @@ -54,13 +55,62 @@ namespace mp_units { MP_UNITS_EXPORT struct dimensionless; +MP_UNITS_EXPORT +#if MP_UNITS_API_NO_CRTP +template +#else +template +#endif +struct quantity_spec; + +template +struct derived_quantity_spec; + +MP_UNITS_EXPORT template +[[nodiscard]] consteval bool explicitly_convertible(From from, To to); + namespace detail { -template - requires(!AssociatedUnit) || UnitOf +#if MP_UNITS_API_NO_CRTP +template +void to_base_specialization_of_quantity_spec(const volatile quantity_spec*); +#else +template +void to_base_specialization_of_quantity_spec(const volatile quantity_spec*); +#endif + +template +constexpr bool is_derived_from_specialization_of_quantity_spec = + requires(T* type) { detail::to_base_specialization_of_quantity_spec(type); }; + +/** + * @brief Concept matching all named quantity specification types + * + * Satisfied by all types that derive from `quantity_spec`. + */ +template +concept NamedQuantitySpec = + QuantitySpec && is_derived_from_specialization_of_quantity_spec && (!QuantityKindSpec); + +/** + * @brief Concept matching all derived quantity specification types + * + * Satisfied by all `derived_quantity_spec` specializations. + * + * @note Deriving a strong type from it is considered a logic error and thus is + * explicitly not supported here. + */ +template +concept DerivedQuantitySpec = + QuantitySpec && + (is_specialization_of || + (QuantityKindSpec && is_specialization_of)); + + +template U> [[nodiscard]] consteval Reference auto make_reference(QS, U u) { - if constexpr (requires { requires(get_quantity_spec(U{}) == QS{}); }) + if constexpr (requires { requires(mp_units::get_quantity_spec(U{}) == QS{}); }) return u; else return reference{}; @@ -75,7 +125,7 @@ template template... Ts> [[nodiscard]] consteval quantity_character common_quantity_character(Ts... args) { - return max({args...}); + return detail::max({args...}); } template @@ -83,13 +133,13 @@ template const type_list&) { constexpr quantity_character num = - common_quantity_character(quantity_character::scalar, expr_type::character...); + detail::common_quantity_character(quantity_character::scalar, expr_type::character...); constexpr quantity_character den = - common_quantity_character(quantity_character::scalar, expr_type::character...); + detail::common_quantity_character(quantity_character::scalar, expr_type::character...); if constexpr (num == den) return quantity_character::scalar; else - return common_quantity_character(num, den); + return detail::common_quantity_character(num, den); } /** @@ -101,35 +151,55 @@ template template [[nodiscard]] consteval quantity_character quantity_character_init(quantity_character ch) { - if constexpr (contains()) - return get(); + if constexpr (mp_units::contains()) + return mp_units::get(); else return ch; } -template - requires requires { Q::dimension; } -using to_dimension = MP_UNITS_NONCONST_TYPE(Q::dimension); - template [[nodiscard]] consteval QuantitySpec auto clone_kind_of(Q q); template [[nodiscard]] consteval auto remove_kind(Q q); +template +[[nodiscard]] consteval QuantitySpec auto get_kind_tree_root(Q); + +template +struct quantity_spec_less_impl : + std::bool_constant<(detail::type_name() < detail::type_name()) || + (detail::type_name() == detail::type_name() && + detail::type_name() < detail::type_name()) || + (detail::type_name() == detail::type_name() && + detail::type_name() == detail::type_name() && + detail::type_name() < detail::type_name())> {}; + +template +struct quantity_spec_less : + quantity_spec_less_impl {}; + +template +using type_list_of_quantity_spec_less = expr_less; + struct quantity_spec_interface_base { template [[nodiscard]] friend consteval QuantitySpec auto operator*(Lhs lhs, Rhs rhs) { - return clone_kind_of( - expr_multiply(remove_kind(lhs), remove_kind(rhs))); + return detail::clone_kind_of( + detail::expr_multiply( + detail::remove_kind(lhs), detail::remove_kind(rhs))); } template [[nodiscard]] friend consteval QuantitySpec auto operator/(Lhs lhs, Rhs rhs) { - return clone_kind_of( - expr_divide(remove_kind(lhs), remove_kind(rhs))); + return detail::clone_kind_of( + detail::expr_divide( + detail::remove_kind(lhs), detail::remove_kind(rhs))); } template @@ -147,28 +217,29 @@ struct quantity_spec_interface : quantity_spec_interface_base { template U> [[nodiscard]] consteval Reference auto operator[](this Self self, U u) { - return make_reference(self, u); + return detail::make_reference(self, u); } template> - requires QuantitySpecExplicitlyConvertibleTo + requires(mp_units::explicitly_convertible(Q::quantity_spec, Self{})) [[nodiscard]] constexpr Quantity auto operator()(this Self self, FwdQ&& q) { - return quantity{std::forward(q).numerical_value_is_an_implementation_detail_, make_reference(self, Q::unit)}; + return quantity{std::forward(q).numerical_value_is_an_implementation_detail_, + detail::make_reference(self, Q::unit)}; } #else template U> [[nodiscard]] MP_UNITS_CONSTEVAL Reference auto operator[](U u) const { - return make_reference(Self{}, u); + return detail::make_reference(Self{}, u); } template, typename Self_ = Self> - requires QuantitySpecExplicitlyConvertibleTo + requires(mp_units::explicitly_convertible(Q::quantity_spec, Self_{})) [[nodiscard]] constexpr Quantity auto operator()(FwdQ&& q) const { return quantity{std::forward(q).numerical_value_is_an_implementation_detail_, - make_reference(Self{}, Q::unit)}; + detail::make_reference(Self{}, Q::unit)}; } #endif }; @@ -415,6 +486,10 @@ struct quantity_spec : detail::quantity_spec_interface + requires requires { Q::dimension; } +using to_dimension = MP_UNITS_NONCONST_TYPE(Q::dimension); + template struct derived_quantity_spec_impl : #if MP_UNITS_API_NO_CRTP @@ -426,9 +501,10 @@ struct derived_quantity_spec_impl : using _base_type_ = derived_quantity_spec_impl; using _base_ = expr_fractions; - static constexpr Dimension auto dimension = expr_map(_base_{}); + static constexpr Dimension auto dimension = + detail::expr_map(_base_{}); static constexpr quantity_character character = - derived_quantity_character(typename _base_::_num_{}, typename _base_::_den_{}); + detail::derived_quantity_character(typename _base_::_num_{}, typename _base_::_den_{}); }; } // namespace detail @@ -555,7 +631,8 @@ template [[nodiscard]] consteval QuantitySpec auto pow(Q q) { return detail::clone_kind_of( - detail::expr_pow(detail::remove_kind(q))); + detail::expr_pow( + detail::remove_kind(q))); } @@ -566,7 +643,7 @@ template * * @return QuantitySpec The result of computation */ -[[nodiscard]] consteval QuantitySpec auto sqrt(QuantitySpec auto q) { return pow<1, 2>(q); } +[[nodiscard]] consteval QuantitySpec auto sqrt(QuantitySpec auto q) { return mp_units::pow<1, 2>(q); } /** @@ -576,7 +653,7 @@ template * * @return QuantitySpec The result of computation */ -[[nodiscard]] consteval QuantitySpec auto cbrt(QuantitySpec auto q) { return pow<1, 3>(q); } +[[nodiscard]] consteval QuantitySpec auto cbrt(QuantitySpec auto q) { return mp_units::pow<1, 3>(q); } MP_UNITS_EXPORT_END @@ -588,75 +665,66 @@ namespace detail { template [[nodiscard]] consteval int get_complexity(Q); -// dimensionless should not be exploded to an empty derived quantity -[[nodiscard]] consteval int get_complexity(struct dimensionless) { return 0; } +template +[[nodiscard]] consteval int get_complexity_impl(power); + +template +[[nodiscard]] consteval int get_complexity_impl(kind_of_); + +template +[[nodiscard]] consteval int get_complexity_impl(Q); template -[[nodiscard]] consteval int get_complexity(type_list) +[[nodiscard]] consteval int get_complexity_impl(type_list) { if constexpr (sizeof...(Ts) == 0) return 0; else - return max({get_complexity(Ts{})...}); + return max({detail::get_complexity_impl(Ts{})...}); } template -[[nodiscard]] consteval int get_complexity(power) +[[nodiscard]] consteval int get_complexity_impl(power) { - return get_complexity(Q{}); + return detail::get_complexity(Q{}); } template -[[nodiscard]] consteval int get_complexity(kind_of_) +[[nodiscard]] consteval int get_complexity_impl(kind_of_) { - return get_complexity(Q{}); + return detail::get_complexity(Q{}); } template [[nodiscard]] consteval int get_complexity_impl(Q) { if constexpr (DerivedQuantitySpec) - return max(get_complexity(typename Q::_num_{}), get_complexity(typename Q::_den_{})); - else if constexpr (requires { Q::_equation_; }) - return 1 + get_complexity(Q::_equation_); + return max(detail::get_complexity_impl(typename Q::_num_{}), detail::get_complexity_impl(typename Q::_den_{})); + else if constexpr (requires { Q::_equation_; } && Q{} != dimensionless) + return 1 + detail::get_complexity(Q::_equation_); else return 0; } template -constexpr auto get_complexity_result = get_complexity_impl(Q{}); +constexpr auto get_complexity_result = detail::get_complexity_impl(Q{}); +/** + * @brief Get the complexity of a quantity spec + * + * Complexity of the quantity spec specifies how many times a given quantity can be exploded + * to the underlying equation. For the derived quantities, the largest complexity of the + * ingredients is returned. + * + * @tparam Q Quantity specification to be analyzed + * @return int Complexity of the quantity spec + */ template [[nodiscard]] consteval int get_complexity(Q) { return get_complexity_result; } -// dimension_one is always the last one -// otherwise, sort by typename -template -[[nodiscard]] consteval bool ingredients_dimension_less(D1, D2) -{ - if constexpr (D1{} == D2{} || D1{} == dimension_one) - return false; - else if constexpr (D2{} == dimension_one) - return true; - else - return type_name() < type_name(); -} - -template -struct ingredients_less : - std::bool_constant<(lhs_compl > rhs_compl) || - (lhs_compl == rhs_compl && ingredients_dimension_less(Lhs::dimension, Rhs::dimension)) || - (lhs_compl == rhs_compl && Lhs::dimension == Rhs::dimension && - type_name() < type_name())>{}; - -template -using type_list_of_ingredients_less = expr_less; - template requires requires { Q::_equation_; } [[nodiscard]] consteval bool defines_equation(Q) @@ -667,717 +735,309 @@ template return true; } -enum class specs_convertible_result : std::int8_t { no, cast, explicit_conversion, yes }; +enum class [[nodiscard]] specs_convertible_result : std::int8_t { + no, + cast, + explicit_conversion_beyond_kind, + explicit_conversion, + yes +}; template -struct explode_to_equation_result { +struct [[nodiscard]] explode_result { Q equation; specs_convertible_result result; }; -#if MP_UNITS_COMP_CLANG +#if MP_UNITS_COMP_CLANG && MP_UNITS_COMP_CLANG < 17 template -explode_to_equation_result(Q, specs_convertible_result) -> explode_to_equation_result; +explode_result(Q, specs_convertible_result) -> explode_result; #endif template requires requires { Q::_equation_; } -[[nodiscard]] consteval auto explode_to_equation(Q q) +[[nodiscard]] consteval auto explode(Q q) { - return explode_to_equation_result{ - Q::_equation_, defines_equation(q) ? specs_convertible_result::yes : specs_convertible_result::explicit_conversion}; + return explode_result{Q::_equation_, detail::defines_equation(q) ? specs_convertible_result::yes + : specs_convertible_result::explicit_conversion}; } template requires requires { Q::_equation_; } -[[nodiscard]] consteval auto explode_to_equation(power) +[[nodiscard]] consteval auto explode(power) { constexpr ratio exp = power::_exponent_; - return explode_to_equation_result{ - pow(Q::_equation_), - defines_equation(Q{}) ? specs_convertible_result::yes : specs_convertible_result::explicit_conversion}; + return explode_result{pow(Q::_equation_), defines_equation(Q{}) + ? specs_convertible_result::yes + : specs_convertible_result::explicit_conversion}; } -template -struct explode_result { - Q quantity; - specs_convertible_result result = specs_convertible_result::yes; - - template - [[nodiscard]] consteval explode_result common_convertibility_with(explode_to_equation_result res) const - { - return {quantity, min(result, res.result)}; - } -}; - -#if MP_UNITS_COMP_CLANG - -template -explode_result(Q) -> explode_result; - -#endif - -template -[[nodiscard]] consteval auto explode(Q q); - -template -[[nodiscard]] consteval auto explode_impl(Q q); - -template -[[nodiscard]] consteval auto explode_impl(Q q); - -template -[[nodiscard]] consteval auto explode_impl(Q, type_list, type_list) -{ - constexpr auto num = get_complexity(Num{}); - constexpr auto den = get_complexity(Den{}); - constexpr auto max_compl = max(num, den); - - if constexpr (max_compl == Complexity || ((num >= den && !requires { explode_to_equation(Num{}); }) || - (num < den && !requires { explode_to_equation(Den{}); }))) - return explode_result{(map_power(Num{}) * ... * map_power(Nums{})) / (map_power(Den{}) * ... * map_power(Dens{}))}; - else { - if constexpr (num >= den) { - constexpr auto res = explode_to_equation(Num{}); - return explode((res.equation * ... * map_power(Nums{})) / - (map_power(Den{}) * ... * map_power(Dens{}))) - .common_convertibility_with(res); - } else { - constexpr auto res = explode_to_equation(Den{}); - return explode((map_power(Num{}) * ... * map_power(Nums{})) / - (res.equation * ... * map_power(Dens{}))) - .common_convertibility_with(res); - } - } -} - -template -[[nodiscard]] consteval auto explode_impl(Q, type_list, type_list<>) -{ - constexpr auto n = get_complexity(Num{}); - if constexpr (n == Complexity || !requires { explode_to_equation(Num{}); }) - return explode_result{(map_power(Num{}) * ... * map_power(Nums{}))}; - else { - constexpr auto res = explode_to_equation(Num{}); - return explode((res.equation * ... * map_power(Nums{}))).common_convertibility_with(res); - } -} - -template -[[nodiscard]] consteval auto explode_impl(Q, type_list<>, type_list) -{ - constexpr auto den = get_complexity(Den{}); - if constexpr (den == Complexity || !requires { explode_to_equation(Den{}); }) - return explode_result{dimensionless / (map_power(Den{}) * ... * map_power(Dens{}))}; - else { - constexpr auto res = explode_to_equation(Den{}); - return explode(dimensionless / (res.equation * ... * map_power(Dens{}))) - .common_convertibility_with(res); - } -} +template +[[nodiscard]] constexpr specs_convertible_result are_ingredients_convertible(NumFrom num_from, DenFrom den_from, + NumTo num_to, DenTo den_to); -template -[[nodiscard]] consteval auto explode_impl(Q, type_list<>, type_list<>) -{ - return explode_result{dimensionless}; -} - -template -[[nodiscard]] consteval auto explode_impl(Q q) -{ - constexpr auto complexity = get_complexity(Q{}); - if constexpr (complexity > Complexity) - return explode_impl(q, type_list_sort{}, - type_list_sort{}); - else - return explode_result{q}; -} - -template -[[nodiscard]] consteval auto explode_impl(Q q) -{ - constexpr auto complexity = get_complexity(Q{}); - if constexpr (complexity > Complexity && requires { Q::_equation_; }) { - constexpr auto res = explode_to_equation(Q{}); - return explode(res.equation).common_convertibility_with(res); - } else - return explode_result{q}; -} -template -constexpr auto explode_res = explode_impl(Q{}); - -template -[[nodiscard]] consteval auto explode(Q) +template +[[nodiscard]] consteval specs_convertible_result convertible_common_base(From from, To to) { - return explode_res; + using enum specs_convertible_result; + if constexpr (is_same_v) return yes; + if constexpr (detail::is_child_of(From{}, To{})) + return (detail::get_kind_tree_root(from) == detail::get_kind_tree_root(to)) ? yes : explicit_conversion_beyond_kind; + if constexpr (detail::is_child_of(To{}, From{})) return explicit_conversion; + if constexpr (detail::get_kind_tree_root(From{}) == detail::get_kind_tree_root(To{})) return cast; + return no; } -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list num_from, - type_list den_from, - type_list num_to, - type_list den_to); - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, - type_list, - type_list, - type_list); - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list, - type_list<>, type_list, - type_list); - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list, - type_list, - type_list<>, type_list); - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list, - type_list, - type_list, type_list<>); - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list, - type_list<>, type_list, - type_list<>); - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, - type_list, - type_list<>, type_list); - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list, - type_list, type_list<>, - type_list<>); - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, type_list<>, - type_list, - type_list); - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list, type_list<>, - type_list<>, type_list<>); -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, type_list, - type_list<>, type_list<>); - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, type_list<>, - type_list, type_list<>); - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, type_list<>, type_list<>, - type_list); - -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, type_list<>, type_list<>, - type_list<>); - -enum class prepend_rest : std::int8_t { no, first, second }; - -template -struct extract_results { - bool same_kind{}; - From from{}; - To to{}; - prepend_rest prepend{}; - Elem elem{}; +template, TypeList RestTo = type_list<>> +struct [[nodiscard]] extract_common_base_result { + specs_convertible_result result; + RestFrom rest_from{}; + RestTo rest_to{}; }; -#if MP_UNITS_COMP_CLANG +#if MP_UNITS_COMP_CLANG && MP_UNITS_COMP_CLANG < 17 -template -extract_results(bool, From = {}, To = {}, prepend_rest = {}, Elem = {}) -> extract_results; +extract_common_base_result(specs_convertible_result) -> extract_common_base_result, type_list<>>; -#endif +template +extract_common_base_result(specs_convertible_result, RestFrom, RestTo) -> extract_common_base_result; -template -[[nodiscard]] consteval auto extract_convertible_quantities_impl(From, To) -{ - constexpr auto qfrom = map_power(From{}); - constexpr auto qto = map_power(To{}); - if constexpr (get_kind_tree_root(qfrom) == get_kind_tree_root(qto)) { - if constexpr (is_specialization_of_power && is_specialization_of_power) - return extract_results{true, typename From::_factor_{}, typename To::_factor_{}, prepend_rest::no}; - else - return extract_results{true, qfrom, qto, prepend_rest::no}; - } else { - auto normalize = [](Q) { - if constexpr (is_specialization_of_power) - return std::tuple{typename Q::_factor_{}, Q::_exponent_}; - else - return std::tuple{Q{}, ratio{1}}; - }; - constexpr auto from_norm = normalize(From{}); - constexpr auto to_norm = normalize(To{}); - constexpr auto from_factor = std::get<0>(from_norm); - constexpr auto from_exp = std::get<1>(from_norm); - constexpr auto to_factor = std::get<0>(to_norm); - constexpr auto to_exp = std::get<1>(to_norm); - if constexpr (get_kind_tree_root(from_factor) != get_kind_tree_root(to_factor)) - return extract_results{false}; - else if constexpr (from_exp > to_exp) - return extract_results{true, from_factor, to_factor, prepend_rest::first, - power_or_T{}}; - else - return extract_results{true, from_factor, to_factor, prepend_rest::second, - power_or_T{}}; - } -} - -template -constexpr auto extract_convertible_quantities_result = extract_convertible_quantities_impl(From{}, To{}); +#endif -// tries to find the largest common power of a quantity -// in case powers have different factors of the same dimension, returns the remainder -template -[[nodiscard]] consteval auto extract_convertible_quantities(From, To) +template +[[nodiscard]] consteval auto try_extract_common_base(ProcessedFrom, RemainingFrom, ProcessedTo, RemainingTo) { - return extract_convertible_quantities_result; + return std::optional>(std::nullopt); } -enum class extracted_entities : std::int8_t { numerators, denominators, from, to }; - -template -[[nodiscard]] consteval specs_convertible_result prepend_and_process_rest(NumFrom num_from, DenFrom den_from, - NumTo num_to, DenTo den_to) +template +[[nodiscard]] consteval auto try_extract_common_base(type_list, type_list, + type_list, type_list) { - constexpr auto res = [&]() consteval { - if constexpr (Ext.prepend == prepend_rest::no) - return are_ingredients_convertible(num_from, den_from, num_to, den_to); - else { - using elem = decltype(Ext.elem); - if constexpr (Entities == extracted_entities::numerators) { - if constexpr (Ext.prepend == prepend_rest::first) - return are_ingredients_convertible(type_list_push_front{}, den_from, num_to, den_to); - else - return are_ingredients_convertible(num_from, den_from, type_list_push_front{}, den_to); - } else if constexpr (Entities == extracted_entities::denominators) { - if constexpr (Ext.prepend == prepend_rest::first) - return are_ingredients_convertible(num_from, type_list_push_front{}, num_to, den_to); - else - return are_ingredients_convertible(num_from, den_from, num_to, type_list_push_front{}); - } else if constexpr (Entities == extracted_entities::from) { - if constexpr (Ext.prepend == prepend_rest::first) - return are_ingredients_convertible(type_list_push_front{}, den_from, num_to, den_to); - else - return are_ingredients_convertible(num_from, type_list_push_front{}, num_to, den_to); - } else if constexpr (Entities == extracted_entities::to) { - if constexpr (Ext.prepend == prepend_rest::first) - return are_ingredients_convertible(num_from, den_from, type_list_push_front{}, den_to); - else - return are_ingredients_convertible(num_from, den_from, num_to, type_list_push_front{}); - } - } - }(); - - if constexpr (Entities == extracted_entities::numerators || Entities == extracted_entities::denominators) - return min(res, convertible(Ext.from, Ext.to)); - else - return res; -} - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list num_from, - type_list den_from, - type_list num_to, - type_list den_to) -{ - if constexpr (constexpr auto extN = extract_convertible_quantities(NumFrom{}, NumTo{}); extN.same_kind) - return prepend_and_process_rest(type_list{}, den_from, - type_list{}, den_to); - else if constexpr (constexpr auto extD = extract_convertible_quantities(DenFrom{}, DenTo{}); extD.same_kind) - return prepend_and_process_rest(num_from, type_list{}, num_to, - type_list{}); - else if constexpr (constexpr auto extF = extract_convertible_quantities(NumFrom{}, DenFrom{}); extF.same_kind) - return prepend_and_process_rest(type_list{}, type_list{}, - num_to, den_to); - else if constexpr (constexpr auto extT = extract_convertible_quantities(NumTo{}, DenTo{}); extT.same_kind) - return prepend_and_process_rest(num_from, den_from, type_list{}, - type_list{}); + constexpr QuantitySpec auto from_factor = detail::get_factor(From{}); + constexpr QuantitySpec auto to_factor = detail::get_factor(To{}); + constexpr QuantitySpec auto from_root = get_hierarchy_root(from_factor); + constexpr QuantitySpec auto to_root = get_hierarchy_root(to_factor); + constexpr std::string_view from_root_name = detail::type_name(); + constexpr std::string_view to_root_name = detail::type_name(); + + // Type lists are sorted by the root name. This allows us to easily progress through both lists to find the quantities + // belonging to the same hierarchy tree. + if constexpr (from_root_name < to_root_name) + return detail::try_extract_common_base(type_list{}, type_list{}, + type_list{}, type_list{}); + else if constexpr (from_root_name > to_root_name) + return detail::try_extract_common_base(type_list{}, type_list{}, + type_list{}, type_list{}); else { - constexpr auto num_from_compl = get_complexity(NumFrom{}); - constexpr auto den_from_compl = get_complexity(DenFrom{}); - constexpr auto num_to_compl = get_complexity(NumTo{}); - constexpr auto den_to_compl = get_complexity(DenTo{}); - constexpr auto max_compl = max({num_from_compl, num_to_compl, den_from_compl, den_to_compl}); - if constexpr (max_compl > 0) { - if constexpr (num_from_compl == max_compl) { - constexpr auto res = explode_to_equation(NumFrom{}); - return convertible( - (res.equation * ... * map_power(NumsFrom{})) / (map_power(DenFrom{}) * ... * map_power(DensFrom{})), - (map_power(NumTo{}) * ... * map_power(NumsTo{})) / (map_power(DenTo{}) * ... * map_power(DensTo{}))); - } else if constexpr (den_from_compl == max_compl) { - constexpr auto res = explode_to_equation(DenFrom{}); - return convertible( - (map_power(NumFrom{}) * ... * map_power(NumsFrom{})) / (res.equation * ... * map_power(DensFrom{})), - (map_power(NumTo{}) * ... * map_power(NumsTo{})) / (map_power(DenTo{}) * ... * map_power(DensTo{}))); - } else if constexpr (num_to_compl == max_compl) { - constexpr auto res = explode_to_equation(NumTo{}); - return min(res.result, convertible((map_power(NumFrom{}) * ... * map_power(NumsFrom{})) / - (map_power(DenFrom{}) * ... * map_power(DensFrom{})), - (res.equation * ... * map_power(NumsTo{})) / - (map_power(DenTo{}) * ... * map_power(DensTo{})))); + // We are dealing with quantities from the same hierarchy tree. + constexpr specs_convertible_result res = detail::convertible_common_base(from_factor, to_factor); + if constexpr (res == specs_convertible_result::no) + // We do not need to analyze the rest of the lists as the quantities are not convertible when at least one element + // is not convertible. + return std::optional(extract_common_base_result{specs_convertible_result::no}); + else { + // We might deal with different powers of quantities. Let's find the biggest common exponent and extract it. + // The rest of the quantities must be reinserted into the list. + constexpr ratio from_exp = detail::get_exponent(From{}); + constexpr ratio to_exp = detail::get_exponent(To{}); + if constexpr (from_exp == to_exp) + return std::optional(extract_common_base_result{res, type_list{}, + type_list{}}); + else if constexpr (from_exp > to_exp) { + using rest = power_or_T; + return std::optional(extract_common_base_result{res, type_list{}, + type_list{}}); } else { - constexpr auto res = explode_to_equation(DenTo{}); - return min(res.result, convertible((map_power(NumFrom{}) * ... * map_power(NumsFrom{})) / - (map_power(DenFrom{}) * ... * map_power(DensFrom{})), - (map_power(NumTo{}) * ... * map_power(NumsTo{})) / - (res.equation * ... * map_power(DensTo{})))); + using rest = power_or_T; + return std::optional(extract_common_base_result{res, type_list{}, + type_list{}}); } } } } -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<> num_from, - type_list den_from, - type_list num_to, - type_list) +template +[[nodiscard]] consteval auto try_extract_common_base(From from, To to) { - if constexpr (constexpr auto extD = extract_convertible_quantities(DenFrom{}, DenTo{}); extD.same_kind) - return prepend_and_process_rest(num_from, type_list{}, num_to, - type_list{}); - else if constexpr (constexpr auto extT = extract_convertible_quantities(NumTo{}, DenTo{}); extT.same_kind) - return prepend_and_process_rest(num_from, den_from, type_list{}, - type_list{}); - else { - constexpr auto den_from_compl = get_complexity(DenFrom{}); - constexpr auto num_to_compl = get_complexity(NumTo{}); - constexpr auto den_to_compl = get_complexity(DenTo{}); - constexpr auto max_compl = max({num_to_compl, den_from_compl, den_to_compl}); - if constexpr (max_compl > 0) { - if constexpr (den_from_compl == max_compl) { - constexpr auto res = explode_to_equation(DenFrom{}); - return convertible( - dimensionless / (res.equation * ... * map_power(DensFrom{})), - (map_power(NumTo{}) * ... * map_power(NumsTo{})) / (map_power(DenTo{}) * ... * map_power(DensTo{}))); - } else if constexpr (num_to_compl == max_compl) { - constexpr auto res = explode_to_equation(NumTo{}); - return min(res.result, convertible(dimensionless / (map_power(DenFrom{}) * ... * map_power(DensFrom{})), - (res.equation * ... * map_power(NumsTo{})) / - (map_power(DenTo{}) * ... * map_power(DensTo{})))); - } else { - constexpr auto res = explode_to_equation(DenTo{}); - return min(res.result, convertible(dimensionless / (map_power(DenFrom{}) * ... * map_power(DensFrom{})), - (map_power(NumTo{}) * ... * map_power(NumsTo{})) / - (res.equation * ... * map_power(DensTo{})))); - } - } - } + return detail::try_extract_common_base(type_list<>{}, from, type_list<>{}, to); } -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list num_from, - type_list<> den_from, - type_list, - type_list den_to) +template +[[nodiscard]] consteval std::tuple get_max_complexity(type_list) { - if constexpr (constexpr auto extN = extract_convertible_quantities(NumFrom{}, NumTo{}); extN.same_kind) - return prepend_and_process_rest(type_list{}, den_from, - type_list{}, den_to); - else if constexpr (constexpr auto extT = extract_convertible_quantities(NumTo{}, DenTo{}); extT.same_kind) - return prepend_and_process_rest(num_from, den_from, type_list{}, - type_list{}); + if constexpr (sizeof...(Elems) == 0) + return {-1, -1}; else { - constexpr auto num_from_compl = get_complexity(NumFrom{}); - constexpr auto num_to_compl = get_complexity(NumTo{}); - constexpr auto den_to_compl = get_complexity(DenTo{}); - constexpr auto max_compl = max({num_from_compl, num_to_compl, den_to_compl}); - if constexpr (max_compl > 0) { - if constexpr (num_from_compl == max_compl) { - constexpr auto res = explode_to_equation(NumFrom{}); - return convertible( - (res.equation * ... * map_power(NumsFrom{})), - (map_power(NumTo{}) * ... * map_power(NumsTo{})) / (map_power(DenTo{}) * ... * map_power(DensTo{}))); - } else if constexpr (num_to_compl == max_compl) { - constexpr auto res = explode_to_equation(NumTo{}); - return min(res.result, convertible((map_power(NumFrom{}) * ... * map_power(NumsFrom{})), - (res.equation * ... * map_power(NumsTo{})) / - (map_power(DenTo{}) * ... * map_power(DensTo{})))); - } else { - constexpr auto res = explode_to_equation(DenTo{}); - return min(res.result, convertible((map_power(NumFrom{}) * ... * map_power(NumsFrom{})), - (map_power(NumTo{}) * ... * map_power(NumsTo{})) / - (res.equation * ... * map_power(DensTo{})))); - } - } + const std::initializer_list list{detail::get_complexity_impl(Elems{})...}; + const auto it = max_element(list.begin(), list.end()); + return {*it, it - list.begin()}; } } -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list num_from, - type_list, - type_list<> num_to, - type_list den_to) -{ - if constexpr (constexpr auto extD = extract_convertible_quantities(DenFrom{}, DenTo{}); extD.same_kind) - return prepend_and_process_rest(num_from, type_list{}, num_to, - type_list{}); - else if constexpr (constexpr auto extF = extract_convertible_quantities(NumFrom{}, DenFrom{}); extF.same_kind) - return prepend_and_process_rest(type_list{}, type_list{}, - num_to, den_to); - else { - constexpr auto num_from_compl = get_complexity(NumFrom{}); - constexpr auto den_from_compl = get_complexity(DenFrom{}); - constexpr auto den_to_compl = get_complexity(DenTo{}); - constexpr auto max_compl = max({num_from_compl, den_from_compl, den_to_compl}); - if constexpr (max_compl > 0) { - if constexpr (num_from_compl == max_compl) { - constexpr auto res = explode_to_equation(NumFrom{}); - return convertible( - (res.equation * ... * map_power(NumsFrom{})) / (map_power(DenFrom{}) * ... * map_power(DensFrom{})), - dimensionless / (map_power(DenTo{}) * ... * map_power(DensTo{}))); - } else if constexpr (den_from_compl == max_compl) { - constexpr auto res = explode_to_equation(DenFrom{}); - return convertible( - (map_power(NumFrom{}) * ... * map_power(NumsFrom{})) / (res.equation * ... * map_power(DensFrom{})), - dimensionless / (map_power(DenTo{}) * ... * map_power(DensTo{}))); - } else { - constexpr auto res = explode_to_equation(DenTo{}); - return min(res.result, convertible((map_power(NumFrom{}) * ... * map_power(NumsFrom{})) / - (map_power(DenFrom{}) * ... * map_power(DensFrom{})), - dimensionless / (res.equation * ... * map_power(DensTo{})))); - } - } - } -} +enum class ingredient_type : std::int8_t { numerator_from, denominator_from, numerator_to, denominator_to }; -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list, - type_list den_from, - type_list num_to, - type_list<> den_to) +template +[[nodiscard]] consteval std::tuple get_max_complexity(NumFrom num_from, + DenFrom den_from, NumTo num_to, + DenTo den_to) { - if constexpr (constexpr auto extN = extract_convertible_quantities(NumFrom{}, NumTo{}); extN.same_kind) - return prepend_and_process_rest(type_list{}, den_from, - type_list{}, den_to); - else if constexpr (constexpr auto extF = extract_convertible_quantities(NumFrom{}, DenFrom{}); extF.same_kind) - return prepend_and_process_rest(type_list{}, type_list{}, - num_to, den_to); - else { - constexpr auto num_from_compl = get_complexity(NumFrom{}); - constexpr auto den_from_compl = get_complexity(DenFrom{}); - constexpr auto num_to_compl = get_complexity(NumTo{}); - constexpr auto max_compl = max({num_from_compl, num_to_compl, den_from_compl}); - if constexpr (max_compl > 0) { - if constexpr (num_from_compl == max_compl) { - constexpr auto res = explode_to_equation(NumFrom{}); - return convertible( - (res.equation * ... * map_power(NumsFrom{})) / (map_power(DenFrom{}) * ... * map_power(DensFrom{})), - (map_power(NumTo{}) * ... * map_power(NumsTo{}))); - } else if constexpr (den_from_compl == max_compl) { - constexpr auto res = explode_to_equation(DenFrom{}); - return convertible( - (map_power(NumFrom{}) * ... * map_power(NumsFrom{})) / (res.equation * ... * map_power(DensFrom{})), - (map_power(NumTo{}) * ... * map_power(NumsTo{}))); - } else { - constexpr auto res = explode_to_equation(NumTo{}); - return min(res.result, convertible((map_power(NumFrom{}) * ... * map_power(NumsFrom{})) / - (map_power(DenFrom{}) * ... * map_power(DensFrom{})), - (res.equation * ... * map_power(NumsTo{})))); - } - } - } + const std::initializer_list> list{ + detail::get_max_complexity(num_from), detail::get_max_complexity(den_from), detail::get_max_complexity(num_to), + detail::get_max_complexity(den_to)}; + const auto it = + max_element(list.begin(), list.end(), [](auto lhs, auto rhs) { return std::get<0>(lhs) < std::get<0>(rhs); }); + return std::tuple_cat(*it, std::tuple{static_cast(it - list.begin())}); } -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list, - type_list<> den_from, - type_list, - type_list<> den_to) +template +[[nodiscard]] consteval auto merge_with_equation(Equation, Num, Den) { - if constexpr (constexpr auto ext = extract_convertible_quantities(NumFrom{}, NumTo{}); ext.same_kind) { - return prepend_and_process_rest(type_list{}, den_from, - type_list{}, den_to); - } else { - constexpr auto num_from_compl = get_complexity(NumFrom{}); - constexpr auto num_to_compl = get_complexity(NumTo{}); - constexpr auto max_compl = max(num_from_compl, num_to_compl); - if constexpr (max_compl > 0) { - if constexpr (num_from_compl == max_compl) { - constexpr auto res = explode_to_equation(NumFrom{}); - return convertible((res.equation * ... * map_power(NumsFrom{})), - (map_power(NumTo{}) * ... * map_power(NumsTo{}))); - } else { - constexpr auto res = explode_to_equation(NumTo{}); - return min(res.result, convertible((map_power(NumFrom{}) * ... * map_power(NumsFrom{})), - (res.equation * ... * map_power(NumsTo{})))); - } - } - } + constexpr QuantitySpec auto merged_qs = [&]() { + if constexpr (DerivedQuantitySpec) + return detail::get_optimized_expression< + type_list_merge_sorted, + type_list_merge_sorted, struct dimensionless, + type_list_of_quantity_spec_less, derived_quantity_spec>(); + else + return detail::get_optimized_expression< + type_list_merge_sorted, type_list_of_quantity_spec_less>, Den, struct dimensionless, + type_list_of_quantity_spec_less, derived_quantity_spec>(); + }(); + using qs_type = MP_UNITS_NONCONST_TYPE(merged_qs); + if constexpr (DerivedQuantitySpec) + return std::pair(typename qs_type::_num_{}, typename qs_type::_den_{}); + else + return std::pair(type_list{}, type_list<>{}); } -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<> num_from, - type_list, - type_list<> num_to, - type_list) -{ - if constexpr (constexpr auto ext = extract_convertible_quantities(DenFrom{}, DenTo{}); ext.same_kind) - return prepend_and_process_rest(num_from, type_list{}, num_to, - type_list{}); - else { - constexpr auto den_from_compl = get_complexity(DenFrom{}); - constexpr auto den_to_compl = get_complexity(DenTo{}); - constexpr auto max_compl = max(den_from_compl, den_to_compl); - if constexpr (max_compl > 0) { - if constexpr (den_from_compl == max_compl) { - constexpr auto res = explode_to_equation(DenFrom{}); - return convertible(dimensionless / (res.equation * ... * map_power(DensFrom{})), - dimensionless / (map_power(DenTo{}) * ... * map_power(DensTo{}))); - } else { - constexpr auto res = explode_to_equation(DenTo{}); - return min(res.result, convertible(dimensionless / (map_power(DenFrom{}) * ... * map_power(DensFrom{})), - dimensionless / (res.equation * ... * map_power(DensTo{})))); - } - } - } -} +template +[[nodiscard]] consteval specs_convertible_result convertible(From, To); -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list, - type_list<>, type_list<>, - type_list) +template +[[nodiscard]] constexpr specs_convertible_result are_ingredients_convertible(NumFrom num_from, DenFrom den_from, + NumTo num_to, DenTo den_to) { - constexpr auto num_from_compl = get_complexity(NumFrom{}); - constexpr auto den_to_compl = get_complexity(DenTo{}); - constexpr auto max_compl = max(num_from_compl, den_to_compl); - if constexpr (max_compl > 0) { - if constexpr (num_from_compl == max_compl) { - constexpr auto res = explode_to_equation(NumFrom{}); - return convertible((res.equation * ... * map_power(NumsFrom{})), - dimensionless / (map_power(DenTo{}) * ... * map_power(DensTo{}))); + constexpr std::size_t num_from_size = type_list_size; + constexpr std::size_t den_from_size = type_list_size; + constexpr std::size_t num_to_size = type_list_size; + constexpr std::size_t den_to_size = type_list_size; + + // if at least one of the sides is empty then we compare with dimensionless + if constexpr (num_from_size == den_from_size && num_to_size + den_to_size == 0) + return specs_convertible_result::yes; + else if constexpr (num_from_size + den_from_size == 0 && num_to_size == den_to_size && num_to_size >= 1) + return specs_convertible_result::explicit_conversion; + else { + // otherwise, check if the pairwise ingredients are convertible + if constexpr (constexpr auto nums = detail::try_extract_common_base(NumFrom{}, NumTo{})) { + if (nums->result == specs_convertible_result::no) return specs_convertible_result::no; + return detail::min(nums->result, + detail::are_ingredients_convertible(nums->rest_from, den_from, nums->rest_to, den_to)); + } else if constexpr (constexpr auto dens = detail::try_extract_common_base(DenFrom{}, DenTo{})) { + if (dens->result == specs_convertible_result::no) return specs_convertible_result::no; + return detail::min(dens->result, + detail::are_ingredients_convertible(num_from, dens->rest_from, num_to, dens->rest_to)); } else { - constexpr auto res = explode_to_equation(DenTo{}); - return min(res.result, convertible((map_power(NumFrom{}) * ... * map_power(NumsFrom{})), - dimensionless / (res.equation * ... * map_power(DensTo{})))); - } - } -} + // otherwise, get the ingredient with the highest complexity + constexpr auto max_compl_res = detail::get_max_complexity(NumFrom{}, DenFrom{}, NumTo{}, DenTo{}); + constexpr int max_compl = std::get<0>(max_compl_res); + constexpr std::size_t idx = std::get<1>(max_compl_res); + constexpr ingredient_type type = std::get<2>(max_compl_res); + + constexpr auto list = [&] { + if constexpr (type == ingredient_type::numerator_from) + return NumFrom{}; + else if constexpr (type == ingredient_type::denominator_from) + return DenFrom{}; + else if constexpr (type == ingredient_type::numerator_to) + return NumTo{}; + else + return DenTo{}; + }(); + using extracted = type_list_extract; + + // before exploding check if the ingredient is convertible with dimensionless + if constexpr (type == ingredient_type::numerator_from && num_to_size + den_to_size == 0) { + constexpr specs_convertible_result res = + detail::convertible(detail::get_factor(typename extracted::element{}), dimensionless); + if constexpr (res != specs_convertible_result::no) + return detail::min(res, + detail::are_ingredients_convertible(typename extracted::rest{}, den_from, num_to, den_to)); + } else if constexpr (type == ingredient_type::numerator_to && num_from_size + den_from_size == 0) { + constexpr specs_convertible_result res = + detail::convertible(dimensionless, detail::get_factor(typename extracted::element{})); + if constexpr (res != specs_convertible_result::no) + return detail::min( + res, detail::are_ingredients_convertible(num_from, den_from, typename extracted::rest{}, den_to)); + } -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, - type_list, - type_list, type_list<>) -{ - constexpr auto den_from_compl = get_complexity(DenFrom{}); - constexpr auto num_to_compl = get_complexity(NumTo{}); - constexpr auto max_compl = max(den_from_compl, num_to_compl); - if constexpr (max_compl > 0) { - if constexpr (den_from_compl == max_compl) { - constexpr auto res = explode_to_equation(DenFrom{}); - return convertible(dimensionless / (res.equation * ... * map_power(DensFrom{})), - (map_power(NumTo{}) * ... * map_power(NumsTo{}))); - } else { - constexpr auto res = explode_to_equation(NumTo{}); - return min(res.result, convertible(dimensionless / (map_power(DenFrom{}) * ... * map_power(DensFrom{})), - (res.equation * ... * map_power(NumsTo{})))); + if constexpr (max_compl > 0) { + // explode the ingredient to the underlying equation + constexpr auto explode_res = detail::explode(typename extracted::element{}); + if constexpr (type == ingredient_type::numerator_from) { + const auto [num, den] = + detail::merge_with_equation(explode_res.equation, typename extracted::rest{}, den_from); + return detail::are_ingredients_convertible(num, den, num_to, den_to); + } else if constexpr (type == ingredient_type::denominator_from) { + const auto [num, den] = + detail::merge_with_equation(dimensionless / explode_res.equation, num_from, typename extracted::rest{}); + return detail::min(explode_res.result, detail::are_ingredients_convertible(num, den, num_to, den_to)); + } else if constexpr (type == ingredient_type::numerator_to) { + const auto [num, den] = detail::merge_with_equation(explode_res.equation, typename extracted::rest{}, den_to); + return detail::min(explode_res.result, detail::are_ingredients_convertible(num_from, den_from, num, den)); + } else { + const auto [num, den] = + detail::merge_with_equation(dimensionless / explode_res.equation, num_to, typename extracted::rest{}); + return detail::min(explode_res.result, detail::are_ingredients_convertible(num_from, den_from, num, den)); + } + } else + // this should never happen + return specs_convertible_result::no; } } } -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list, - type_list, type_list<>, - type_list<>) -{ - return specs_convertible_result::yes; -} - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, type_list<>, - type_list, type_list) -{ - return specs_convertible_result::explicit_conversion; -} - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list, type_list<>, - type_list<>, type_list<>) -{ - return specs_convertible_result::yes; -} - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, type_list, - type_list<>, type_list<>) -{ - return specs_convertible_result::yes; -} - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, type_list<>, - type_list, type_list<>) -{ - return specs_convertible_result::explicit_conversion; -} - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, type_list<>, type_list<>, - type_list) -{ - return specs_convertible_result::explicit_conversion; -} - -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(type_list<>, type_list<>, type_list<>, - type_list<>) -{ - return specs_convertible_result::yes; -} - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(From, To) -{ - return are_ingredients_convertible(type_list_sort{}, - type_list_sort{}, - type_list_sort{}, - type_list_sort{}); -} - -template -[[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(From, To) -{ - return are_ingredients_convertible(type_list_sort{}, - type_list_sort{}, - type_list{}, type_list<>{}); -} - -template +template [[nodiscard]] consteval specs_convertible_result are_ingredients_convertible(From, To) { - return are_ingredients_convertible(type_list{}, type_list<>{}, - type_list_sort{}, - type_list_sort{}); + if constexpr (DerivedQuantitySpec && DerivedQuantitySpec) + return detail::are_ingredients_convertible(typename From::_num_{}, typename From::_den_{}, typename To::_num_{}, + typename To::_den_{}); + else if constexpr (DerivedQuantitySpec && NamedQuantitySpec) { + if constexpr (To{} == dimensionless) + return detail::are_ingredients_convertible(typename From::_num_{}, typename From::_den_{}, type_list<>{}, + type_list<>{}); + else + return detail::are_ingredients_convertible(typename From::_num_{}, typename From::_den_{}, type_list{}, + type_list<>{}); + } else { + if constexpr (From{} == dimensionless) + return detail::are_ingredients_convertible(type_list<>{}, type_list<>{}, typename To::_num_{}, + typename To::_den_{}); + else + return detail::are_ingredients_convertible(type_list{}, type_list<>{}, typename To::_num_{}, + typename To::_den_{}); + } } template -[[nodiscard]] consteval specs_convertible_result convertible_kinds(From from_kind, To to_kind) +[[nodiscard]] consteval specs_convertible_result convertible_kinds(From from, To to) { - constexpr auto exploded_kind_result = [](specs_convertible_result res) { - using enum specs_convertible_result; - return res == no ? no : yes; - }; - if constexpr ((NamedQuantitySpec && NamedQuantitySpec) || - get_complexity(From{}) == get_complexity(To{})) - return exploded_kind_result(convertible(from_kind, to_kind)); - else if constexpr (get_complexity(From{}) > get_complexity(To{})) - return exploded_kind_result(convertible(explode(from_kind).quantity, to_kind)); - else - return exploded_kind_result(convertible(from_kind, explode(to_kind).quantity)); + constexpr auto from_root = detail::get_kind_tree_root(from); + constexpr auto to_root = detail::get_kind_tree_root(to); + using enum specs_convertible_result; + + if constexpr (QuantityKindSpec) { + auto res = detail::convertible(from_root, to_root); + // the below does not account for `explicit_conversion_beyond_kind` + return (res == explicit_conversion) ? yes : res; + } else + return detail::convertible(from, to_root); } template @@ -1385,19 +1045,16 @@ template { using enum specs_convertible_result; - if constexpr (have_common_base(From{}, To{})) { - if constexpr (is_child_of(From{}, To{})) return yes; - if constexpr (is_child_of(To{}, From{})) return explicit_conversion; - if constexpr (get_kind(From{}) == get_kind(To{})) return cast; - return no; - } else if constexpr (get_kind(From{}) != get_kind(To{})) + if constexpr (detail::have_common_base(From{}, To{})) + return detail::convertible_common_base(From{}, To{}); + else if constexpr (detail::get_kind_tree_root(From{}) != detail::get_kind_tree_root(To{})) return no; - else if constexpr (get_complexity(From{}) != get_complexity(To{})) { - if constexpr (get_complexity(From{}) > get_complexity(To{})) - return convertible(explode(from).quantity, to); + else if constexpr (detail::get_complexity(From{}) != detail::get_complexity(To{})) { + if constexpr (detail::get_complexity(From{}) > detail::get_complexity(To{})) + return detail::convertible(explode(from).quantity, to); else { - auto res = explode(to); - return min(res.result, convertible(from, res.quantity)); + auto res = detail::explode(to); + return detail::min(res.result, detail::convertible(from, res.quantity)); } } } @@ -1414,37 +1071,15 @@ template else if constexpr (From::dimension != To::dimension) return no; else if constexpr (QuantityKindSpec || QuantityKindSpec) - return convertible_kinds(get_kind_tree_root(from), get_kind_tree_root(to)); - else if constexpr (NestedQuantityKindSpecOf && get_kind_tree_root(To{}) == To{}) - return yes; + return detail::convertible_kinds(from, to); else if constexpr (NamedQuantitySpec && NamedQuantitySpec) - return convertible_named(from, to); - else if constexpr (DerivedQuantitySpec && DerivedQuantitySpec) - return are_ingredients_convertible(from, to); - else if constexpr (DerivedQuantitySpec) { - auto res = explode(from); - if constexpr (NamedQuantitySpec) - return convertible(res.quantity, to); - else if constexpr (requires { to._equation_; }) { - auto eq = explode_to_equation(to); - return min(eq.result, convertible(res.quantity, eq.equation)); - } else - return are_ingredients_convertible(from, to); - } else if constexpr (DerivedQuantitySpec) { - auto res = explode(to); - if constexpr (NamedQuantitySpec) - return min(res.result, convertible(from, res.quantity)); - else if constexpr (requires { from._equation_; }) - return min(res.result, convertible(from._equation_, res.quantity)); - else - return min(res.result, are_ingredients_convertible(from, to)); - } - // NOLINTEND(bugprone-branch-clone) - return no; + return detail::convertible_named(from, to); + else + return detail::are_ingredients_convertible(from, to); } template -constexpr specs_convertible_result convertible_result = convertible_impl(From{}, To{}); +constexpr specs_convertible_result convertible_result = detail::convertible_impl(From{}, To{}); template [[nodiscard]] consteval specs_convertible_result convertible(From, To) @@ -1465,7 +1100,7 @@ template template [[nodiscard]] consteval bool explicitly_convertible(From from, To to) { - return detail::convertible(from, to) >= detail::specs_convertible_result::explicit_conversion; + return detail::convertible(from, to) >= detail::specs_convertible_result::explicit_conversion_beyond_kind; } template @@ -1477,7 +1112,7 @@ template template [[nodiscard]] consteval bool interconvertible(QS1 qs1, QS2 qs2) { - return implicitly_convertible(qs1, qs2) && implicitly_convertible(qs2, qs1); + return mp_units::implicitly_convertible(qs1, qs2) && mp_units::implicitly_convertible(qs2, qs1); } MP_UNITS_EXPORT_END @@ -1485,8 +1120,8 @@ MP_UNITS_EXPORT_END namespace detail { template - requires requires(Q q) { get_kind_tree_root(q); } -using to_kind = decltype(get_kind_tree_root(Q{})); + requires requires(Q q) { detail::get_kind_tree_root(q); } +using to_kind = decltype(detail::get_kind_tree_root(Q{})); #if MP_UNITS_API_NO_CRTP template @@ -1496,28 +1131,28 @@ template [[nodiscard]] consteval bool defined_as_kind_impl(quantity_spec) #endif { - return contains(); + return mp_units::contains(); } template [[nodiscard]] consteval QuantitySpec auto get_kind_tree_root_impl(Q q) { auto defined_as_kind = [](QQ qq) { - if constexpr (requires { defined_as_kind_impl(qq); }) - return defined_as_kind_impl(QQ{}); + if constexpr (requires { detail::defined_as_kind_impl(qq); }) + return detail::defined_as_kind_impl(QQ{}); else return false; }; // NOLINTBEGIN(bugprone-branch-clone) if constexpr (QuantityKindSpec) { - return remove_kind(q); + return detail::remove_kind(q); } else if constexpr (defined_as_kind(Q{})) { return q; } else if constexpr (requires { Q::_parent_; }) { - return get_kind_tree_root(Q::_parent_); + return detail::get_kind_tree_root(Q::_parent_); } else if constexpr (DerivedQuantitySpec) { - return expr_map(q); + return detail::expr_map(q); } else { // root quantity return q; @@ -1526,7 +1161,7 @@ template } template -constexpr QuantitySpec auto get_kind_tree_root_result = get_kind_tree_root_impl(Q{}); +constexpr QuantitySpec auto get_kind_tree_root_result = detail::get_kind_tree_root_impl(Q{}); template [[nodiscard]] consteval QuantitySpec auto get_kind_tree_root(Q) @@ -1536,57 +1171,80 @@ template } // namespace detail -MP_UNITS_EXPORT_BEGIN -template +MP_UNITS_EXPORT template [[nodiscard]] consteval detail::QuantityKindSpec auto get_kind(Q) { return kind_of; } +namespace detail { + +template +[[nodiscard]] consteval bool have_common_quantity_spec(QS1, QS2) +{ + constexpr auto qs1 = detail::get_kind_tree_root(QS1{}); + constexpr auto qs2 = detail::get_kind_tree_root(QS2{}); + return (!detail::have_common_base(qs1, qs2) && + (mp_units::implicitly_convertible(qs1, qs2) || mp_units::implicitly_convertible(qs2, qs1))) || + (qs1 == qs2 || (detail::is_child_of(qs2, qs1) && QuantityKindSpec) || + (detail::is_child_of(qs1, qs2) && QuantityKindSpec)); +} + +} // namespace detail + +MP_UNITS_EXPORT_BEGIN + [[nodiscard]] consteval QuantitySpec auto get_common_quantity_spec(QuantitySpec auto q) { return q; } template - requires detail::QuantitySpecConvertibleTo || - detail::QuantitySpecConvertibleTo + requires(detail::have_common_quantity_spec(Q1{}, Q2{})) [[nodiscard]] consteval QuantitySpec auto get_common_quantity_spec(Q1 q1, Q2 q2) { - using QQ1 = decltype(detail::remove_kind(q1)); - using QQ2 = decltype(detail::remove_kind(q2)); - // NOLINTBEGIN(bugprone-branch-clone) - if constexpr (is_same_v) - return q1; - else if constexpr (detail::NestedQuantityKindSpecOf) - return detail::remove_kind(q1); - else if constexpr (detail::NestedQuantityKindSpecOf) - return detail::remove_kind(q2); - else if constexpr ((detail::QuantityKindSpec && !detail::QuantityKindSpec) || - (detail::DerivedQuantitySpec && detail::NamedQuantitySpec && - implicitly_convertible(Q1{}, Q2{}))) - return q2; - else if constexpr ((!detail::QuantityKindSpec && detail::QuantityKindSpec) || - (detail::NamedQuantitySpec && detail::DerivedQuantitySpec && - implicitly_convertible(Q2{}, Q1{}))) - return q1; - else if constexpr (detail::have_common_base(Q1{}, Q2{})) - return detail::get_common_base(q1, q2); - else if constexpr (implicitly_convertible(Q1{}, Q2{})) + if constexpr (is_same_v) return q1; + // quantity kinds have lower priority + else if constexpr (detail::QuantityKindSpec && !detail::QuantityKindSpec) return q2; - else if constexpr (implicitly_convertible(Q2{}, Q1{})) + else if constexpr (!detail::QuantityKindSpec && detail::QuantityKindSpec) return q1; - else if constexpr (implicitly_convertible(detail::get_kind_tree_root(Q1{}), detail::get_kind_tree_root(Q2{}))) - return detail::get_kind_tree_root(q2); - else - return detail::get_kind_tree_root(q1); + else { + constexpr bool q1q2 = mp_units::implicitly_convertible(Q1{}, Q2{}); + constexpr bool q2q1 = mp_units::implicitly_convertible(Q2{}, Q1{}); + + // in case of interconvertible quantities we treat derived quantities with a lower priority + if constexpr (q1q2 && q2q1) { + if constexpr (detail::DerivedQuantitySpec) + return q2; + else + return q1; + } + // we prefer a quantity to which we can convert the other quantity + else if constexpr (q1q2) + return q2; + else if constexpr (q2q1) + return q1; + // if quantities can't be converted in any direction check if they have a common base in the tree + else if constexpr (detail::have_common_base(Q1{}, Q2{})) + return detail::get_common_base(q1, q2); + else { + // verify kind tree roots as the last resort check + constexpr auto q1_root = detail::get_kind_tree_root(Q1{}); + constexpr auto q2_root = detail::get_kind_tree_root(Q2{}); + if constexpr (mp_units::implicitly_convertible(q1_root, q2_root)) + return q2_root; + else if constexpr (mp_units::implicitly_convertible(q2_root, q1_root)) + return q1_root; + } + } // NOLINTEND(bugprone-branch-clone) } [[nodiscard]] consteval QuantitySpec auto get_common_quantity_spec(QuantitySpec auto q1, QuantitySpec auto q2, QuantitySpec auto q3, QuantitySpec auto... rest) - requires requires { get_common_quantity_spec(get_common_quantity_spec(q1, q2), q3, rest...); } + requires requires { mp_units::get_common_quantity_spec(mp_units::get_common_quantity_spec(q1, q2), q3, rest...); } { - return get_common_quantity_spec(get_common_quantity_spec(q1, q2), q3, rest...); + return mp_units::get_common_quantity_spec(mp_units::get_common_quantity_spec(q1, q2), q3, rest...); } MP_UNITS_EXPORT_END diff --git a/src/core/include/mp-units/framework/quantity_spec_concepts.h b/src/core/include/mp-units/framework/quantity_spec_concepts.h index 110e52ebe6..6047c0ac21 100644 --- a/src/core/include/mp-units/framework/quantity_spec_concepts.h +++ b/src/core/include/mp-units/framework/quantity_spec_concepts.h @@ -25,7 +25,6 @@ // IWYU pragma: private, include #include #include -#include #include namespace mp_units { @@ -39,14 +38,6 @@ struct quantity_spec_interface_base; MP_UNITS_EXPORT template concept QuantitySpec = detail::SymbolicConstant && std::derived_from; -MP_UNITS_EXPORT -#if MP_UNITS_API_NO_CRTP -template -#else -template -#endif -struct quantity_spec; - template struct kind_of_; @@ -55,93 +46,19 @@ namespace detail { template concept QuantityKindSpec = QuantitySpec && is_specialization_of; -#if MP_UNITS_API_NO_CRTP -template -void to_base_specialization_of_quantity_spec(const volatile quantity_spec*); -#else -template -void to_base_specialization_of_quantity_spec(const volatile quantity_spec*); -#endif - -template -constexpr bool is_derived_from_specialization_of_quantity_spec = - requires(T* type) { to_base_specialization_of_quantity_spec(type); }; - -/** - * @brief Concept matching all named quantity specification types - * - * Satisfied by all types that derive from `quantity_spec`. - */ -template -concept NamedQuantitySpec = - QuantitySpec && is_derived_from_specialization_of_quantity_spec && (!QuantityKindSpec); - } // namespace detail -template -struct derived_quantity_spec; - -namespace detail { +MP_UNITS_EXPORT template +[[nodiscard]] consteval bool implicitly_convertible(From from, To to); /** - * @brief Concept matching all derived quantity specification types + * @brief A concept matching all quantity specifications of a provided quantity spec value * - * Satisfied by all `derived_quantity_spec` specializations. - * - * @note Deriving a strong type from it is considered a logic error and thus is - * explicitly not supported here. + * Satisfied by all quantity specifications that are implicitly convertible to the provided @c QS + * value. */ -template -concept DerivedQuantitySpec = - QuantitySpec && - (is_specialization_of || - (QuantityKindSpec && is_specialization_of)); - -} // namespace detail - - -MP_UNITS_EXPORT template -[[nodiscard]] consteval detail::QuantityKindSpec auto get_kind(Q q); - -namespace detail { - -template -[[nodiscard]] consteval bool is_child_of(Child ch, Parent p); - -template -concept ChildQuantitySpecOf = (is_child_of(Child, Parent)); - -template -concept NestedQuantityKindSpecOf = - QuantitySpec && QuantitySpec && - (get_kind(From) != get_kind(To)) && ChildQuantitySpecOf; - -template -concept QuantitySpecConvertibleTo = - QuantitySpec && QuantitySpec && - implicitly_convertible(From, To); - -template -concept QuantitySpecExplicitlyConvertibleTo = - QuantitySpec && QuantitySpec && - explicitly_convertible(From, To); - -template -concept QuantitySpecCastableTo = QuantitySpec && - QuantitySpec && castable(From, To); - -} // namespace detail - MP_UNITS_EXPORT template concept QuantitySpecOf = - QuantitySpec && QuantitySpec && detail::QuantitySpecConvertibleTo && - // the below is to make the following work - // static_assert(ReferenceOf); - // static_assert(!ReferenceOf); - // static_assert(!ReferenceOf - // static_assert(ReferenceOf); - // static_assert(!ReferenceOf); - !detail::NestedQuantityKindSpecOf && - (detail::QuantityKindSpec || !detail::NestedQuantityKindSpecOf); + QuantitySpec && QuantitySpec && (mp_units::implicitly_convertible(T{}, QS)); } // namespace mp_units diff --git a/src/core/include/mp-units/framework/reference.h b/src/core/include/mp-units/framework/reference.h index d4af7fba6d..1856fcdaad 100644 --- a/src/core/include/mp-units/framework/reference.h +++ b/src/core/include/mp-units/framework/reference.h @@ -187,24 +187,6 @@ struct reference { { return {}; } - - template - [[nodiscard]] friend consteval bool convertible(reference, reference) - { - return implicitly_convertible(Q{}, Q2{}) && convertible(U{}, U2{}); - } - - template - [[nodiscard]] friend consteval bool convertible(reference, U2 u2) - { - return implicitly_convertible(Q{}, get_quantity_spec(u2)) && convertible(U{}, u2); - } - - template - [[nodiscard]] friend consteval bool convertible(U1 u1, reference) - { - return implicitly_convertible(get_quantity_spec(u1), Q{}) && convertible(u1, U{}); - } }; diff --git a/src/core/include/mp-units/framework/reference_concepts.h b/src/core/include/mp-units/framework/reference_concepts.h index 775933afb9..2d3463994c 100644 --- a/src/core/include/mp-units/framework/reference_concepts.h +++ b/src/core/include/mp-units/framework/reference_concepts.h @@ -60,13 +60,13 @@ template concept Reference = AssociatedUnit || is_specialization_of; /** - * @brief A concept matching all references with provided quantity spec + * @brief A concept matching all references of the provided quantity spec * - * Satisfied by all references with a quantity_spec being the instantiation derived from - * the provided quantity_spec type. + * Satisfied by all references for which @c QuantitySpecOf is true. */ template -concept ReferenceOf = Reference && QuantitySpecOf; +concept ReferenceOf = Reference && QuantitySpec && + QuantitySpecOf; MP_UNITS_EXPORT_END diff --git a/src/core/include/mp-units/framework/symbolic_expression.h b/src/core/include/mp-units/framework/symbolic_expression.h index a0e0bc61e7..237a646008 100644 --- a/src/core/include/mp-units/framework/symbolic_expression.h +++ b/src/core/include/mp-units/framework/symbolic_expression.h @@ -143,8 +143,29 @@ constexpr bool is_specialization_of_power = false; template constexpr bool is_specialization_of_power> = true; +template +[[nodiscard]] consteval auto get_factor(T element) +{ + if constexpr (is_specialization_of_power) + return typename T::_factor_{}; + else + return element; +} + +template +[[nodiscard]] MP_UNITS_CONSTEVAL ratio get_exponent(T) +{ + // this covers both `power` and `power_v` + if constexpr (requires { T::_exponent_; }) + return T::_exponent_; + else if constexpr (requires { T::exponent; }) + return T::exponent; + else + return ratio{1}; +}; + template -consteval auto power_or_T_impl() +[[nodiscard]] consteval auto power_or_T_impl() { if constexpr (is_specialization_of_power) { return power_or_T_impl(); diff --git a/src/core/include/mp-units/framework/system_reference.h b/src/core/include/mp-units/framework/system_reference.h index 3df21be25a..79fc9bb453 100644 --- a/src/core/include/mp-units/framework/system_reference.h +++ b/src/core/include/mp-units/framework/system_reference.h @@ -66,7 +66,7 @@ struct system_reference { static constexpr auto coherent_unit = CoU; template - requires detail::UnitConvertibleTo + requires(interconvertible(coherent_unit, U{})) #if MP_UNITS_COMP_MSVC [[nodiscard]] constexpr decltype(reference{}) operator[](U) const #else diff --git a/src/core/include/mp-units/framework/unit.h b/src/core/include/mp-units/framework/unit.h index 43bf8b194a..dabe2e98de 100644 --- a/src/core/include/mp-units/framework/unit.h +++ b/src/core/include/mp-units/framework/unit.h @@ -126,30 +126,34 @@ constexpr auto get_canonical_unit_result = get_canonical_unit_impl(U{}, U{}); namespace detail { -template -concept PotentiallyConvertibleTo = Unit && Unit && - ((AssociatedUnit && AssociatedUnit && - implicitly_convertible(get_quantity_spec(From{}), get_quantity_spec(To{}))) || - (!AssociatedUnit && !AssociatedUnit)); - -} +// We are using a helper concept to benefit from short-circuiting +template +concept PotentiallyInterConvertibleTo = Unit && Unit && + (!AssociatedUnit || !AssociatedUnit || + explicitly_convertible(get_quantity_spec(U1{}), get_quantity_spec(U2{}))); +} // namespace detail -// convertible -template -[[nodiscard]] consteval bool convertible(From from, To to) +// interconvertible +template +[[nodiscard]] consteval bool interconvertible(U1 u1, U2 u2) { - if constexpr (is_same_v) + if constexpr (is_same_v) return true; - else if constexpr (detail::PotentiallyConvertibleTo) - return is_same_v; + else if constexpr (detail::PotentiallyInterConvertibleTo) + return is_same_v; else return false; } +template + requires(M != detail::unit_magnitude<>{} && M != mag<1>) +struct scaled_unit; + template struct derived_unit; +MP_UNITS_EXPORT struct one; + namespace detail { struct unit_interface { @@ -424,7 +428,7 @@ struct prefixed_unit : decltype(M * U)::_base_type_ { namespace detail { template - requires(convertible(U1{}, U2{})) + requires(interconvertible(U1{}, U2{})) [[nodiscard]] consteval Unit auto get_common_scaled_unit(U1, U2) { constexpr auto canonical_lhs = get_canonical_unit(U1{}); @@ -668,7 +672,7 @@ inline constexpr auto ppm = parts_per_million; [[nodiscard]] consteval Unit auto get_common_unit(Unit auto u) { return u; } template - requires(convertible(U1{}, U2{})) + requires(interconvertible(U1{}, U2{})) [[nodiscard]] consteval Unit auto get_common_unit(U1 u1, U2 u2) { if constexpr (is_same_v) @@ -729,7 +733,7 @@ using collapse_common_unit = type_list_unique< } // namespace detail template - requires(convertible(common_unit{}, NewUnit{})) + requires(interconvertible(common_unit{}, NewUnit{})) [[nodiscard]] consteval Unit auto get_common_unit(common_unit, NewUnit) { using type = detail::collapse_common_unit; @@ -740,7 +744,7 @@ template } template - requires(convertible(common_unit{}, NewUnit{})) + requires(interconvertible(common_unit{}, NewUnit{})) [[nodiscard]] consteval Unit auto get_common_unit(NewUnit nu, common_unit cu) { return get_common_unit(cu, nu); diff --git a/src/core/include/mp-units/framework/unit_concepts.h b/src/core/include/mp-units/framework/unit_concepts.h index 86ff5e2817..ec3b32c988 100644 --- a/src/core/include/mp-units/framework/unit_concepts.h +++ b/src/core/include/mp-units/framework/unit_concepts.h @@ -25,7 +25,6 @@ // IWYU pragma: private, include #include #include -#include #include #include @@ -45,15 +44,9 @@ struct unit_interface; MP_UNITS_EXPORT template concept Unit = detail::SymbolicConstant && std::derived_from; -template - requires(M != detail::unit_magnitude<>{} && M != mag<1>) -struct scaled_unit; - MP_UNITS_EXPORT template struct named_unit; -MP_UNITS_EXPORT struct one; - /** * @brief A concept to be used to define prefixes for a unit */ @@ -100,24 +93,21 @@ concept AssociatedUnit = Unit && detail::has_associated_quantity(U{}); /** * @brief A concept matching all units associated with the provided quantity spec * - * Satisfied by all units associated with the quantity_spec being the instantiation derived from - * the provided quantity_spec type. + * Satisfied by all units for which an associated quantity spec is implicitly convertible to + * the provided @c QS value. */ MP_UNITS_EXPORT template -concept UnitOf = - AssociatedUnit && QuantitySpec && - detail::QuantitySpecConvertibleTo && - // the below is to make `dimensionless[radian]` invalid - (get_kind(QS) == get_kind(get_quantity_spec(U{})) || !detail::NestedQuantityKindSpecOf); +concept UnitOf = AssociatedUnit && QuantitySpec && + (implicitly_convertible(get_quantity_spec(U{}), QS)); -MP_UNITS_EXPORT template -[[nodiscard]] consteval bool convertible(From from, To to); +MP_UNITS_EXPORT template +[[nodiscard]] consteval bool interconvertible(U1 u1, U2 u2); namespace detail { -template -concept UnitConvertibleTo = - Unit && Unit && (convertible(From, To)); +template +concept WeakUnitOf = + Unit && QuantitySpec && ((!AssociatedUnit) || UnitOf); /** * @brief A concept matching all units compatible with the provided unit and quantity spec @@ -125,10 +115,10 @@ concept UnitConvertibleTo = * Satisfied by all units that have the same canonical reference as `U2` and in case they * have associated quantity specification it should satisfy `UnitOf`. */ -MP_UNITS_EXPORT template +template concept UnitCompatibleWith = Unit && Unit && QuantitySpec && - (!AssociatedUnit || UnitOf) && detail::UnitConvertibleTo; + WeakUnitOf && (interconvertible(FromU, U{})); template concept OffsetUnit = Unit && requires { T::_point_origin_; }; diff --git a/src/core/include/mp-units/framework/value_cast.h b/src/core/include/mp-units/framework/value_cast.h index 1d70616c67..064533a677 100644 --- a/src/core/include/mp-units/framework/value_cast.h +++ b/src/core/include/mp-units/framework/value_cast.h @@ -44,8 +44,8 @@ namespace mp_units { * * @tparam ToU a unit to use for a target quantity */ -template> - requires(convertible(Q::reference, ToU)) +template> + requires detail::UnitCompatibleWith [[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) { return detail::sudo_cast>( @@ -81,16 +81,16 @@ template> - requires(convertible(Q::reference, ToU)) && RepresentationOf && - std::constructible_from + requires detail::UnitCompatibleWith && + RepresentationOf && std::constructible_from [[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) { return detail::sudo_cast>(std::forward(q)); } template> - requires(convertible(Q::reference, ToU)) && RepresentationOf && - std::constructible_from + requires detail::UnitCompatibleWith && + RepresentationOf && std::constructible_from [[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) { return value_cast(std::forward(q)); @@ -112,8 +112,8 @@ template> - requires(convertible(Q::reference, ToQ::unit)) && - (ToQ::quantity_spec == Q::quantity_spec) && std::constructible_from + requires detail::UnitCompatibleWith && + (ToQ::quantity_spec == Q::quantity_spec) && std::constructible_from [[nodiscard]] constexpr Quantity auto value_cast(FwdQ&& q) { return detail::sudo_cast(std::forward(q)); @@ -130,7 +130,7 @@ template> * @tparam ToU a unit to use for a target quantity point */ template> - requires(convertible(QP::reference, ToU)) + requires detail::UnitCompatibleWith [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { return quantity_point{value_cast(std::forward(qp).quantity_from_origin_is_an_implementation_detail_), @@ -167,8 +167,8 @@ template> - requires(convertible(QP::reference, ToU)) && RepresentationOf && - std::constructible_from + requires detail::UnitCompatibleWith && + RepresentationOf && std::constructible_from [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { return quantity_point{ @@ -177,8 +177,8 @@ template> - requires(convertible(QP::reference, ToU)) && RepresentationOf && - std::constructible_from + requires detail::UnitCompatibleWith && + RepresentationOf && std::constructible_from [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { return value_cast(std::forward(qp)); @@ -201,8 +201,8 @@ template> - requires(convertible(QP::reference, ToQ::unit)) && - (ToQ::quantity_spec == QP::quantity_spec) && std::constructible_from + requires detail::UnitCompatibleWith && + (ToQ::quantity_spec == QP::quantity_spec) && std::constructible_from [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { return quantity_point{value_cast(std::forward(qp).quantity_from_origin_is_an_implementation_detail_), @@ -238,9 +238,10 @@ template> - requires(convertible(QP::reference, ToQP::unit)) && (ToQP::quantity_spec == QP::quantity_spec) && - (detail::same_absolute_point_origins(ToQP::point_origin, QP::point_origin)) && - std::constructible_from + requires detail::UnitCompatibleWith && + (ToQP::quantity_spec == QP::quantity_spec) && + (detail::same_absolute_point_origins(ToQP::point_origin, QP::point_origin)) && + std::constructible_from [[nodiscard]] constexpr QuantityPoint auto value_cast(FwdQP&& qp) { return detail::sudo_cast(std::forward(qp)); diff --git a/test/static/concepts_test.cpp b/test/static/concepts_test.cpp index 0b1a7babea..154e835265 100644 --- a/test/static/concepts_test.cpp +++ b/test/static/concepts_test.cpp @@ -98,6 +98,32 @@ static_assert(QuantitySpec); static_assert(!QuantitySpec); static_assert(!QuantitySpec); +// QuantitySpecOf +static_assert(QuantitySpecOf); +static_assert(QuantitySpecOf); +static_assert(!QuantitySpecOf); +static_assert(QuantitySpecOf); +static_assert(!QuantitySpecOf); +static_assert(QuantitySpecOf); +static_assert(!QuantitySpecOf); +static_assert(QuantitySpecOf, isq::height>); +static_assert(QuantitySpecOf, isq::displacement>); + +static_assert(!QuantitySpecOf); +static_assert(!QuantitySpecOf>); +static_assert(!QuantitySpecOf, dimensionless>); +static_assert(!QuantitySpecOf, kind_of>); + +static_assert(!QuantitySpecOf); +static_assert(!QuantitySpecOf>); +static_assert(QuantitySpecOf, isq::angular_measure>); +static_assert(QuantitySpecOf, kind_of>); + +static_assert(!QuantitySpecOf); +static_assert(!QuantitySpecOf>); +static_assert(!QuantitySpecOf, isq::angular_measure>); +static_assert(!QuantitySpecOf, kind_of>); + // NamedQuantitySpec static_assert(detail::NamedQuantitySpec); static_assert(detail::NamedQuantitySpec); @@ -134,9 +160,6 @@ static_assert(!detail::QuantityKindSpec); static_assert(!detail::QuantityKindSpec); static_assert(!detail::QuantityKindSpec); -// QuantitySpecOf -// TODO add tests - // Unit static_assert(Unit); static_assert(Unit); @@ -229,6 +252,8 @@ static_assert(UnitOf); static_assert(UnitOf); static_assert(UnitOf); static_assert(UnitOf); +static_assert(UnitOf); +static_assert(UnitOf); static_assert(!UnitOf); static_assert(!UnitOf); static_assert(!UnitOf); diff --git a/test/static/quantity_point_test.cpp b/test/static/quantity_point_test.cpp index 975b2d6b94..ba5b47aadc 100644 --- a/test/static/quantity_point_test.cpp +++ b/test/static/quantity_point_test.cpp @@ -1697,7 +1697,8 @@ static_assert(invalid_subtraction(zero_Bq + 5 * isq::activity[Bq], 5 * isq::freq static_assert(invalid_subtraction(zero_Bq + 5 * isq::activity[Bq], zero_Hz + 5 * isq::frequency[Hz])); static_assert(invalid_addition(zero_Bq + 5 * isq::activity[Bq], 10 / (2 * isq::time[s]), 5 * isq::frequency[Hz])); -static_assert(invalid_addition(5 * isq::activity[Bq], zero_Hz + 10 / (2 * isq::time[s]), 5 * isq::frequency[Hz])); +static_assert(invalid_addition(5 * isq::activity[Bq], zero_Hz + 10 / (2 * isq::period_duration[s]), + 5 * isq::frequency[Hz])); static_assert(invalid_addition(5 * isq::activity[Bq], 10 / (2 * isq::time[s]), zero_Hz + 5 * isq::frequency[Hz])); static_assert(invalid_subtraction(zero_Bq + 5 * isq::activity[Bq], 10 / (2 * isq::time[s]), 5 * isq::frequency[Hz])); diff --git a/test/static/quantity_spec_test.cpp b/test/static/quantity_spec_test.cpp index f024ca5b55..936a16a0b4 100644 --- a/test/static/quantity_spec_test.cpp +++ b/test/static/quantity_spec_test.cpp @@ -40,6 +40,7 @@ using dim_one_ = struct dimension_one; inline constexpr struct dim_length_ final : base_dimension<"L"> {} dim_length; inline constexpr struct dim_mass_ final : base_dimension<"M"> {} dim_mass; inline constexpr struct dim_time_ final : base_dimension<"T"> {} dim_time; +inline constexpr struct dim_electric_current_ final : base_dimension<"I"> {} dim_electric_current; // quantities specification QUANTITY_SPEC_(length, dim_length); @@ -48,6 +49,7 @@ QUANTITY_SPEC_(time, dim_time); inline constexpr struct second_ final : named_unit<"s", kind_of