// The MIT License (MIT) // // Copyright (c) 2018 Mateusz Pusz // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #pragma once #include #include #include #include namespace units { namespace detail { template inline constexpr bool is_derived_dimension = false; } /** * @brief A concept matching all derived dimensions in the library. * * Satisfied by all dimension types either being a specialization of `derived_dimension` * or derived from it. */ template concept DerivedDimension = detail::is_derived_dimension; /** * @brief A concept matching all dimensions in the library. * * Satisfied by all dimension types for which either `BaseDimension` or `DerivedDimension` is `true`. */ template concept Dimension = BaseDimension || DerivedDimension; namespace detail { 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; /** * @brief A dimension of 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. * * Symbol template parameters is an unique identifier of the base dimension. The same identifiers can be multiplied * and divided which will result with an adjustment of its factor in an exponent of a derived_dimension * (in case of zero the dimension will be simplified and removed from further analysis of current expresion). * * 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 length : base_dimension<"L"> {} length; * inline constexpr struct time : base_dimension<"T"> {} time; * inline constexpr struct mass : base_dimension<"M"> {} mass; * @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 Symbol an unique identifier of the base dimension used to provide dimensional analysis support */ #ifdef __cpp_explicit_this_parameter template #else template #endif struct base_dimension { static constexpr auto symbol = Symbol; ///< Unique base dimension identifier #ifdef __cpp_explicit_this_parameter template U> [[nodiscard]] constexpr reference operator[](this const Self, U) #else template U> [[nodiscard]] constexpr reference operator[](U) const #endif { return {}; } }; namespace detail { template struct base_dimension_less : std::bool_constant<(Lhs::symbol < Rhs::symbol)> {}; template using type_list_of_base_dimension_less = expr_less; template inline constexpr bool is_dimension_one = false; template inline constexpr bool is_power_of_dim = requires { requires is_specialization_of_power && (BaseDimension || is_dimension_one); }; template inline constexpr bool is_per_of_dims = false; template inline constexpr bool is_per_of_dims> = (... && (BaseDimension || is_dimension_one || is_power_of_dim)); } // namespace detail template concept DerivedDimensionSpec = BaseDimension || detail::is_dimension_one || detail::is_power_of_dim || detail::is_per_of_dims; template struct derived_dimension; namespace detail { template struct derived_dimension_impl : detail::expr_fractions, Ds...> { using _type_ = derived_dimension; // exposition only }; } // namespace detail /** * @brief A dimension of a derived quantity * * Derived dimension is an expression of the dependence of a quantity on the base quantities of a system of quantities * as a product of powers of factors corresponding to the base quantities, omitting any numerical factors. * * Instead of using a raw list of exponents this library decided to use expression template syntax to make types * 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 `dimension_one` being a dimension of * a dimensionless quantity is put in the front to increase the readability. * * For example: * * @code{.cpp} * inline constexpr struct frequency : decltype(1 / time) {} frequency; * inline constexpr struct speed : decltype(length / time) {} speed; * inline constexpr struct acceleration : decltype(speed / time) {} acceleration; * inline constexpr struct force : decltype(mass * acceleration) {} force; * inline constexpr struct energy : decltype(force * length) {} energy; * inline constexpr struct moment_of_force : decltype(length * force) {} moment_of_force; * inline constexpr struct torque : decltype(moment_of_force) {} torque; * @endcode * * - `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>>` * - `energy` will be derived from type `derived_dimension, mass, per>>` * * @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. * * Two dimensions are deemed equal when they are of the same type. With that strong type `speed` and * `derived_dimension>` are considered not equal. They are convertible though. * User can implicitly convert up and down the inheritance hierarchy between those two. * `torque` and `moment_of_force` are convertible as well. However, `energy` and `torque` * are not convertible as they do not inherit from each other. They are from two separate branches of * dimensionally equivalent quantities. * * @tparam Ds a parameter pack consisting tokens allowed in the dimension specification * (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. */ #ifdef __cpp_explicit_this_parameter template struct derived_dimension : detail::derived_dimension_impl { template requires valid_unit_for_dimension || (sizeof...(Ds) == 0 && convertible(U{}, one)) [[nodiscard]] constexpr reference operator[](this const Self, U) { return {}; } }; #else template struct derived_dimension; template struct derived_dimension : detail::derived_dimension_impl { template requires valid_unit_for_dimension || (sizeof...(Ds) == 0 && convertible(U{}, one)) [[nodiscard]] constexpr reference operator[](U) const { return {}; } }; template struct derived_dimension : D { template requires valid_unit_for_dimension || (convertible(derived_dimension{}, derived_dimension<>{}) && convertible(U{}, one)) [[nodiscard]] constexpr reference operator[](U) const { return {}; } }; #endif namespace detail { template void to_base_specialization_of_derived_dimension(const volatile derived_dimension*); template inline constexpr bool is_derived_from_specialization_of_derived_dimension = requires(T * t) { to_base_specialization_of_derived_dimension(t); }; template requires is_derived_from_specialization_of_derived_dimension inline constexpr bool is_derived_dimension = true; } // namespace detail /** * @brief Dimension one * * Dimension for which all the exponents of the factors corresponding to the base * dimensions are zero. Also commonly named as "dimensionless". */ inline constexpr struct dimension_one : derived_dimension<> { } dimension_one; namespace detail { template<> inline constexpr bool is_dimension_one = true; template struct dim_type_impl { using type = T; }; template struct dim_type_impl { using type = TYPENAME T::_type_; }; template using dim_type = TYPENAME dim_type_impl::type; } // namespace detail // Operators template [[nodiscard]] consteval Dimension auto operator*(Lhs, Rhs) { return detail::expr_multiply( detail::dim_type{}, detail::dim_type{}); } template [[nodiscard]] consteval Dimension auto operator/(Lhs, Rhs) { return detail::expr_divide( detail::dim_type{}, detail::dim_type{}); } template [[nodiscard]] consteval Dimension auto operator/(int value, D) { gsl_Expects(value == 1); return detail::expr_invert(detail::dim_type{}); } template [[nodiscard]] consteval Dimension auto operator/(D, int) = delete; template [[nodiscard]] consteval bool operator==(Lhs, Rhs) { return is_same_v; } template [[nodiscard]] consteval bool convertible(D1, D2) { return std::derived_from || std::derived_from; } /** * @brief Computes the value of a dimension raised to the `Num/Den` power * * @tparam Num Exponent numerator * @tparam Den Exponent denominator * @param d Dimension being the base of the operation * * @return Dimension The result of computation */ template requires detail::non_zero [[nodiscard]] consteval Dimension auto pow(D d) { if constexpr (BaseDimension) { if constexpr (Den == 1) return derived_dimension>{}; else return derived_dimension>{}; } else return detail::expr_pow(d); } namespace detail { template [[nodiscard]] consteval Dimension auto get_dimension_for_impl(U) requires requires { U::base_dimension; } { return U::base_dimension; } template requires requires { U::base_dimension; } 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); } 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 // TODO consider adding the support for text output of the dimensional equation } // namespace units namespace std { /** * @brief Partial specialization of `std::common_type` for dimensions * * Defined only for convertible types and returns the most derived/specific dimension type of * the two provided. */ template requires(units::convertible(D1{}, D2{})) struct common_type { using type = ::units::conditional, std::remove_const_t>, std::remove_const_t, std::remove_const_t>; }; } // namespace std #ifdef __cpp_explicit_this_parameter #define BASE_DIMENSION(name, symbol) \ inline constexpr struct name : base_dimension { \ } name #define DERIVED_DIMENSION(name, base) \ inline constexpr struct name : base { \ } name #else #define BASE_DIMENSION(name, symbol) \ inline constexpr struct name : base_dimension { \ } name #define DERIVED_DIMENSION(name, base) \ inline constexpr struct name : derived_dimension { \ } name #endif