From b7628a17521e59b8be7d38b8796e61656a7ced3f Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Tue, 14 Feb 2023 12:28:34 +0100 Subject: [PATCH] feat: initial version of quantity_spec conversions implemented --- src/core/include/mp_units/quantity_spec.h | 982 ++++++++++++------- test/unit_test/static/quantity_spec_test.cpp | 642 +++++++++--- 2 files changed, 1090 insertions(+), 534 deletions(-) diff --git a/src/core/include/mp_units/quantity_spec.h b/src/core/include/mp_units/quantity_spec.h index fafa2b64..81c6a7d6 100644 --- a/src/core/include/mp_units/quantity_spec.h +++ b/src/core/include/mp_units/quantity_spec.h @@ -26,29 +26,15 @@ #include #include #include +#include #include +#include +#include +#include #include -#include namespace mp_units { -template -struct kind_of_ { - using type = Q; -}; - -template -consteval kind_of_> kind_of() -{ - return {}; -} - -template -consteval kind_of_ kind_of() -{ - return {}; -} - namespace detail { // TODO revise the note in the below comment @@ -60,7 +46,7 @@ namespace detail { template... Ts> [[nodiscard]] consteval quantity_character common_quantity_character(Ts... args) { - return detail::max({args...}); + return max({args...}); } template @@ -87,30 +73,6 @@ template return ch; } -template -inline constexpr bool is_dimensionless = false; - -template -inline constexpr bool is_power_of_quantity_spec = requires { - requires is_specialization_of_power && - (NamedQuantitySpec || is_dimensionless); -}; - -template -inline constexpr bool is_per_of_quantity_specs = false; - -template -inline constexpr bool is_per_of_quantity_specs> = - (... && (NamedQuantitySpec || is_dimensionless || is_power_of_quantity_spec)); - -} // namespace detail - -template -concept DerivedQuantitySpecExpr = NamedQuantitySpec || detail::is_dimensionless || - detail::is_power_of_quantity_spec || detail::is_per_of_quantity_specs; - -namespace detail { - template struct quantity_spec_less : std::bool_constant() < type_name()> {}; @@ -121,35 +83,291 @@ template requires requires { Q::dimension; } using to_dimension = std::remove_const_t; -template - requires requires { U::base_quantity.dimension; } -using to_base_dimension = std::remove_const_t; +template +[[nodiscard]] consteval auto get_associated_quantity(U); -template -[[nodiscard]] consteval Dimension auto get_dimension_for_impl(U) - requires requires { U::base_quantity.dimension; } -{ - return U::base_quantity.dimension; -} +#ifndef __cpp_explicit_this_parameter +template +#endif +struct quantity_spec_interface { +#ifdef __cpp_explicit_this_parameter + template + [[nodiscard]] consteval std::same_as> auto operator[](this const Self, U u) +#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) + { + return reference{}; + } -template -[[nodiscard]] consteval Dimension auto get_dimension_for_impl(const derived_unit& u) - requires detail::expr_projectable, to_base_dimension> -{ - return detail::expr_map(u); -} - -template -concept associated_unit = Unit && requires(U u) { get_dimension_for_impl(get_canonical_unit(u).reference_unit); }; - -[[nodiscard]] consteval Dimension auto get_dimension_for(associated_unit auto u) -{ - return get_dimension_for_impl(get_canonical_unit(u).reference_unit); -} +#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 + { + return std::forward(q).number() * reference::unit>{}; + } +}; } // namespace detail +/** + * @brief Quantity Specification + * + * This type specifies all the properties of a quantity and allow modeling most of the quantities in the ISO 80000. + * It serves to define base and derived quantities as well as quantity kinds. Each quantity specification + * provides an information on how this quantity relates to other quantities, specifies its dimension, and character. + * + * Quantity character can be derived from other quantities or explicitly overriden through a template parameter. + * + * Binding a proper unit to a quantity specification via an indexing operator (`operator[]`) results + * in a quantity reference. + * + * Call operator may be used to change the type of a provided quantity. + * + * Two quantity specifications are deemed equal when they are of the same type. With that, both strong + * types `speed` and `velocity` are considered not equal to `derived_dimension>` or + * to each other. + */ +#ifdef __cpp_explicit_this_parameter +template +#else +template +#endif +struct quantity_spec; + +/** + * @brief Specialization defining a base quantity + * + * Base quantity is a quantity in a conventionally chosen subset of a given system of quantities, where no quantity + * in the subset can be expressed in terms of the other quantities within that subset. They are referred to as + * being mutually independent since a base quantity cannot be expressed as a product of powers of the other base + * quantities. + * + * This quantity serves as a root/kind for a new hierarchy of quantities of the same kind. + * + * Base quantities have scalar character by default. + * + * User should derive a strong type from this class template rather than use it directly in the source code. + * For example: + * + * @code{.cpp} + * inline constexpr struct dim_length : base_dimension<"L"> {} dim_length; + * inline constexpr struct dim_mass : base_dimension<"M"> {} dim_mass; + * inline constexpr struct dim_time : base_dimension<"T"> {} dim_time; + * + * inline constexpr struct length : quantity_spec {} length; + * inline constexpr struct mass : quantity_spec {} mass; + * inline constexpr struct time : quantity_spec {} time; + * @endcode + * + * @note A common convention in this library is to assign the same name for a type and an object of this type. + * Besides defining them user never works with the types in the source code. All operations + * are done on the objects. Contrarily, the types are the only one visible in the compilation + * errors. Having them of the same names improves user experience and somehow blurs those separate domains. + * + * @tparam BaseDimension base dimension for which a base quantity is being defined + * @tparam Args optionally a value of a `quantity_character` in case the base quantity should not be scalar + */ +#ifdef __cpp_explicit_this_parameter +template auto... Args> +struct quantity_spec : detail::quantity_spec_interface { +#else +template auto... Args> +struct quantity_spec : detail::quantity_spec_interface { +#endif + static constexpr BaseDimension auto dimension = Dim; + static constexpr quantity_character character = detail::quantity_character_init(quantity_character::scalar); +}; + +/** + * @brief Specialization defining a named quantity being the result of a quantity calculus + * + * Derived quantity is a quantity, in a system of quantities, defined in terms of other quantities + * of that system. + * + * This quantity serves as a root/kind for a new hierarchy of quantities of the same kind. + * + * Such quantities by default derive the character from the derived quantity definition. + * + * User should derive a strong type from this class template rather than use it directly in the source code. + * For example: + * + * @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 force : quantity_spec {} force; + * inline constexpr struct power : quantity_spec {} power; + * @endcode + * + * @note A common convention in this library is to assign the same name for a type and an object of this type. + * Besides defining them user never works with the types in the source code. All operations + * are done on the objects. Contrarily, the types are the only one visible in the compilation + * errors. Having them of the same names improves user experience and somehow blurs those separate domains. + * + * @tparam Eq quantity equation specification of a derived quantity + * @tparam Args optionally a value of a `quantity_character` in case the base quantity should not be scalar + */ +#ifdef __cpp_explicit_this_parameter +template auto... Args> +struct quantity_spec : detail::quantity_spec_interface { +#else +template +struct quantity_spec : detail::quantity_spec_interface { +#endif + static constexpr auto _equation_ = Eq; + static constexpr Dimension auto dimension = Eq.dimension; + static constexpr quantity_character character = detail::quantity_character_init(Eq.character); +}; + +/** + * @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. + * + * The character of those quantities by default is derived from the parent quantity. + * + * User should derive a strong type from this class template rather than use it directly in the source code. + * For example: + * + * @code{.cpp} + * inline constexpr struct width : quantity_spec {} width; + * 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. + * Besides defining them user never works with the types in the source code. All operations + * are done on the objects. Contrarily, the types are the only one visible in the compilation + * errors. Having them of the same names improves user experience and somehow blurs those separate domains. + * + * @tparam Q quantity specification of a parent quantity + * @tparam Args optionally a value of a `quantity_character` in case the base quantity should not be scalar + */ +#ifdef __cpp_explicit_this_parameter +template auto... Args> +struct quantity_spec : std::remove_const_t { +#else +template +struct quantity_spec : std::remove_const_t { +#endif + static constexpr auto _parent_ = QS; + static constexpr quantity_character character = detail::quantity_character_init(QS.character); + +#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) + { + 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 + { + return std::forward(q).number() * reference::unit>{}; + } +#endif +}; + +/** + * @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 + * constraints on the derived quantity's equation. + * + * The character of those quantities by default is derived from the parent quantity. + * + * User should derive a strong type from this class template rather than use it directly in the source code. + * For example: + * + * @code{.cpp} + * inline constexpr struct velocity : quantity_spec {} velocity; + * inline constexpr struct weight : quantity_spec {} weight; + * inline constexpr struct kinetic_energy : quantity_spec(speed)> {} kinetic_energy; + * @endcode + * + * @note A common convention in this library is to assign the same name for a type and an object of this type. + * Besides defining them user never works with the types in the source code. All operations + * are done on the objects. Contrarily, the types are the only one visible in the compilation + * errors. Having them of the same names improves user experience and somehow blurs those separate domains. + * + * @tparam Q quantity specification of a parent quantity + * @tparam Args optionally a value of a `quantity_character` in case the base quantity should not be scalar + */ +#ifdef __cpp_explicit_this_parameter +template auto... Args> + requires requires { QS._equation_; } && (implicitly_convertible_to(Eq, QS._equation_)) +struct quantity_spec : quantity_spec { +#else +template + requires requires { QS._equation_; } && (implicitly_convertible_to(Eq, QS._equation_)) +struct quantity_spec : quantity_spec { +#endif + static constexpr auto _equation_ = Eq; + static constexpr quantity_character character = detail::quantity_character_init(Eq.character); +}; + +#ifdef __cpp_explicit_this_parameter + +#define QUANTITY_SPEC(name, ...) \ + inline constexpr struct name : ::mp_units::quantity_spec<##__VA_ARGS__> { \ + } name + +#else + +#define QUANTITY_SPEC(name, ...) \ + inline constexpr struct name : ::mp_units::quantity_spec { \ + } name + +#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 * @@ -164,6 +382,8 @@ concept associated_unit = Unit && requires(U u) { get_dimension_for_impl(get_ * 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. * + * The character of those quantities is derived from ingredients or overriden with a template parameter. + * * For example: * * @code{.cpp} @@ -185,233 +405,25 @@ concept associated_unit = Unit && requires(U u) { get_dimension_for_impl(get_ * - the type of `acceleration` is `derived_quantity_spec>` * - the dimension type of `acceleration` is `derived_dimension>>` * - * @note A common convention in this library is to assign the same name for a type and an object of this type. - * Besides defining them user never works with the dimension types in the source code. All operations - * are done on the objects. Contrarily, the dimension types are the only one visible in the compilation - * errors. Having them of the same names improves user experience and somehow blurs those separate domains. - * - * Derived quantity specification will have a character being the strongest ones from its ingredients. - * - * Binding a proper unit to a quantity specification via an indexing operator (`operator[]`) results - * in a quantity reference. - * - * @tparam Qs a parameter pack consisting tokens allowed in the quantity specification + * @tparam Expr a parameter pack consisting tokens allowed in the quantity specification * (named quantity specification, `dimensionless`, `power`, `per<...>`) * * @note User should not instantiate this type! It is not exported from the C++ module. The library will * instantiate this type automatically based on the dimensional arithmetic equation provided by the user. */ -template -struct derived_quantity_spec : detail::expr_fractions, Qs...> { - using base = detail::expr_fractions, Qs...>; +template +struct derived_quantity_spec : + detail::quantity_spec_interface>, + detail::expr_fractions { + using _base_ = detail::expr_fractions; static constexpr Dimension auto dimension = detail::expr_map(base{}); + detail::type_list_of_base_dimension_less>(_base_{}); static constexpr quantity_character character = - detail::derived_quantity_character(typename base::_num_{}, typename base::_num_{}); - -#ifdef __cpp_explicit_this_parameter - template - [[nodiscard]] consteval std::same_as> auto operator[](this const Self, U u) - requires(dimension == detail::get_dimension_for(u)) - { - return reference{}; - } -#else - // TODO can we somehow return an explicit reference type here? - template - [[nodiscard]] consteval std::same_as> auto operator[](U u) const - requires(dimension == detail::get_dimension_for(u)) - { - return reference{}; - } -#endif - - template Rep, Unit U> - [[nodiscard]] constexpr Quantity auto operator()(Rep&& v, U u) const - requires(dimension == detail::get_dimension_for(u)) - { - return derived_quantity_spec{}[u](std::forward(v)); - } + detail::derived_quantity_character(typename _base_::_num_{}, typename _base_::_num_{}); }; -namespace detail { - -template -inline constexpr bool is_specialization_of_derived_quantity_spec> = true; - -} - -/** - * @brief Quantity Specification - * - * This type specifies all the properties of a quantity and allow modeling most of the quantities in the ISO 80000. - * It serves to define base and derived quantities as well as quantity kinds. Each quantity specification - * provides an information on how this quantity relates to other quantities, specifies its dimension and character. - * - * Quantity character can be derived from other quantities or explicitly overriden through a template parameter. - * - * Binding a proper unit to a quantity specification via an indexing operator (`operator[]`) results - * in a quantity reference. - */ -#ifdef __cpp_explicit_this_parameter -template -#else -template -#endif -struct quantity_spec; - -/** - * @brief Specialization defining a base quantity - * - * Base quantity is a quantity in a conventionally chosen subset of a given system of quantities, where no quantity - * in the subset can be expressed in terms of the other quantities within that subset. They are referred to as - * being mutually independent since a base quantity cannot be expressed as a product of powers of the other base - * quantities. - * - * Base quantities have scalar character by default. - * - * User should derive a strong type from this class template rather than use it directly in the source code. - * For example: - * - * @code{.cpp} - * inline constexpr struct dim_length : base_dimension<"L"> {} dim_length; - * inline constexpr struct dim_mass : base_dimension<"M"> {} dim_mass; - * inline constexpr struct dim_time : base_dimension<"T"> {} dim_time; - * - * inline constexpr struct length : quantity_spec {} length; - * inline constexpr struct mass : quantity_spec {} mass; - * inline constexpr struct time : quantity_spec {} time; - * @endcode - * - * @note A common convention in this library is to assign the same name for a type and an object of this type. - * Besides defining them user never works with the dimension types in the source code. All operations - * are done on the objects. Contrarily, the dimension types are the only one visible in the compilation - * errors. Having them of the same names improves user experience and somehow blurs those separate domains. - * - * @tparam BaseDimension base dimension for which a base quantity is being defined - * @tparam Args optionally a value of a `quantity_character` in case the base quantity should not be scalar - */ -#ifdef __cpp_explicit_this_parameter -template auto... Args> -struct quantity_spec { -#else -template auto... Args> -struct quantity_spec { -#endif - static constexpr BaseDimension auto dimension = Dim; - static constexpr quantity_character character = detail::quantity_character_init(quantity_character::scalar); - -#ifdef __cpp_explicit_this_parameter - template - [[nodiscard]] consteval std::same_as> auto operator[](this const Self, U u) - requires(dimension == detail::get_dimension_for(u)) -#else - template - // TODO can we somehow return an explicit reference type here? - [[nodiscard]] consteval std::same_as> auto operator[](U u) const - requires(dimension == detail::get_dimension_for(u)) -#endif - { - return reference{}; - } - - template Rep, Unit U> - [[nodiscard]] constexpr Quantity auto operator()(Rep&& v, U u) const - requires(dimension == detail::get_dimension_for(u)) - { - return Self{}[u](std::forward(v)); - } -}; - -/** - * @brief Specialization defining a named derived quantity or a quantity kind - * - * The division of the concept "quantity" into several kinds is to some extent arbitrary. - * For example the quantities diameter, circumference, and wavelength are generally considered - * to be quantities of the same kind, namely, of the kind of quantity called length. - * - * Quantities of the same kind within a given system of quantities have the same quantity dimension. - * However, quantities of the same dimension are not necessarily of the same kind. - * - * The character of those quantities is derived from ingredients or overriden with a template parameter. - * - * User should derive a strong type from this class template rather than use it directly in the source code. - * For example: - * - * @code{.cpp} - * // quantity kinds - * inline constexpr struct width : quantity_spec {} width; - * inline constexpr struct height : quantity_spec {} height; - * inline constexpr struct thickness : quantity_spec {} thickness; - * inline constexpr struct diameter : quantity_spec {} diameter; - * inline constexpr struct position_vector : quantity_spec {} position_vector; - * - * // derived quantities - * 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 force : quantity_spec {} force; - * inline constexpr struct power : quantity_spec {} power; - * @endcode - * - * Two quantity specifications are deemed equal when they are of the same type. With that, both strong - * types `speed` and `velocity` are considered not equal to `derived_dimension>` or - * to each other. However, they are both convertible to `derived_dimension>`. - * User can implicitly convert up and down the inheritance hierarchy between them. However, `speed` and - * `velocity` are not convertible as they do not inherit from each other. They are from two separate branches - * of dimensionally equivalent quantities. - * - * @tparam Q quantity specification of a parent quantity - * @tparam Args optionally a value of a `quantity_character` in case the base quantity should not be scalar - */ -#ifdef __cpp_explicit_this_parameter -template auto... Args> -struct quantity_spec : std::remove_const_t { -#else -template auto... Args> -struct quantity_spec : std::remove_const_t { -#endif - static constexpr auto kind_of = Q; - static constexpr quantity_character character = detail::quantity_character_init(Q.character); - -#ifndef __cpp_explicit_this_parameter - template - // TODO can we somehow return an explicit reference type here? - [[nodiscard]] consteval std::same_as> auto operator[](U u) const - requires(this->dimension == detail::get_dimension_for(u)) - { - return reference{}; - } - - template Rep, Unit U> - [[nodiscard]] constexpr Quantity auto operator()(Rep&& v, U u) const - requires(this->dimension == detail::get_dimension_for(u)) - { - return Self{}[u](std::forward(v)); - } -#endif -}; - -#ifdef __cpp_explicit_this_parameter - -#define QUANTITY_SPEC(name, ...) \ - inline constexpr struct name : ::mp_units::quantity_spec<##__VA_ARGS__> { \ - } name - -#else - -#define QUANTITY_SPEC(name, ...) \ - inline constexpr struct name : ::mp_units::quantity_spec { \ - } name - -#endif - -// TODO Should a quantity_cast be allowed to cast between speed and velocity? - - /** * @brief Quantity of dimension one * @@ -423,86 +435,43 @@ QUANTITY_SPEC(dimensionless, derived_quantity_spec<>{}); namespace detail { template<> -inline constexpr bool is_dimensionless = true; +struct is_dimensionless : std::true_type {}; -} // namespace detail - -namespace detail { - -#ifdef __cpp_explicit_this_parameter -template -[[nodiscard]] consteval bool defines_kind(quantity_spec) -#else -template -[[nodiscard]] consteval bool defines_kind(quantity_spec) -#endif +template +[[nodiscard]] consteval QuantitySpec auto clone_kind_of(Q q) { - return contains...>(); -} - -#ifdef __cpp_explicit_this_parameter -template -[[nodiscard]] consteval QuantitySpec auto fetch_kind(quantity_spec) -#else -template -[[nodiscard]] consteval QuantitySpec auto fetch_kind(quantity_spec) -#endif -{ - return typename decltype(get...>())::type{}; -} - -template - requires requires(Q q) { get_kind(q); } -using to_kind = std::remove_const_t; - -} // namespace detail - -template -[[nodiscard]] consteval QuantitySpec auto get_kind(Q q) -{ - if constexpr (requires { Q::_parent_; }) { - // named non-base quantity - if constexpr (detail::defines_kind(q)) - return detail::fetch_kind(q); - else - return get_kind(Q::_parent_); - } else if constexpr (requires { - typename Q::_equation_; - }) { // TODO can we just check if it is derived from the derived_quantity_spec? - // derived quantity - return detail::expr_map(typename Q::_base_{}); - } else { - // base quantity + if constexpr ((... && QuantityKindSpec>)) + return kind_of; + else return q; - } } +} // namespace detail + // Operators -template -[[nodiscard]] consteval QuantitySpec auto operator*(Lhs lhs, Rhs rhs) +[[nodiscard]] consteval QuantitySpec auto operator*(QuantitySpec auto lhs, QuantitySpec auto rhs) { - return detail::expr_multiply( - lhs, rhs); + return clone_kind_of( + detail::expr_multiply( + remove_kind(lhs), remove_kind(rhs))); } template [[nodiscard]] consteval QuantitySpec auto operator/(Lhs lhs, Rhs rhs) { - return detail::expr_divide(lhs, - rhs); + return clone_kind_of( + detail::expr_divide( + remove_kind(lhs), remove_kind(rhs))); } -template -[[nodiscard]] consteval QuantitySpec auto operator/(int value, Q q) +[[nodiscard]] consteval QuantitySpec auto operator/(int value, QuantitySpec auto q) { gsl_Expects(value == 1); - return detail::expr_invert(q); + return clone_kind_of(detail::expr_invert(q)); } -template -[[nodiscard]] consteval QuantitySpec auto operator/(Q, int) = delete; +[[nodiscard]] consteval QuantitySpec auto operator/(QuantitySpec auto, int) = delete; template [[nodiscard]] consteval bool operator==(Lhs, Rhs) @@ -510,16 +479,302 @@ template return is_same_v; } -template -[[nodiscard]] consteval bool interconvertible(Q1, Q2) +template +[[nodiscard]] consteval bool operator==(Lhs, Rhs) { - if constexpr (NamedQuantitySpec && NamedQuantitySpec) - // check if quantity kinds are convertible (across one hierarchy) - return std::derived_from || std::derived_from; + return is_same_v; +} + +template +[[nodiscard]] consteval bool operator==(Lhs, Rhs rhs) +{ + return is_same_v>; +} + + +/** + * @brief Computes the value of a quantity specification raised to the `Num/Den` power + * + * @tparam Num Exponent numerator + * @tparam Den Exponent denominator + * @param q Quantity specification being the base of the operation + * + * @return QuantitySpec The result of computation + */ +template + requires detail::non_zero +[[nodiscard]] consteval QuantitySpec auto pow(Q q) +{ + if constexpr (q == dimensionless) + return q; + else if constexpr (detail::IntermediateDerivedQuantitySpec) + return detail::clone_kind_of( + detail::expr_pow( + remove_kind(q))); + else if constexpr (Den == 1) + return detail::clone_kind_of( + derived_quantity_spec, Num>>{}); + else + return detail::clone_kind_of( + derived_quantity_spec, Num, Den>>{}); +} + +namespace detail { + +enum class convertible_to_result { no, cast, explicit_conversion, yes }; + +template +[[nodiscard]] consteval ratio get_complexity(Q); + +template +[[nodiscard]] consteval ratio get_complexity(type_list) +{ + return (0 + ... + get_complexity(Ts{})); +} + +template +[[nodiscard]] consteval ratio get_complexity(power) +{ + return get_complexity(Q{}) * power::exponent; +} + +template +[[nodiscard]] consteval ratio get_complexity(Q) +{ + if constexpr (detail::IntermediateDerivedQuantitySpec) + return get_complexity(typename Q::_num_{}) + get_complexity(typename Q::_den_{}); + else if constexpr (requires { Q::_equation_; }) + return 1 + get_complexity(Q::_equation_); + else + return 1; +} + +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())> {}; + +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) +{ + // 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; +} + +template +[[nodiscard]] consteval auto get_equation(Q) +{ + return Q::_equation_; +} + +template +[[nodiscard]] consteval auto get_equation(power) +{ + return pow::exponent>(Q::_equation_); +} + +template +[[nodiscard]] consteval auto explode(Q q); + +template +[[nodiscard]] consteval auto explode(Q q); + +template +[[nodiscard]] consteval auto explode(Q 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; else { - // check weather the quantity spec's dimension is the same - // TODO Can we improve that to account for quantity kinds (i.e. `altitude` / `time` -> `sink_rate`) - return Q1::dimension == Q2::dimension; + if constexpr (n >= d) + return explode((get_equation(Num{}) * ... * map_power(Nums{})) / + (map_power(Den{}) * ... * map_power(Dens{}))); + else + return explode((map_power(Num{}) * ... * map_power(Nums{})) / + (get_equation(Den{}) * ... * map_power(Dens{}))); + } +} + +template +[[nodiscard]] consteval auto explode(Q 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{}))); +} + +template +[[nodiscard]] consteval auto explode(Q 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{}))); +} + +template +[[nodiscard]] consteval auto explode(Q, type_list<>, type_list<>) +{ + return dimensionless; +} + +template +[[nodiscard]] consteval auto explode(Q q) +{ + if constexpr (requires { Q::_equation_; }) + return explode( + q, type_list_sort{}, + type_list_sort{}); + else + return q; +} + +template +[[nodiscard]] consteval auto explode(Q q) +{ + return explode(q, type_list_sort{}, + type_list_sort{}); +} + +template +[[nodiscard]] consteval convertible_to_result convertible_to_impl(Q1 q1, Q2 q2) +{ + using enum convertible_to_result; + + if constexpr (Q1::dimension != Q2::dimension) + return no; + else if constexpr (q1 == q2) + return yes; + else if constexpr (QuantityKindSpec || QuantityKindSpec) { + if constexpr (IntermediateDerivedQuantitySpec && + NamedQuantitySpec>) + return convertible_to_impl(get_kind(q1), remove_kind(q2)); + else + return convertible_to_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 yes; + else + return std::derived_from ? explicit_conversion : cast; + } + } else if constexpr (IntermediateDerivedQuantitySpec) { + auto q1_exploded = explode(q1); + if constexpr (NamedQuantitySpec>) + return convertible_to_impl(q1_exploded, q2); + else if constexpr (requires { q2._equation_; }) + return convertible_to_impl(q1_exploded, q2._equation_); + } else if constexpr (IntermediateDerivedQuantitySpec) { + auto q2_exploded = explode(q2); + if constexpr (NamedQuantitySpec>) + return convertible_to_impl(q2_exploded, q1); + else if constexpr (requires { q1._equation_; }) + return std::min(explicit_conversion, convertible_to_impl(q1._equation_, q2_exploded)); + } + return no; +} + +} // namespace detail + +template +[[nodiscard]] consteval bool implicitly_convertible_to(Q1 q1, Q2 q2) +{ + return detail::convertible_to_impl(q1, q2) == detail::convertible_to_result::yes; +} + +template +[[nodiscard]] consteval bool explicitly_convertible_to(Q1 q1, Q2 q2) +{ + return detail::convertible_to_impl(q1, q2) >= detail::convertible_to_result::explicit_conversion; +} + +template +[[nodiscard]] consteval bool castable_to(Q1 q1, Q2 q2) +{ + return detail::convertible_to_impl(q1, q2) >= detail::convertible_to_result::cast; +} + +namespace detail { + +template + requires requires(Q q) { get_kind(q); } +using to_kind = std::remove_const_t; + +} + +template +[[nodiscard]] consteval auto remove_kind(Q q) +{ + if constexpr (QuantityKindSpec) { + if constexpr (requires { Q::_parent_; }) + return Q::_parent_; + else + return Q::_equation_; + } else + return q; +} + +template +[[nodiscard]] consteval QuantitySpec auto get_kind(Q q) +{ + if constexpr (QuantityKindSpec) { + return remove_kind(q); + } else if constexpr (requires { Q::_parent_; }) { + return get_kind(Q::_parent_); + } else if constexpr (detail::IntermediateDerivedQuantitySpec) { + return detail::expr_map(q); + } else { + // root quantity + return q; } } @@ -544,27 +799,4 @@ template return common_quantity_spec(common_quantity_spec(q1, q2), q3, rest...); } -/** - * @brief Computes the value of a quantity specification raised to the `Num/Den` power - * - * @tparam Num Exponent numerator - * @tparam Den Exponent denominator - * @param q Quantity specification being the base of the operation - * - * @return QuantitySpec The result of computation - */ -template - requires detail::non_zero -[[nodiscard]] consteval QuantitySpec auto pow(Q q) -{ - if constexpr (BaseQuantitySpec) { - if constexpr (Den == 1) - return derived_quantity_spec>{}; - else - return derived_quantity_spec>{}; - } else - return detail::expr_pow(q); -} - } // namespace mp_units diff --git a/test/unit_test/static/quantity_spec_test.cpp b/test/unit_test/static/quantity_spec_test.cpp index b807fa0a..c76ff154 100644 --- a/test/unit_test/static/quantity_spec_test.cpp +++ b/test/unit_test/static/quantity_spec_test.cpp @@ -43,22 +43,26 @@ QUANTITY_SPEC_(length, dim_length); QUANTITY_SPEC_(mass, dim_mass); QUANTITY_SPEC_(time, dim_time); -inline constexpr struct second_ : named_unit<"s", time> {} second; +inline constexpr struct second_ : named_unit<"s", kind_of