From cc3314903d596b4c5ca18636133b53ee44c79e79 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 21 Apr 2023 14:41:33 +0100 Subject: [PATCH] feat: `quantity_spec` logic implemented --- src/core/include/mp_units/quantity_spec.h | 952 +++++++++++++++---- test/unit_test/static/quantity_spec_test.cpp | 261 +++-- 2 files changed, 938 insertions(+), 275 deletions(-) diff --git a/src/core/include/mp_units/quantity_spec.h b/src/core/include/mp_units/quantity_spec.h index 8170b51d..db040d54 100644 --- a/src/core/include/mp_units/quantity_spec.h +++ b/src/core/include/mp_units/quantity_spec.h @@ -32,6 +32,7 @@ #include #include #include +#include namespace mp_units { @@ -53,9 +54,14 @@ template [[nodiscard]] consteval quantity_character derived_quantity_character(const type_list&, const type_list&) { - quantity_character num = common_quantity_character(quantity_character::scalar, expr_type::character...); - quantity_character den = common_quantity_character(quantity_character::scalar, expr_type::character...); - return common_quantity_character(num, den); + constexpr quantity_character num = + common_quantity_character(quantity_character::scalar, expr_type::character...); + constexpr quantity_character den = + common_quantity_character(quantity_character::scalar, expr_type::character...); + if constexpr (num == den) + return quantity_character::scalar; + else + return common_quantity_character(num, den); } /** @@ -74,7 +80,8 @@ template } template -struct quantity_spec_less : std::bool_constant() < type_name()> {}; +struct quantity_spec_less : std::bool_constant() < type_name()> { +}; template using type_list_of_quantity_spec_less = expr_less; @@ -92,36 +99,33 @@ template struct quantity_spec_interface { #ifdef __cpp_explicit_this_parameter template - [[nodiscard]] consteval std::same_as> auto operator[](this const Self, U u) + [[nodiscard]] consteval Reference auto operator[](this Self self, U u) const + requires(implicitly_convertible(self, detail::get_associated_quantity(u))) + { + return reference{}; + } + + template + [[nodiscard]] constexpr Quantity auto operator()(this Self self, Q&& q) const + requires Quantity> && (explicitly_convertible(std::remove_cvref_t::quantity_spec, self)) + { + return std::forward(q).number() * reference::unit>{}; + } #else template - // TODO can we somehow return an explicit reference type here? - [[nodiscard]] consteval std::same_as> auto operator[](U u) const -#endif - requires(Self::dimension == detail::get_associated_quantity(u).dimension) + [[nodiscard]] consteval Reference auto operator[](U u) const + requires(implicitly_convertible(Self{}, detail::get_associated_quantity(u))) { return reference{}; } -#ifdef __cpp_explicit_this_parameter template - requires Quantity> && - (explicitly_convertible_to(std::remove_cvref_t::quantity_spec, Self{})) - [[nodiscard]] constexpr std::same_as< - quantity::unit>{}, typename Q::rep>> auto - operator()(this const Self, Q&& q) const -#else - template - requires Quantity> && - (explicitly_convertible_to(std::remove_cvref_t::quantity_spec, Self{})) - // TODO can we somehow return an explicit quantity type here? - [[nodiscard]] constexpr std::same_as< - quantity::unit>{}, typename std::remove_cvref_t::rep>> auto - operator()(Q&& q) const -#endif + requires Quantity> && (explicitly_convertible(std::remove_cvref_t::quantity_spec, Self{})) + [[nodiscard]] constexpr Quantity auto operator()(Q&& q) const { return std::forward(q).number() * reference::unit>{}; } +#endif }; } // namespace detail @@ -186,9 +190,11 @@ struct quantity_spec; */ #ifdef __cpp_explicit_this_parameter template auto... Args> + requires(... && !QuantitySpec>) struct quantity_spec : detail::quantity_spec_interface { #else template auto... Args> + requires(... && !QuantitySpec>) struct quantity_spec : detail::quantity_spec_interface { #endif static constexpr BaseDimension auto dimension = Dim; @@ -211,8 +217,8 @@ struct quantity_spec : detail::quantity_spec_interface * @code{.cpp} * inline constexpr struct area : quantity_spec(length)> {} area; * inline constexpr struct volume : quantity_spec(length)> {} volume; - * inline constexpr struct velocity : quantity_spec {} velocity; // vector - * inline constexpr struct speed : quantity_spec {} speed; + * inline constexpr struct velocity : quantity_spec {} velocity; + * inline constexpr struct speed : quantity_spec {} speed; * inline constexpr struct force : quantity_spec {} force; * inline constexpr struct power : quantity_spec {} power; * @endcode @@ -227,9 +233,11 @@ struct quantity_spec : detail::quantity_spec_interface */ #ifdef __cpp_explicit_this_parameter template auto... Args> + requires(... && !QuantitySpec>) struct quantity_spec : detail::quantity_spec_interface { #else template + requires(... && !QuantitySpec>) struct quantity_spec : detail::quantity_spec_interface { #endif static constexpr auto _equation_ = Eq; @@ -241,7 +249,7 @@ struct quantity_spec : detail::quantity_spec_interface * @brief Specialization defining a leaf quantity in the hierarchy * * Quantities of the same kind form a hierarchy. This specialization adds new leaf to such a tree which - * can later be used as parent by other quantities. + * can later be used as a parent by other quantities. * * The character of those quantities by default is derived from the parent quantity. * @@ -253,7 +261,6 @@ struct quantity_spec : detail::quantity_spec_interface * inline constexpr struct height : quantity_spec {} height; * inline constexpr struct diameter : quantity_spec {} diameter; * inline constexpr struct position_vector : quantity_spec {} position_vector; - * inline constexpr struct speed : quantity_spec {} speed; * @endcode * * @note A common convention in this library is to assign the same name for a type and an object of this type. @@ -266,9 +273,11 @@ struct quantity_spec : detail::quantity_spec_interface */ #ifdef __cpp_explicit_this_parameter template auto... Args> + requires(... && !QuantitySpec>) struct quantity_spec : std::remove_const_t { #else template + requires(... && !QuantitySpec>) struct quantity_spec : std::remove_const_t { #endif static constexpr auto _parent_ = QS; @@ -276,18 +285,15 @@ struct quantity_spec : std::remove_const_t { #ifndef __cpp_explicit_this_parameter template - [[nodiscard]] consteval std::same_as> auto operator[](U u) const - requires(Self::dimension == detail::get_associated_quantity(u).dimension) + [[nodiscard]] consteval Reference auto operator[](U u) const + requires(implicitly_convertible(Self{}, detail::get_associated_quantity(u))) { return reference{}; } template - requires Quantity> && - (explicitly_convertible_to(std::remove_cvref_t::quantity_spec, Self{})) - [[nodiscard]] constexpr std::same_as< - quantity::unit>{}, typename std::remove_cvref_t::rep>> auto - operator()(Q&& q) const + requires Quantity> && (explicitly_convertible(std::remove_cvref_t::quantity_spec, Self{})) + [[nodiscard]] constexpr Quantity auto operator()(Q&& q) const { return std::forward(q).number() * reference::unit>{}; } @@ -298,7 +304,7 @@ struct quantity_spec : std::remove_const_t { * @brief Specialization defining a leaf derived quantity in the hierarchy and refining paren't equation * * Quantities of the same kind form a hierarchy. This specialization adds new leaf to such a tree which - * can later be used as parent by other quantities. Additionally, this defintion adds additional + * can later be used as a parent by other quantities. Additionally, this defintion adds additional * constraints on the derived quantity's equation. * * The character of those quantities by default is derived from the parent quantity. @@ -323,12 +329,16 @@ struct quantity_spec : std::remove_const_t { #ifdef __cpp_explicit_this_parameter template auto... Args> - requires requires { QS._equation_; } && (implicitly_convertible(Eq, QS._equation_)) + requires(!requires { QS._equation_; } || + (requires { QS._equation_; } && (explicitly_convertible(Eq, QS._equation_)))) && + (... && !QuantitySpec>) struct quantity_spec : quantity_spec { #else template - requires requires { QS._equation_; } && (implicitly_convertible(Eq, QS._equation_)) + requires(!requires { QS._equation_; } || + (requires { QS._equation_; } && (explicitly_convertible(Eq, QS._equation_)))) && + (... && !QuantitySpec>) struct quantity_spec : quantity_spec { #endif static constexpr auto _equation_ = Eq; @@ -349,24 +359,6 @@ struct quantity_spec : quantity_spec { #endif -/** - * @brief Quantity kind specifier - * - * Specifies that the provided `Q` should be treated as a quantity kind. - */ -template - requires(get_kind(Q) == Q) -#ifdef __cpp_explicit_this_parameter -struct kind_of_ : Q { -}; -#else -struct kind_of_ : quantity_spec, Q> { -}; -#endif - -template - requires(get_kind(Q) == Q) -inline constexpr kind_of_ kind_of; /** * @brief A specification of a derived quantity @@ -378,7 +370,7 @@ inline constexpr kind_of_ kind_of; * Instead of using a raw list of exponents this library decided to use expression template syntax to make types * more digestable for the user both for quantity specification and its dimension. The positive exponents are ordered * first and all negative exponents are put as a list into the `per<...>` class template. If a power of exponent - * is different than `1` the dimension type is enclosed in `power` class template. Otherwise, it is + * is different than `1` the quantity type is enclosed in `power` class template. Otherwise, it is * just put directly in the list without any wrapper. In case all of the exponents are negative than the * `dimensionless`/`dimension_one` is put in the front to increase the readability. * @@ -421,7 +413,7 @@ struct derived_quantity_spec : detail::expr_map(_base_{}); static constexpr quantity_character character = - detail::derived_quantity_character(typename _base_::_num_{}, typename _base_::_num_{}); + detail::derived_quantity_character(typename _base_::_num_{}, typename _base_::_den_{}); }; /** @@ -432,10 +424,31 @@ struct derived_quantity_spec : */ QUANTITY_SPEC(dimensionless, derived_quantity_spec<>{}); +/** + * @brief Quantity kind specifier + * + * Specifies that the provided `Q` should be treated as a quantity kind. + */ +template + requires(get_kind(Q) == Q) +#ifdef __cpp_explicit_this_parameter +struct kind_of_ : Q { +}; +#else +struct kind_of_ : quantity_spec, Q> { +}; +#endif + +template + requires(get_kind(Q) == Q) +inline constexpr kind_of_ kind_of; + + namespace detail { template<> -struct is_dimensionless : std::true_type {}; +struct is_dimensionless : std::true_type { +}; template [[nodiscard]] consteval QuantitySpec auto clone_kind_of(Q q) @@ -507,6 +520,8 @@ template { if constexpr (q == dimensionless) return q; + else if constexpr (Num == 1 && Den == 1) + return q; else if constexpr (detail::IntermediateDerivedQuantitySpec) return detail::clone_kind_of( detail::expr_pow( @@ -524,22 +539,28 @@ namespace detail { enum class convertible_to_result { no, cast, explicit_conversion, yes }; template -[[nodiscard]] consteval ratio get_complexity(Q); +[[nodiscard]] consteval int get_complexity(Q); template -[[nodiscard]] consteval ratio get_complexity(type_list) +[[nodiscard]] consteval int get_complexity(type_list) { return (0 + ... + get_complexity(Ts{})); } template -[[nodiscard]] consteval ratio get_complexity(power) +[[nodiscard]] consteval int get_complexity(power) { - return get_complexity(Q{}) * power::exponent; + return get_complexity(Q{}); +} + +template +[[nodiscard]] consteval int get_complexity(kind_of_) +{ + return get_complexity(Q); } template -[[nodiscard]] consteval ratio get_complexity(Q) +[[nodiscard]] consteval int get_complexity(Q) { if constexpr (detail::IntermediateDerivedQuantitySpec) return get_complexity(typename Q::_num_{}) + get_complexity(typename Q::_den_{}); @@ -553,194 +574,751 @@ template struct ingredients_less : - std::bool_constant<(lhs_eq > rhs_eq) || (lhs_eq == rhs_eq && lhs_compl > rhs_compl) || - (lhs_eq == rhs_eq && lhs_compl == rhs_compl && - type_name>() < - type_name>()) || - (lhs_eq == rhs_eq && lhs_compl == rhs_compl && - type_name>() == - type_name>() && - type_name() < type_name())> {}; + std::bool_constant< + (lhs_compl > rhs_compl) || + (lhs_compl == rhs_compl && (Lhs::dimension != Rhs::dimension && Rhs::dimension == dimension_one) || + type_name>() < + type_name>()) || + (lhs_compl == rhs_compl && Lhs::dimension == Rhs::dimension && type_name() < type_name())> { +}; template using type_list_of_ingredients_less = expr_less; -template -[[nodiscard]] consteval convertible_to_result are_ingredients_convertible_to(derived_quantity_spec, - derived_quantity_spec) +template + requires requires { Q::_equation_; } +[[nodiscard]] consteval bool defines_equation(Q) { - // order ingredients by their complexity (number of base quantities involved in the definition) - // if first ingredients are of different complexity extract the most complex one - // repeat above until first ingredients will have the same complexity and dimension - // check interconvertibility of quantities with the same dimension and continue to the nex one if successful - // repeat until the end of the list or not interconvertible quantities are found - return convertible_to_result::yes; -} - -template -[[nodiscard]] consteval convertible_to_result are_ingredients_convertible_to(derived_quantity_spec, - QuantitySpec auto) -{ - // order ingredients by their complexity (number of base quantities involved in the definition) - // if first ingredients are of different complexity extract the most complex one - // repeat above until first ingredients will have the same complexity and dimension - // check interconvertibility of quantities with the same dimension and continue to the nex one if successful - // repeat until the end of the list or not interconvertible quantities are found - return convertible_to_result::yes; -} - -template -[[nodiscard]] consteval convertible_to_result are_ingredients_convertible_to(QuantitySpec auto, - derived_quantity_spec) -{ - // order ingredients by their complexity (number of base quantities involved in the definition) - // if first ingredients are of different complexity extract the most complex one - // repeat above until first ingredients will have the same complexity and dimension - // check interconvertibility of quantities with the same dimension and continue to the nex one if successful - // repeat until the end of the list or not interconvertible quantities are found - return convertible_to_result::yes; + if constexpr (requires { Q::_parent_; }) + return Q::_parent_._equation_ != Q::_equation_; + else + return true; } template -[[nodiscard]] consteval auto get_equation(Q) +struct explode_to_equation_result { + Q equation; + convertible_to_result convertible_result; +}; + +template + requires requires { Q::_equation_; } +[[nodiscard]] consteval auto explode_to_equation(Q q) { - return Q::_equation_; + return explode_to_equation_result{ + Q::_equation_, defines_equation(q) ? convertible_to_result::yes : convertible_to_result::explicit_conversion}; } template -[[nodiscard]] consteval auto get_equation(power) + requires requires { Q::_equation_; } +[[nodiscard]] consteval auto explode_to_equation(power) { constexpr ratio exp = power::exponent; - return pow(Q::_equation_); + return explode_to_equation_result{ + pow(Q::_equation_), + defines_equation(Q{}) ? convertible_to_result::yes : convertible_to_result::explicit_conversion}; } -template +template +struct explode_result { + Q quantity; + convertible_to_result convertible_result = convertible_to_result::yes; + + template + [[nodiscard]] consteval explode_result common_convertibility_with(explode_to_equation_result res) const + { + return {quantity, std::min(convertible_result, res.convertible_result)}; + } +}; + +template [[nodiscard]] consteval auto explode(Q q); -template +template [[nodiscard]] consteval auto explode(Q q); -template -[[nodiscard]] consteval auto explode(Q q, type_list, type_list) +template +[[nodiscard]] consteval auto explode(Q, type_list, type_list) { constexpr auto n = get_complexity(Num{}); constexpr auto d = get_complexity(Den{}); constexpr auto max = n > d ? n : d; - if constexpr (max <= Complexity) - return q; + if constexpr (max == Complexity || ((n >= d && !requires { explode_to_equation(Num{}); }) || + (n < d && !requires { explode_to_equation(Den{}); }))) + return explode_result{(map_power(Num{}) * ... * map_power(Nums{})) / (map_power(Den{}) * ... * map_power(Dens{}))}; else { - if constexpr (n >= d) - return explode((get_equation(Num{}) * ... * map_power(Nums{})) / - (map_power(Den{}) * ... * map_power(Dens{}))); - else + if constexpr (n >= d) { + 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{})) / - (get_equation(Den{}) * ... * map_power(Dens{}))); + (res.equation * ... * map_power(Dens{}))) + .common_convertibility_with(res); + } } } -template -[[nodiscard]] consteval auto explode(Q q, type_list, type_list<>) +template +[[nodiscard]] consteval auto explode(Q, type_list, type_list<>) { constexpr auto n = get_complexity(Num{}); - if constexpr (n <= Complexity) - return q; - else - return explode((get_equation(Num{}) * ... * map_power(Nums{}))); + 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(Q q, type_list<>, type_list) +template +[[nodiscard]] consteval auto explode(Q, type_list<>, type_list) { constexpr auto d = get_complexity(Den{}); - if constexpr (d <= Complexity) - return q; - else - return explode(dimensionless / (get_equation(Den{}) * ... * map_power(Dens{}))); + if constexpr (d == 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 +template [[nodiscard]] consteval auto explode(Q, type_list<>, type_list<>) { - return dimensionless; + return explode_result{dimensionless}; } -template +template [[nodiscard]] consteval auto explode(Q q) { - if constexpr (requires { Q::_equation_; }) - return explode( - q, type_list_sort{}, - type_list_sort{}); + constexpr auto c = get_complexity(q); + if constexpr (c > Complexity) + return explode(q, type_list_sort{}, + type_list_sort{}); else - return q; + return explode_result{q}; } -template +template [[nodiscard]] consteval auto explode(Q q) { - return explode(q, type_list_sort{}, - type_list_sort{}); + constexpr auto c = get_complexity(q); + if constexpr (c > 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 -[[nodiscard]] consteval convertible_to_result convertible_impl(Q1 q1, Q2 q2) +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list num_from, + type_list den_from, + type_list num_to, + type_list den_to); + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list, + type_list, + type_list); + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list, type_list<>, + type_list, + type_list); + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list, + type_list, type_list<>, + type_list); + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list, + type_list, + type_list, type_list<>); + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list, type_list<>, + type_list, type_list<>); + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list, + type_list<>, type_list); + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list, + type_list, type_list<>, + type_list<>); + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list<>, + type_list, type_list); + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list, type_list<>, + type_list<>, type_list<>); +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list, + type_list<>, type_list<>); + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list<>, + type_list, type_list<>); + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list<>, type_list<>, + type_list); + +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list<>, type_list<>, + type_list<>); + +enum class prepend_rest { no, first, second }; + +template +struct extract_results { + bool same_dimension; + From from{}; + To to{}; + prepend_rest prepend{}; + Elem elem{}; +}; + +template +[[nodiscard]] consteval auto extract_convertible_quantities(From from, To to) +{ + constexpr auto qfrom = map_power(from); + constexpr auto qto = map_power(to); + if constexpr (qfrom.dimension == qto.dimension) { + if constexpr (is_specialization_of_power && is_specialization_of_power) { + constexpr auto cr = common_ratio(From::exponent, To::exponent); + constexpr auto from_ratio = From::exponent / cr; + constexpr auto to_ratio = To::exponent / cr; + return extract_results{true, pow(typename From::factor{}), + pow(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 (from_factor.dimension != to_factor.dimension) + return extract_results{false}; + else if constexpr (from_exp > to_exp) + return extract_results{true, pow(from_factor), pow(to_factor), + prepend_rest::first, + power_or_T, from_exp - to_exp>{}}; + else + return extract_results{true, pow(from_factor), + pow(to_factor), prepend_rest::second, + power_or_T, to_exp - from_exp>{}}; + } +} + +enum class process_entities { numerators, denominators, from, to }; + +template +[[nodiscard]] consteval convertible_to_result process_extracted(NumFrom num_from, DenFrom den_from, NumTo num_to, + DenTo den_to) +{ + if constexpr (Entities == process_entities::numerators || Entities == process_entities::denominators) { + constexpr auto res = convertible_impl(Ext.from, Ext.to); + if constexpr (Ext.prepend == prepend_rest::no) + return std::min(res, are_ingredients_convertible(num_from, den_from, num_to, den_to)); + else { + using elem = std::remove_cvref_t; + if constexpr (Entities == process_entities::numerators) { + if constexpr (Ext.prepend == prepend_rest::first) + return std::min(res, + are_ingredients_convertible(type_list_push_front{}, den_from, num_to, den_to)); + else + return std::min(res, + are_ingredients_convertible(num_from, den_from, type_list_push_front{}, den_to)); + } else { + if constexpr (Ext.prepend == prepend_rest::first) + return std::min(res, + are_ingredients_convertible(num_from, type_list_push_front{}, num_to, den_to)); + else + return std::min(res, + are_ingredients_convertible(num_from, den_from, num_to, type_list_push_front{})); + } + } + } else { + if constexpr (Ext.prepend == prepend_rest::no) + return are_ingredients_convertible(num_from, den_from, num_to, den_to); + else { + using elem = std::remove_cvref_t; + if constexpr (Entities == process_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 (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{}); + } + } + } +} + +template +[[nodiscard]] consteval convertible_to_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_dimension) + return process_extracted(type_list{}, den_from, + type_list{}, den_to); + else if constexpr (constexpr auto extD = extract_convertible_quantities(DenFrom{}, DenTo{}); extD.same_dimension) + return process_extracted(num_from, type_list{}, num_to, + type_list{}); + else if constexpr (constexpr auto extF = extract_convertible_quantities(NumFrom{}, DenFrom{}); extF.same_dimension) + return process_extracted(type_list{}, type_list{}, num_to, + den_to); + else if constexpr (constexpr auto extT = extract_convertible_quantities(NumTo{}, DenTo{}); extT.same_dimension) + return process_extracted(num_from, den_from, 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 = std::max({num_from_compl, num_to_compl, den_from_compl, den_to_compl}); + if constexpr (max > 1) { + if constexpr (num_from_compl == max) { + constexpr auto res = explode_to_equation(NumFrom{}); + return convertible_impl( + (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) { + constexpr auto res = explode_to_equation(DenFrom{}); + return convertible_impl( + (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) { + constexpr auto res = explode_to_equation(NumTo{}); + return std::min(res.convertible_result, convertible_impl((map_power(NumFrom{}) * ... * map_power(NumsFrom{})) / + (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 std::min(res.convertible_result, convertible_impl((map_power(NumFrom{}) * ... * map_power(NumsFrom{})) / + (map_power(DenFrom{}) * ... * map_power(DensFrom{})), + (map_power(NumTo{}) * ... * map_power(NumsTo{})) / + (res.equation * ... * map_power(DensTo{})))); + } + } + } + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<> num_from, + type_list den_from, + type_list num_to, + type_list) +{ + if constexpr (constexpr auto extD = extract_convertible_quantities(DenFrom{}, DenTo{}); extD.same_dimension) + return process_extracted(num_from, type_list{}, num_to, + type_list{}); + else if constexpr (constexpr auto extT = extract_convertible_quantities(NumTo{}, DenTo{}); extT.same_dimension) + return process_extracted(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 = std::max({num_to_compl, den_from_compl, den_to_compl}); + if constexpr (max > 1) { + if constexpr (den_from_compl == max) { + constexpr auto res = explode_to_equation(DenFrom{}); + return convertible_impl( + 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) { + constexpr auto res = explode_to_equation(NumTo{}); + return std::min(res.convertible_result, + convertible_impl(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 std::min(res.convertible_result, + convertible_impl(dimensionless / (map_power(DenFrom{}) * ... * map_power(DensFrom{})), + (map_power(NumTo{}) * ... * map_power(NumsTo{})) / + (res.equation * ... * map_power(DensTo{})))); + } + } + } + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list num_from, + type_list<> den_from, + type_list, + type_list den_to) +{ + if constexpr (constexpr auto extN = extract_convertible_quantities(NumFrom{}, NumTo{}); extN.same_dimension) + return process_extracted(type_list{}, den_from, + type_list{}, den_to); + else if constexpr (constexpr auto extT = extract_convertible_quantities(NumTo{}, DenTo{}); extT.same_dimension) + return process_extracted(num_from, den_from, type_list{}, + type_list{}); + 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 = std::max({num_from_compl, num_to_compl, den_to_compl}); + if constexpr (max > 1) { + if constexpr (num_from_compl == max) { + constexpr auto res = explode_to_equation(NumFrom{}); + return convertible_impl( + (res.equation * ... * map_power(NumsFrom{})), + (map_power(NumTo{}) * ... * map_power(NumsTo{})) / (map_power(DenTo{}) * ... * map_power(DensTo{}))); + } else if constexpr (num_to_compl == max) { + constexpr auto res = explode_to_equation(NumTo{}); + return std::min(res.convertible_result, convertible_impl((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 std::min(res.convertible_result, convertible_impl((map_power(NumFrom{}) * ... * map_power(NumsFrom{})), + (map_power(NumTo{}) * ... * map_power(NumsTo{})) / + (res.equation * ... * map_power(DensTo{})))); + } + } + } + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_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_dimension) + return process_extracted(num_from, type_list{}, num_to, + type_list{}); + else if constexpr (constexpr auto extF = extract_convertible_quantities(NumFrom{}, DenFrom{}); extF.same_dimension) + return process_extracted(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 = std::max({num_from_compl, den_from_compl, den_to_compl}); + if constexpr (max > 1) { + if constexpr (num_from_compl == max) { + constexpr auto res = explode_to_equation(NumFrom{}); + return convertible_impl( + (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) { + constexpr auto res = explode_to_equation(DenFrom{}); + return convertible_impl( + (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 std::min(res.convertible_result, + convertible_impl((map_power(NumFrom{}) * ... * map_power(NumsFrom{})) / + (map_power(DenFrom{}) * ... * map_power(DensFrom{})), + dimensionless / (res.equation * ... * map_power(DensTo{})))); + } + } + } + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list, + type_list den_from, + type_list num_to, + type_list<> den_to) +{ + if constexpr (constexpr auto extN = extract_convertible_quantities(NumFrom{}, NumTo{}); extN.same_dimension) + return process_extracted(type_list{}, den_from, + type_list{}, den_to); + else if constexpr (constexpr auto extF = extract_convertible_quantities(NumFrom{}, DenFrom{}); extF.same_dimension) + return process_extracted(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 = std::max({num_from_compl, num_to_compl, den_from_compl}); + if constexpr (max > 1) { + if constexpr (num_from_compl == max) { + constexpr auto res = explode_to_equation(NumFrom{}); + return convertible_impl( + (res.equation * ... * map_power(NumsFrom{})) / (map_power(DenFrom{}) * ... * map_power(DensFrom{})), + (map_power(NumTo{}) * ... * map_power(NumsTo{}))); + } else if constexpr (den_from_compl == max) { + constexpr auto res = explode_to_equation(DenFrom{}); + return convertible_impl( + (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 std::min(res.convertible_result, convertible_impl((map_power(NumFrom{}) * ... * map_power(NumsFrom{})) / + (map_power(DenFrom{}) * ... * map_power(DensFrom{})), + (res.equation * ... * map_power(NumsTo{})))); + } + } + } + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list, + type_list<> den_from, + type_list, + type_list<> den_to) +{ + if constexpr (constexpr auto ext = extract_convertible_quantities(NumFrom{}, NumTo{}); ext.same_dimension) { + return process_extracted(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 = std::max({num_from_compl, num_to_compl}); + if constexpr (max > 1) { + if constexpr (num_from_compl == max) { + constexpr auto res = explode_to_equation(NumFrom{}); + return convertible_impl((res.equation * ... * map_power(NumsFrom{})), + (map_power(NumTo{}) * ... * map_power(NumsTo{}))); + } else { + constexpr auto res = explode_to_equation(NumTo{}); + return std::min(res.convertible_result, convertible_impl((map_power(NumFrom{}) * ... * map_power(NumsFrom{})), + (res.equation * ... * map_power(NumsTo{})))); + } + } + } + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_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_dimension) + return process_extracted(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 = std::max({den_from_compl, den_to_compl}); + if constexpr (max > 1) { + if constexpr (den_from_compl == max) { + constexpr auto res = explode_to_equation(DenFrom{}); + return convertible_impl(dimensionless / (res.equation * ... * map_power(DensFrom{})), + dimensionless / (map_power(DenTo{}) * ... * map_power(DensTo{}))); + } else { + constexpr auto res = explode_to_equation(DenTo{}); + return std::min(res.convertible_result, + convertible_impl(dimensionless / (map_power(DenFrom{}) * ... * map_power(DensFrom{})), + dimensionless / (res.equation * ... * map_power(DensTo{})))); + } + } + } + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list, + type_list, type_list<>, + type_list<>) +{ + if constexpr (((... * map_power(NumsFrom{})) / (... * map_power(DensFrom{}))).dimension == dimension_one) + return convertible_to_result::yes; + else + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list<>, + type_list, type_list) +{ + if constexpr (((... * map_power(NumsTo{})) / (... * map_power(DensTo{}))).dimension == dimension_one) + return convertible_to_result::yes; + else + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list, type_list<>, + type_list<>, type_list<>) +{ + if constexpr ((... * map_power(NumsFrom{})).dimension == dimension_one) + return convertible_to_result::yes; + else + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list, + type_list<>, type_list<>) +{ + if constexpr ((... * map_power(DensFrom{})).dimension == dimension_one) + return convertible_to_result::yes; + else + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list<>, + type_list, type_list<>) +{ + if constexpr ((... * map_power(NumsTo{})).dimension == dimension_one) + return convertible_to_result::yes; + else + return convertible_to_result::no; +} + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list<>, type_list<>, + type_list) +{ + if constexpr ((... * map_power(DensFrom{})).dimension == dimension_one) + return convertible_to_result::yes; + else + return convertible_to_result::no; +} + +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(type_list<>, type_list<>, type_list<>, + type_list<>) +{ + return convertible_to_result::yes; +} + +template +[[nodiscard]] consteval convertible_to_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 convertible_to_result are_ingredients_convertible(From, To) +{ + return are_ingredients_convertible(type_list_sort{}, + type_list_sort{}, + type_list{}, type_list<>{}); +} + +template +[[nodiscard]] consteval convertible_to_result are_ingredients_convertible(From, To) +{ + return are_ingredients_convertible(type_list{}, type_list<>{}, + type_list_sort{}, + type_list_sort{}); +} + +template +[[nodiscard]] consteval convertible_to_result convertible_impl(From from, To to) { using enum convertible_to_result; - if constexpr (Q1::dimension != Q2::dimension) + if constexpr (From::dimension != To::dimension) return no; - else if constexpr (q1 == q2) + else if constexpr (from == to) return yes; - else if constexpr (QuantityKindSpec || QuantityKindSpec) { - if constexpr (IntermediateDerivedQuantitySpec && - NamedQuantitySpec>) - return convertible_impl(get_kind(q1), remove_kind(q2)); + else if constexpr (QuantityKindSpec || QuantityKindSpec) { + constexpr auto from_kind = get_kind(from); + constexpr auto to_kind = get_kind(to); + if constexpr ((NamedQuantitySpec> && + NamedQuantitySpec>) || + get_complexity(from_kind) == get_complexity(to_kind)) + return convertible_impl(from_kind, to_kind); + else if constexpr (get_complexity(from_kind) > get_complexity(to_kind)) + return convertible_impl(get_kind(explode(from_kind).quantity), to_kind); else - return convertible_impl(get_kind(q1), get_kind(q2)) != no ? yes : no; - } else if constexpr (IntermediateDerivedQuantitySpec && IntermediateDerivedQuantitySpec) - return are_ingredients_convertible_to(q1, q2); - else if constexpr (NamedQuantitySpec && NamedQuantitySpec) { - if constexpr (have_common_base(q1, q2)) { - if (std::derived_from) + return convertible_impl(from_kind, get_kind(explode(to_kind).quantity)); + } else if constexpr (NamedQuantitySpec && NamedQuantitySpec) { + if constexpr (get_kind(from) != get_kind(to)) + return no; + else if constexpr (have_common_base(from, to)) { + if (std::derived_from) return yes; else - return std::derived_from ? explicit_conversion : cast; + return std::derived_from ? explicit_conversion : cast; + } else if constexpr (get_complexity(from) != get_complexity(to)) { + if constexpr (get_complexity(from) > get_complexity(to)) + return convertible_impl(explode(from).quantity, to); + else { + constexpr auto res = explode(to); + return std::min(res.convertible_result, convertible_impl(from, res.quantity)); + } } - } else if constexpr (IntermediateDerivedQuantitySpec) { - auto q1_exploded = explode(q1); - if constexpr (NamedQuantitySpec>) - return convertible_impl(q1_exploded, q2); - else if constexpr (requires { q2._equation_; }) - return convertible_impl(q1_exploded, q2._equation_); - } else if constexpr (IntermediateDerivedQuantitySpec) { - auto q2_exploded = explode(q2); - if constexpr (NamedQuantitySpec>) - return convertible_impl(q1, q2_exploded); - else if constexpr (requires { q1._equation_; }) - return std::min(explicit_conversion, convertible_impl(q1._equation_, q2_exploded)); + } else if constexpr (IntermediateDerivedQuantitySpec && IntermediateDerivedQuantitySpec) { + return are_ingredients_convertible(from, to); + } else if constexpr (IntermediateDerivedQuantitySpec) { + auto res = explode(from); + if constexpr (NamedQuantitySpec>) + return convertible_impl(res.quantity, to); + else if constexpr (requires { to._equation_; }) { + constexpr auto eq = explode_to_equation(to); + return std::min(eq.convertible_result, convertible_impl(res.quantity, eq.equation)); + } else + return are_ingredients_convertible(from, to); + } else if constexpr (IntermediateDerivedQuantitySpec) { + auto res = explode(to); + if constexpr (NamedQuantitySpec>) + return std::min(res.convertible_result, convertible_impl(from, res.quantity)); + else if constexpr (requires { from._equation_; }) + return std::min(res.convertible_result, convertible_impl(from._equation_, res.quantity)); + else + return std::min(res.convertible_result, are_ingredients_convertible(from, to)); } return no; } } // namespace detail -template -[[nodiscard]] consteval bool implicitly_convertible(Q1 q1, Q2 q2) +template +[[nodiscard]] consteval bool implicitly_convertible(From from, To to) { - return detail::convertible_impl(q1, q2) == detail::convertible_to_result::yes; + return detail::convertible_impl(from, to) == detail::convertible_to_result::yes; } -template -[[nodiscard]] consteval bool explicitly_convertible(Q1 q1, Q2 q2) +template +[[nodiscard]] consteval bool explicitly_convertible(From from, To to) { - return detail::convertible_impl(q1, q2) >= detail::convertible_to_result::explicit_conversion; + return detail::convertible_impl(from, to) >= detail::convertible_to_result::explicit_conversion; } -template -[[nodiscard]] consteval bool castable(Q1 q1, Q2 q2) +template +[[nodiscard]] consteval bool castable(From from, To to) { - return detail::convertible_impl(q1, q2) >= detail::convertible_to_result::cast; + return detail::convertible_impl(from, to) >= detail::convertible_to_result::cast; } namespace detail { @@ -785,10 +1363,24 @@ template [[nodiscard]] consteval QuantitySpec auto common_quantity_spec(Q1 q1, Q2 q2) requires(implicitly_convertible(get_kind(q1), get_kind(q2)) || implicitly_convertible(get_kind(q2), get_kind(q1))) { - if constexpr (q1 == q2) + using QQ1 = std::remove_const_t; + using QQ2 = std::remove_const_t; + if constexpr (is_same_v) + return q1; + else if constexpr ((QuantityKindSpec && !QuantityKindSpec) || + (detail::IntermediateDerivedQuantitySpec && detail::NamedQuantitySpec && + implicitly_convertible(q1, q2))) + return q2; + else if constexpr ((!QuantityKindSpec && QuantityKindSpec) || + (detail::NamedQuantitySpec && detail::IntermediateDerivedQuantitySpec && + 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)) + return q2; + else if constexpr (implicitly_convertible(q2, q1)) + return q1; else if constexpr (implicitly_convertible(get_kind(q1), get_kind(q2))) return get_kind(q2); else diff --git a/test/unit_test/static/quantity_spec_test.cpp b/test/unit_test/static/quantity_spec_test.cpp index b4d83440..d80c967d 100644 --- a/test/unit_test/static/quantity_spec_test.cpp +++ b/test/unit_test/static/quantity_spec_test.cpp @@ -47,20 +47,25 @@ inline constexpr struct second_ : named_unit<"s", kind_of