// 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 namespace units { /** * @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_dim : base_dimension<"L"> {} length_dim; * inline constexpr struct time_dim : base_dimension<"T"> {} time_dim; * inline constexpr struct mass_dim : base_dimension<"M"> {} mass_dim; * @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 */ template struct base_dimension { static constexpr auto symbol = Symbol; ///< Unique base dimension identifier }; namespace detail { template void to_base_base_dimension(const volatile base_dimension*); template inline constexpr bool is_specialization_of_base_dimension = false; template inline constexpr bool is_specialization_of_base_dimension> = true; } // namespace detail /** * @brief A concept matching all named base dimensions in the library. * * Satisfied by all dimension types derived from a specialization of `base_dimension`. */ template concept BaseDimension = requires(T* t) { detail::to_base_base_dimension(t); } && (!detail::is_specialization_of_base_dimension); 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 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_one_dim = false; template inline constexpr bool is_power_of_dim = requires { requires is_specialization_of_power && (BaseDimension || is_one_dim); }; template inline constexpr bool is_per_of_dims = false; template inline constexpr bool is_per_of_dims> = (... && (BaseDimension || is_one_dim || is_power_of_dim)); } // namespace detail template concept DerivedDimensionSpec = BaseDimension || detail::is_one_dim || 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 `one_dim` being a dimension of * a dimensionless quantity is put in the front to increase the readability. * * For example: * * @code{.cpp} * inline constexpr struct frequency_dim : decltype(1 / time_dim) {} frequency_dim; * inline constexpr struct speed_dim : decltype(length_dim / time_dim) {} speed_dim; * inline constexpr struct acceleration_dim : decltype(speed_dim / time_dim) {} acceleration_dim; * inline constexpr struct force_dim : decltype(mass_dim * acceleration_dim) {} force_dim; * inline constexpr struct energy_dim : decltype(force_dim * length_dim) {} energy_dim; * inline constexpr struct moment_of_force_dim : decltype(length_dim * force_dim) {} moment_of_force_dim; * inline constexpr struct torque_dim : decltype(moment_of_force_dim) {} torque_dim; * @endcode * * - `frequency_dim` will be derived from type `derived_dimension>` * - `speed_dim` will be derived from type `derived_dimension>` * - `acceleration_dim` will be derived from type `derived_dimension>>` * - `force_dim` will be derived from type `derived_dimension>>` * - `energy_dim` will be derived from type `derived_dimension, mass_dim, 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_dim` 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_dim` and `moment_of_force_dim` are convertible as well. However, `energy_dim` and `torque_dim` * 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, `one_dim`, `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_dimension : detail::derived_dimension_impl {}; 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 one_dim : derived_dimension<> { } one_dim; namespace detail { template<> inline constexpr bool is_one_dim = true; template struct dim_type_impl { using type = T; }; template struct dim_type_impl { using type = T::_type_; }; template using dim_type = 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; } // 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