From 32a65a74059ca2059d7b1d38782ce0226b1d488c Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Wed, 18 Sep 2019 06:36:52 -0600 Subject: [PATCH] Support for derived dimensions in exp added --- doc/DESIGN.md | 84 ++++++--- src/include/units/dimension.h | 159 +++++++++++------- src/include/units/dimensions/acceleration.h | 2 +- .../units/dimensions/base_dimensions.h | 14 +- src/include/units/dimensions/capacitance.h | 2 +- src/include/units/dimensions/energy.h | 2 +- src/include/units/dimensions/force.h | 5 +- src/include/units/dimensions/power.h | 2 +- src/include/units/dimensions/pressure.h | 3 +- src/include/units/dimensions/voltage.h | 2 +- src/include/units/unit.h | 16 +- test/unit_test/test_custom_units.cpp | 2 +- test/unit_test/test_dimension.cpp | 23 ++- test/unit_test/test_type_list.cpp | 4 +- 14 files changed, 207 insertions(+), 113 deletions(-) diff --git a/doc/DESIGN.md b/doc/DESIGN.md index 68b96297..8e3fb2d2 100644 --- a/doc/DESIGN.md +++ b/doc/DESIGN.md @@ -74,8 +74,8 @@ constexpr units::Velocity auto avg_speed(units::Length auto d, units::Time auto ### `Dimensions` -`units::dimension` is a type-list like type that stores an ordered list of exponents of one -or more base dimensions: +`units::dimension` represents a derived dimension and is implemented as a type-list like type that +stores an ordered list of exponents of one or more base dimensions: ```cpp template @@ -94,28 +94,6 @@ concept Dimension = #### `Exponents` -`units::exp` provides an information about a single base dimension and its (possibly fractional) -exponent in a derived dimension: - -```cpp -template -struct exp { - static constexpr const base_dimension& dimension = BaseDimension; - static constexpr int num = Num; - static constexpr int den = Den; -}; -``` - -where `BaseDimension` is a unique sortable compile-time value: - -```cpp -struct base_dimension { - const char* name; -}; -constexpr bool operator==(const base_dimension& lhs, const base_dimension& rhs); -constexpr bool operator<(const base_dimension& lhs, const base_dimension& rhs); -``` - `units::Exponent` concept is satisfied if provided type is an instantiation of `units::exp` class template: @@ -125,6 +103,45 @@ concept Exponent = detail::is_exp; // exposition only ``` +`units::exp` provides an information about a single dimension and its (possibly fractional) +exponent in a derived dimension. + +```cpp +template + requires BaseDimension || Dimension +struct exp { + using dimension = Dim; + static constexpr int num = Num; + static constexpr int den = Den; +}; +``` + +Both a base dimension and a derived dimension can be provided to `units::exp` class template. + +`units::BaseDimension` represents a base dimension and should be implemented as a type with +a unique compile-time text describing the dimension name: + +```cpp +template +concept BaseDimension = std::is_empty_v && + requires { + { T::value } -> std::same_as; + }; +``` + +For example here is a list of SI base dimensions: + +```cpp +struct base_dim_length { static constexpr const char* value = "length"; }; +struct base_dim_mass { static constexpr const char* value = "mass"; }; +struct base_dim_time { static constexpr const char* value = "time"; }; +struct base_dim_current { static constexpr const char* value = "current"; }; +struct base_dim_temperature { static constexpr const char* value = "temperature"; }; +struct base_dim_substance { static constexpr const char* value = "substance"; }; +struct base_dim_luminous_intensity { static constexpr const char* value = "luminous intensity"; }; +``` + + #### `make_dimension` Above design of dimensions is created with the ease of use for end users in mind. Compile-time @@ -169,6 +186,23 @@ contained base dimensions. Beside providing ordering to base dimensions it also - aggregate two arguments of the same base dimension but different exponents - eliminate two arguments of the same base dimension and with opposite equal exponents +`make_dimension_t` is also able to form a dimension type based not only on base dimensions but +it can take other derived dimensions as well. So for some more complex dimensions user can +type either: + +```cpp +struct pressure : make_dimension_t, exp, exp> {}; +``` + +or + +```cpp +struct pressure : make_dimension_t, exp> {}; +``` + +In the second case `make_dimension_t` will extract all derived dimensions into the list of +exponents of base dimensions. Thanks to that both cases will result with exactly the same base +class formed only from the exponents of base units. #### `merge_dimension` @@ -426,7 +460,7 @@ struct downcast_base { }; template -concept bool Downcastable = +concept Downcastable = requires { typename T::base_type; } && diff --git a/src/include/units/dimension.h b/src/include/units/dimension.h index d78f8177..246c353d 100644 --- a/src/include/units/dimension.h +++ b/src/include/units/dimension.h @@ -29,81 +29,49 @@ namespace std::experimental::units { - struct base_dimension { - const char* name; - }; + template + concept bool BaseDimension = std::is_empty_v && + requires { + { T::value } -> std::same_as; + }; - constexpr bool operator==(const base_dimension& lhs, const base_dimension& rhs) - { - const char* p1 = lhs.name; - const char* p2 = rhs.name; - for(; (*p1 != '\0') && (*p2 != '\0'); ++p1, (void)++p2) { - if(*p1 != *p2) return false; - } - return *p1 == *p2; - } + namespace detail { - constexpr bool operator<(const base_dimension& lhs, const base_dimension& rhs) - { - const char* p1 = lhs.name; - const char* p2 = rhs.name; - for(; (*p1 != '\0') && (*p2 != '\0'); ++p1, (void)++p2) { - if(*p1 < *p2) return true; - if(*p2 < *p1) return false; + template + constexpr bool less() + { + const char* p1 = D1::value; + const char* p2 = D2::value; + for(; (*p1 != '\0') && (*p2 != '\0'); ++p1, (void) ++p2) { + if(*p1 < *p2) return true; + if(*p2 < *p1) return false; + } + return (*p1 == '\0') && (*p2 != '\0'); } - return (*p1 == '\0') && (*p2 != '\0'); + } // base_dimension_less - template - struct base_dimension_less : std::bool_constant { - }; - - // exp - - template - struct exp { - static constexpr const base_dimension& dimension = BaseDimension; - static constexpr int num = Num; - static constexpr int den = Den; + template + struct base_dimension_less : std::bool_constant()> { }; // is_exp namespace detail { + template inline constexpr bool is_exp = false; - template - inline constexpr bool is_exp> = true; + // partial specialization for an exp type provided below + } // namespace detail template concept bool Exponent = detail::is_exp; - // exp_dim_id_less - - template - struct exp_less : base_dimension_less { - }; - - // exp_invert - - template - struct exp_invert; - - template - struct exp_invert> { - using type = exp; - }; - - template - using exp_invert_t = exp_invert::type; - - // dimension - template - struct dimension : downcast_base> {}; + struct dimension; // is_dimension namespace detail { @@ -121,6 +89,57 @@ namespace std::experimental::units { std::is_empty_v && detail::is_dimension>; + // exp + + template + requires BaseDimension || Dimension + struct exp { + using dimension = Dim; + static constexpr int num = Num; + static constexpr int den = Den; + }; + + // is_exp + namespace detail { + + template + inline constexpr bool is_exp> = true; + + } // namespace detail + + // exp_less + + template + struct exp_less : base_dimension_less { + }; + + // exp_invert + + template + struct exp_invert; + + template + struct exp_invert> { + using type = exp; + }; + + template + using exp_invert_t = exp_invert::type; + + // exp_multiply + + template + struct exp_multiply { + using type = exp; + }; + + template + using exp_multiply_t = exp_multiply::type; + + // dimension + + template + struct dimension : downcast_base> {}; // dim_invert @@ -161,7 +180,7 @@ namespace std::experimental::units { using type = conditional>, dimension, type_list_push_front>; }; - template + template struct dim_consolidate, exp, ERest...>> { // todo: provide custom implementation for ratio_add using r1 = std::ratio; @@ -171,11 +190,37 @@ namespace std::experimental::units { dim_consolidate_t, ERest...>>>; }; + template + struct extract; + + template + using extract_t = extract::type; + + template<> + struct extract<> { + using type = dimension<>; + }; + + template + struct extract, ERest...> { + using type = type_list_push_front, exp>; + }; + + template + struct extract, Num, Den>, ERest...> { + using type = type_list_push_front, exp_multiply_t...>; + }; + + template + struct extract, ERest...> { + using type = extract_t, Num, Den>, ERest...>; + }; + } // namespace detail template struct make_dimension { - using type = detail::dim_consolidate_t, exp_less>>; + using type = detail::dim_consolidate_t, exp_less>>; }; template diff --git a/src/include/units/dimensions/acceleration.h b/src/include/units/dimensions/acceleration.h index 5a3a88a0..dfb8efc5 100644 --- a/src/include/units/dimensions/acceleration.h +++ b/src/include/units/dimensions/acceleration.h @@ -26,7 +26,7 @@ namespace std::experimental::units { - struct acceleration : make_dimension_t, exp> {}; + struct acceleration : make_dimension_t, exp> {}; template<> struct downcasting_traits> : downcast_to {}; template diff --git a/src/include/units/dimensions/base_dimensions.h b/src/include/units/dimensions/base_dimensions.h index 6b6db449..732e88e0 100644 --- a/src/include/units/dimensions/base_dimensions.h +++ b/src/include/units/dimensions/base_dimensions.h @@ -26,12 +26,12 @@ namespace std::experimental::units { - inline constexpr base_dimension base_dim_length{"length"}; - inline constexpr base_dimension base_dim_mass{"mass"}; - inline constexpr base_dimension base_dim_time{"time"}; - inline constexpr base_dimension base_dim_current{"current"}; - inline constexpr base_dimension base_dim_temperature{"temperature"}; - inline constexpr base_dimension base_dim_substance{"substance"}; - inline constexpr base_dimension base_dim_luminous_intensity{"luminous intensity"}; + struct base_dim_length { static constexpr const char* value = "length"; }; + struct base_dim_mass { static constexpr const char* value = "mass"; }; + struct base_dim_time { static constexpr const char* value = "time"; }; + struct base_dim_current { static constexpr const char* value = "current"; }; + struct base_dim_temperature { static constexpr const char* value = "temperature"; }; + struct base_dim_substance { static constexpr const char* value = "substance"; }; + struct base_dim_luminous_intensity { static constexpr const char* value = "luminous intensity"; }; } // namespace std::experimental::units diff --git a/src/include/units/dimensions/capacitance.h b/src/include/units/dimensions/capacitance.h index d1fbfe4f..770cacb9 100644 --- a/src/include/units/dimensions/capacitance.h +++ b/src/include/units/dimensions/capacitance.h @@ -28,7 +28,7 @@ namespace std::experimental::units { - struct capacitance : make_dimension_t, exp, exp, exp> {}; + struct capacitance : make_dimension_t, exp> {}; template<> struct downcasting_traits> : downcast_to {}; template diff --git a/src/include/units/dimensions/energy.h b/src/include/units/dimensions/energy.h index 3cd1ff5d..c1acfca1 100644 --- a/src/include/units/dimensions/energy.h +++ b/src/include/units/dimensions/energy.h @@ -28,7 +28,7 @@ namespace std::experimental::units { - struct energy : make_dimension_t, exp, exp> {}; + struct energy : make_dimension_t, exp> {}; template<> struct downcasting_traits> : downcast_to {}; template diff --git a/src/include/units/dimensions/force.h b/src/include/units/dimensions/force.h index 7f0e24c7..f67ca285 100644 --- a/src/include/units/dimensions/force.h +++ b/src/include/units/dimensions/force.h @@ -24,12 +24,11 @@ #include #include -#include -#include +#include namespace std::experimental::units { - struct force : make_dimension_t, exp, exp> {}; + struct force : make_dimension_t, exp> {}; template<> struct downcasting_traits> : downcast_to {}; template diff --git a/src/include/units/dimensions/power.h b/src/include/units/dimensions/power.h index 63f39135..ca309ca1 100644 --- a/src/include/units/dimensions/power.h +++ b/src/include/units/dimensions/power.h @@ -27,7 +27,7 @@ namespace std::experimental::units { - struct power : make_dimension_t, exp, exp> {}; + struct power : make_dimension_t, exp> {}; template<> struct downcasting_traits> : downcast_to {}; template diff --git a/src/include/units/dimensions/pressure.h b/src/include/units/dimensions/pressure.h index d3ec83b5..8c630bc8 100644 --- a/src/include/units/dimensions/pressure.h +++ b/src/include/units/dimensions/pressure.h @@ -24,10 +24,11 @@ #include #include +#include namespace std::experimental::units { - struct pressure : make_dimension_t, exp, exp> {}; + struct pressure : make_dimension_t, exp> {}; template<> struct downcasting_traits> : downcast_to {}; template diff --git a/src/include/units/dimensions/voltage.h b/src/include/units/dimensions/voltage.h index 0959dfca..b4bece2c 100644 --- a/src/include/units/dimensions/voltage.h +++ b/src/include/units/dimensions/voltage.h @@ -30,7 +30,7 @@ namespace std::experimental::units { - struct voltage : make_dimension_t, exp, exp, exp> {}; + struct voltage : make_dimension_t, exp> {}; template<> struct downcasting_traits> : downcast_to {}; template diff --git a/src/include/units/unit.h b/src/include/units/unit.h index 15e69e61..bbc342c1 100644 --- a/src/include/units/unit.h +++ b/src/include/units/unit.h @@ -63,19 +63,19 @@ namespace std::experimental::units { template struct get_unit_base_dim> { static_assert(sizeof...(Rest) == 0, "Base unit expected"); - static constexpr const base_dimension& dimension = E::dimension; + using dimension = E::dimension; }; - template + template struct get_ratio { using ratio = ::std::experimental::units::ratio<1>; }; - template - struct get_ratio { - static constexpr const base_dimension& unit_base_dim = get_unit_base_dim::dimension; - using ratio = conditional<&unit_base_dim == &BaseDimension, typename U::ratio, - typename get_ratio::ratio>; + template + struct get_ratio { + using unit_base_dim = get_unit_base_dim::dimension; + using ratio = conditional, typename U::ratio, + typename get_ratio::ratio>; }; template @@ -105,7 +105,7 @@ namespace std::experimental::units { template struct derived_ratio, Us...> { using rest_ratio = derived_ratio, Us...>::ratio; - using e_ratio = get_ratio::ratio; + using e_ratio = get_ratio::ratio; using ratio = ratio_op::ratio; }; diff --git a/test/unit_test/test_custom_units.cpp b/test/unit_test/test_custom_units.cpp index b8349b89..6ffeab57 100644 --- a/test/unit_test/test_custom_units.cpp +++ b/test/unit_test/test_custom_units.cpp @@ -29,7 +29,7 @@ namespace { using namespace std::experimental; - inline constexpr units::base_dimension base_dim_digital_information{"digital information"}; + struct base_dim_digital_information { static constexpr const char* value = "digital information"; }; struct digital_information : units::make_dimension_t> {}; diff --git a/test/unit_test/test_dimension.cpp b/test/unit_test/test_dimension.cpp index 182c54b5..93d10efa 100644 --- a/test/unit_test/test_dimension.cpp +++ b/test/unit_test/test_dimension.cpp @@ -27,16 +27,31 @@ using namespace std::experimental::units; namespace { - inline constexpr base_dimension d0{"d0"}; - inline constexpr base_dimension d1{"d1"}; - inline constexpr base_dimension d2{"d2"}; - inline constexpr base_dimension d3{"d3"}; + struct d0 { static constexpr const char* value = "d0"; }; + struct d1 { static constexpr const char* value = "d1"; }; + struct d2 { static constexpr const char* value = "d2"; }; + struct d3 { static constexpr const char* value = "d3"; }; // exp_invert static_assert(std::is_same_v>, exp>); static_assert(std::is_same_v>, exp>); + // extract + + template + struct typeinfo; + + static_assert(std::is_same_v, dimension<>>); + static_assert(std::is_same_v>, dimension>>); + static_assert(std::is_same_v, exp>, dimension, exp>>); + using dim0 = dimension<>; + using dim1 = dimension>; + using dim2 = dimension, exp>; + static_assert(std::is_same_v, exp>, dimension>>); + static_assert(std::is_same_v, exp>, dimension, exp>>); + static_assert(std::is_same_v, exp, exp>, dimension, exp, exp, exp>>); + // make_dimension static_assert(std::is_same_v>, dimension>>); diff --git a/test/unit_test/test_type_list.cpp b/test/unit_test/test_type_list.cpp index ccf688a4..e3c8db55 100644 --- a/test/unit_test/test_type_list.cpp +++ b/test/unit_test/test_type_list.cpp @@ -82,8 +82,8 @@ namespace { std::is_same_v>::second_list, type_list>); // type_list_merge_sorted - inline constexpr base_dimension d0{"d0"}; - inline constexpr base_dimension d1{"d1"}; + struct d0 { static constexpr const char* value = "d0"; }; + struct d1 { static constexpr const char* value = "d1"; }; static_assert(std::is_same_v>, type_list>, exp_less>, type_list, exp>>);