From 4938e9d5c0051024688bebe9e12050fbc191b7d4 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 4 Nov 2022 19:29:05 +0100 Subject: [PATCH] feat: Added support for natural units-like systems + `dimension_one` cleanup --- example/v2_framework.cpp | 21 -- .../include/units/bits/expression_template.h | 42 +++- src/core/include/units/dimension.h | 62 +++--- src/core/include/units/quantity.h | 8 +- src/core/include/units/reference.h | 39 +--- src/core/include/units/unit.h | 13 +- test/unit_test/static/CMakeLists.txt | 6 +- test/unit_test/static/dimension_test.cpp | 85 ++++++-- test/unit_test/static/quantity_kind_test.cpp | 101 +++++---- test/unit_test/static/reference_test.cpp | 201 ++++++++++++++++++ test/unit_test/static/references_test.cpp | 93 -------- test/unit_test/static/unit_test.cpp | 71 ++++++- 12 files changed, 476 insertions(+), 266 deletions(-) create mode 100644 test/unit_test/static/reference_test.cpp delete mode 100644 test/unit_test/static/references_test.cpp diff --git a/example/v2_framework.cpp b/example/v2_framework.cpp index 04a88915..ffd221d0 100644 --- a/example/v2_framework.cpp +++ b/example/v2_framework.cpp @@ -44,27 +44,6 @@ static_assert(can_not_be_prefixed); static_assert(can_not_be_prefixed); static_assert(can_not_be_prefixed); -// Named quantity/dimension and unit -static_assert(is_same_v{}, int>>); - -// Named quantity/dimension and derived (unnamed) unit -static_assert( - is_same_v>>{}, int>>); - -// Derived (unnamed) quantity/dimension and derived (unnamed) unit -static_assert(is_same_v>, - derived_unit>>{}, - int>>); - -// Base quantity as a result of dimensional transformation -static_assert(is_same_v{}, int>>); - -// Dimensionless -static_assert(is_same_v{}, int>>); // Comparisons diff --git a/src/core/include/units/bits/expression_template.h b/src/core/include/units/bits/expression_template.h index 63264d69..359cb9c1 100644 --- a/src/core/include/units/bits/expression_template.h +++ b/src/core/include/units/bits/expression_template.h @@ -472,23 +472,47 @@ template typename To // expr_map +template typename Proj> +struct expr_type_map; -template typename Proj> -struct expr_type_map { +template typename Proj> + requires requires { typename Proj; } +struct expr_type_map { using type = Proj; }; -template typename Proj> +template typename Proj> + requires requires { typename Proj; } struct expr_type_map, Proj> { using type = power, Ints...>; }; -template typename Proj, template typename To, typename OneType, - template typename Pred, typename... Nums, typename... Dens> +template typename Proj> +concept expr_type_projectable = (requires { typename Proj; } || + (is_specialization_of_power && requires { typename Proj; })); + +template typename Proj> +inline constexpr bool expr_projectable_impl = false; + +template typename Proj> +inline constexpr bool expr_projectable_impl, Proj> = (... && expr_type_projectable); + +template typename Proj> +concept expr_projectable = requires { + typename T::_num_; + typename T::_den_; + requires type_list_size + type_list_size > 0; + requires expr_projectable_impl; + requires expr_projectable_impl; + }; + +template typename Proj, template typename To, typename OneType, + template typename Pred, expr_type_projectable... Nums, + expr_type_projectable... Dens> [[nodiscard]] consteval auto expr_map_impl(type_list, type_list) { - using nums = type_list_sort::type...>, Pred>; - using dens = type_list_sort::type...>, Pred>; + using nums = type_list_sort, Proj>::type...>, Pred>; + using dens = type_list_sort, Proj>::type...>, Pred>; return detail::get_optimized_expression(); } @@ -501,8 +525,8 @@ template typename Proj, template typename To, * @tparam Pred binary less then predicate * @tparam T expression template to map from */ -template typename Proj, template typename To, typename OneType, - template typename Pred, typename T> +template typename Proj, template typename To, typename OneType, + template typename Pred, expr_projectable T> [[nodiscard]] consteval auto expr_map(T) { return expr_map_impl(typename T::_num_{}, typename T::_den_{}); diff --git a/src/core/include/units/dimension.h b/src/core/include/units/dimension.h index c616adfa..0026cd72 100644 --- a/src/core/include/units/dimension.h +++ b/src/core/include/units/dimension.h @@ -53,13 +53,16 @@ concept DerivedDimension = detail::is_derived_dimension; template concept Dimension = BaseDimension || DerivedDimension; - namespace detail { -[[nodiscard]] consteval Dimension auto get_dimension_for(Unit auto); +template +inline constexpr bool is_valid_unit_for_dimension = false; } +template +concept valid_unit_for_dimension = Unit && Dimension && detail::is_valid_unit_for_dimension; + template struct reference; @@ -100,12 +103,10 @@ struct base_dimension { static constexpr auto symbol = Symbol; ///< Unique base dimension identifier #ifdef __cpp_explicit_this_parameter - template - requires(convertible(Self{}, detail::get_dimension_for(U{}))) + template U> [[nodiscard]] constexpr reference operator[](this const Self, U) #else - template - requires(convertible(Self{}, detail::get_dimension_for(U{}))) + template U> [[nodiscard]] constexpr reference operator[](U) const #endif { @@ -122,13 +123,13 @@ template using type_list_of_base_dimension_less = expr_less; template -inline constexpr bool is_dimensionless = false; +inline constexpr bool is_dimension_one = false; template inline constexpr bool is_power_of_dim = requires { requires is_specialization_of_power && - (BaseDimension || is_dimensionless); + (BaseDimension || is_dimension_one); }; template @@ -136,13 +137,13 @@ inline constexpr bool is_per_of_dims = false; template inline constexpr bool is_per_of_dims> = - (... && (BaseDimension || is_dimensionless || is_power_of_dim)); + (... && (BaseDimension || is_dimension_one || is_power_of_dim)); } // namespace detail template concept DerivedDimensionSpec = - BaseDimension || detail::is_dimensionless || detail::is_power_of_dim || detail::is_per_of_dims; + BaseDimension || detail::is_dimension_one || detail::is_power_of_dim || detail::is_per_of_dims; template struct derived_dimension; @@ -167,7 +168,7 @@ struct derived_dimension_impl : detail::expr_fractions, Ds.. * more digestable for the user. 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 just put directly in the list without any wrapper. There - * is also one special case. In case all of the exponents are negative than the `dimensionless` being a dimension of + * is also one special case. In case all of the exponents are negative than the `dimension_one` being a dimension of * a dimensionless quantity is put in the front to increase the readability. * * For example: @@ -182,7 +183,7 @@ struct derived_dimension_impl : detail::expr_fractions, Ds.. * DERIVED_DIMENSION(torque, moment_of_force); * @endcode * - * - `frequency` will be derived from type `derived_dimension>` + * - `frequency` will be derived from type `derived_dimension>` * - `speed` will be derived from type `derived_dimension>` * - `acceleration` will be derived from type `derived_dimension>>` * - `force` will be derived from type `derived_dimension>>` @@ -201,7 +202,7 @@ struct derived_dimension_impl : detail::expr_fractions, Ds.. * dimensionally equivalent quantities. * * @tparam Ds a parameter pack consisting tokens allowed in the dimension specification - * (base dimensions, `dimensionless`, `power`, `per<...>`) + * (base dimensions, `dimension_one`, `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. @@ -210,8 +211,8 @@ struct derived_dimension_impl : detail::expr_fractions, Ds.. template struct derived_dimension : detail::derived_dimension_impl { - template - requires(convertible(Self{}, detail::get_dimension_for(U{}))) + template + requires valid_unit_for_dimension || (sizeof...(Ds) == 0 && convertible(U{}, one)) [[nodiscard]] constexpr reference operator[](this const Self, U) { return {}; @@ -226,7 +227,7 @@ struct derived_dimension; template struct derived_dimension : detail::derived_dimension_impl { template - requires(convertible(derived_dimension{}, detail::get_dimension_for(U{}))) + requires valid_unit_for_dimension || (sizeof...(Ds) == 0 && convertible(U{}, one)) [[nodiscard]] constexpr reference operator[](U) const { return {}; @@ -236,7 +237,8 @@ struct derived_dimension : detail::derived_dimension_impl { template struct derived_dimension : D { template - requires(convertible(Self{}, detail::get_dimension_for(U{}))) + requires valid_unit_for_dimension || + (convertible(derived_dimension{}, derived_dimension<>{}) && convertible(U{}, one)) [[nodiscard]] constexpr reference operator[](U) const { return {}; @@ -266,13 +268,13 @@ inline constexpr bool is_derived_dimension = true; * Dimension for which all the exponents of the factors corresponding to the base * dimensions are zero. Also commonly named as "dimensionless". */ -inline constexpr struct dimensionless : derived_dimension<> { -} dimensionless; +inline constexpr struct dimension_one : derived_dimension<> { +} dimension_one; namespace detail { template<> -inline constexpr bool is_dimensionless = true; +inline constexpr bool is_dimension_one = true; template struct dim_type_impl { @@ -295,14 +297,14 @@ using dim_type = dim_type_impl::type; template [[nodiscard]] consteval Dimension auto operator*(Lhs, Rhs) { - return detail::expr_multiply( + return detail::expr_multiply( detail::dim_type{}, detail::dim_type{}); } template [[nodiscard]] consteval Dimension auto operator/(Lhs, Rhs) { - return detail::expr_divide( + return detail::expr_divide( detail::dim_type{}, detail::dim_type{}); } @@ -310,7 +312,7 @@ template [[nodiscard]] consteval Dimension auto operator/(int value, D) { gsl_Expects(value == 1); - return detail::expr_invert(detail::dim_type{}); + return detail::expr_invert(detail::dim_type{}); } template @@ -347,7 +349,7 @@ template else return derived_dimension>{}; } else - return detail::expr_pow(d); } @@ -366,16 +368,24 @@ using to_base_dimension = std::remove_const_t; 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); } -[[nodiscard]] consteval Dimension auto get_dimension_for(Unit auto 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); } +template + requires requires { detail::get_dimension_for(U); } && (convertible(Dim, detail::get_dimension_for(U))) +inline constexpr bool is_valid_unit_for_dimension = true; + } // namespace detail diff --git a/src/core/include/units/quantity.h b/src/core/include/units/quantity.h index 62eb4d6e..d6e9410d 100644 --- a/src/core/include/units/quantity.h +++ b/src/core/include/units/quantity.h @@ -48,7 +48,7 @@ inline constexpr auto make_quantity = [](Representation auto&& v) { }; template -concept quantity_one = Quantity && (T::dimension == dimensionless) && (T::unit == one); +concept quantity_one = Quantity && (T::dimension == dimension_one) && (T::unit == one); } // namespace detail @@ -419,7 +419,7 @@ public: requires(!Quantity) && (invoke_result_convertible_to_, const Value&, rep>) [[nodiscard]] friend constexpr Quantity auto operator/(const Value& v, const quantity& q) { - return detail::make_quantity(v / q.number()); + return detail::make_quantity(v / q.number()); } template @@ -463,7 +463,7 @@ template explicit(false) quantity(quantity) -> quantity; template -explicit(false) quantity(Rep)->quantity; +explicit(false) quantity(Rep)->quantity; template explicit quantity(Q) @@ -506,7 +506,7 @@ template template requires(!floating_point_) && (!floating_point_) && - (std::convertible_to || quantity_of) && + (std::convertible_to || quantity_of) && (quantity_value_for_, typename Q1::rep, typename Q2::rep>) [[nodiscard]] constexpr Quantity auto operator%(const Q1& lhs, const Q2& rhs) { diff --git a/src/core/include/units/reference.h b/src/core/include/units/reference.h index d88851ef..5e6debd5 100644 --- a/src/core/include/units/reference.h +++ b/src/core/include/units/reference.h @@ -28,22 +28,6 @@ namespace units { -template -struct system_reference; - -// namespace detail { - -// template -// void to_base_specialization_of_inline constexpr struct const volatile system_reference*> {} const volatile system_reference -// // inline constexpr bool // TODO: Replace with concept when it works with MSVC -// concept is_derived_from_specialization_of_system_reference = -// requires(T* t) { detail::to_base_specialization_of_system_reference(t); }; - -// } // namespace detail - /** * @brief The type for quantity references * @@ -83,15 +67,6 @@ struct system_reference; * The following syntaxes are not allowed: * `2 / s`, `km * 3`, `s / 4`, `70 * km / h`. */ -// template -// requires detail::is_derived_from_specialization_of_system_reference -// struct reference { -// using system_reference = R; -// static constexpr auto dimension = R::dimension; -// static constexpr U unit{}; -// // static constexpr UNITS_MSVC_WORKAROUND(Magnitude) auto mag = dimension::mag * unit::mag; -// }; - template struct reference { static constexpr D dimension{}; @@ -107,13 +82,6 @@ template return {}; } -// template -// requires requires { typename R::system_reference; } -// [[nodiscard]] consteval reference operator*(M, R) -// { -// return {}; -// } - template [[nodiscard]] consteval reference operator*(R1, R2) @@ -142,14 +110,9 @@ template return convertible(R1::dimension, R2::dimension) && convertible(R1::unit, R2::unit); } -// template -// [[nodiscard]] consteval bool castable(R1, R2) -// { -// return equivalent(R1::dimension, R2::dimension) && convertible(R1::unit, R2::unit); -// } - template + requires(!detail::associated_unit>) struct system_reference { static constexpr auto dimension = Dim; static constexpr auto coherent_unit = CoU; diff --git a/src/core/include/units/unit.h b/src/core/include/units/unit.h index 94592484..3466a86d 100644 --- a/src/core/include/units/unit.h +++ b/src/core/include/units/unit.h @@ -148,8 +148,10 @@ struct named_unit; /** * @brief Specialization for unit of a specified base dimension * - * This is the preferred way to define a measurement unit for a base dimension. For example `si::metre` - * is a unit to measure `isq::length` in the SI system. + * Associates a unit with a specified base dimension. + * For example `si::metre` is a unit to measure `isq::length` in the SI system. + * + * @note This is the preferred way to define a measurement unit for a specific base dimension. * * @note It does not have to (or sometimes even can't) be a proper system's base unit. For example * a base unit of mass in the SI is `si::kilogram` but here you are about to provide an `si::gram` @@ -170,9 +172,9 @@ struct named_unit { * @brief Specialization for a unit that can be reused by several base dimensions * * This specialization is used in rare cases where more than one base dimension in a specific - * system of units uses the same unit. For example in a hypothetical system of units where - * constant for speed of light `c = 1`, length and time could be measured in seconds. In such - * cases `system_reference` has to be used to explicitly express such a binding. + * system of units uses the same unit. For example in a hypothetical system of natural units + * where constant for speed of light `c = 1`, length and time could be measured in seconds. + * In such cases `system_reference` has to be used to explicitly express such a binding. * * @tparam Symbol a short text representation of the unit */ @@ -337,7 +339,6 @@ struct derived_unit : detail::expr_fractions, Us...> {}; inline constexpr struct one : derived_unit<> { } one; - namespace detail { template diff --git a/test/unit_test/static/CMakeLists.txt b/test/unit_test/static/CMakeLists.txt index 2dcd38bc..f32f4a5f 100644 --- a/test/unit_test/static/CMakeLists.txt +++ b/test/unit_test/static/CMakeLists.txt @@ -35,6 +35,7 @@ cmake_minimum_required(VERSION 3.2) add_library( unit_tests_static dimension_test.cpp + # angle_test.cpp # cgs_test.cpp # chrono_test.cpp @@ -48,11 +49,13 @@ add_library( # iec80000_test.cpp # kind_test.cpp magnitude_test.cpp + # math_test.cpp # point_origin_test.cpp # prime_test.cpp ratio_test.cpp - # references_test.cpp + reference_test.cpp + # si_test.cpp # si_cgs_test.cpp # si_fps_test.cpp @@ -60,6 +63,7 @@ add_library( # symbol_text_test.cpp type_list_test.cpp unit_test.cpp + # us_test.cpp ) diff --git a/test/unit_test/static/dimension_test.cpp b/test/unit_test/static/dimension_test.cpp index 12088ab5..1d1e0e73 100644 --- a/test/unit_test/static/dimension_test.cpp +++ b/test/unit_test/static/dimension_test.cpp @@ -22,18 +22,23 @@ #include "test_tools.h" #include +#include +#include +#include namespace { using namespace units; -using dimensionless_ = struct dimensionless; +using dimension_one_ = struct dimension_one; // clang-format off BASE_DIMENSION_(length, "L"); BASE_DIMENSION_(time, "T"); BASE_DIMENSION_(mass, "M"); +inline constexpr struct second_ : named_unit<"s", time> {} second; + DERIVED_DIMENSION_(frequency, decltype(1 / time)); DERIVED_DIMENSION_(action, decltype(1 / time)); DERIVED_DIMENSION_(area, decltype(length * length)); @@ -60,18 +65,18 @@ static_assert(DerivedDimension); static_assert(Dimension); static_assert(Dimension); -static_assert(DerivedDimension); -static_assert(DerivedDimension); // dimensionless +static_assert(DerivedDimension); +static_assert(DerivedDimension); // dimension_one static_assert(BaseDimension); // length // derived dimension expression template syntax verification -static_assert(is_of_type<1 / time, derived_dimension>>); +static_assert(is_of_type<1 / time, derived_dimension>>); static_assert(is_of_type<1 / (1 / time), time_>); -static_assert(is_of_type); -static_assert(is_of_type