// 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 { // is_quantity namespace detail { template inline constexpr bool is_quantity = false; // partial specialization below after the first quantity forward declaration } // namespace detail template concept Quantity = detail::is_quantity; template concept QuantityOf = Quantity && Dimension && same_dim; // Scalar template concept Scalar = (!Quantity) && Number; template class quantity; namespace detail { template inline constexpr bool is_quantity> = true; } // namespace detail // common_quantity namespace detail { template struct common_quantity_impl; template struct common_quantity_impl, quantity, Rep> { using type = quantity; }; template requires same_dim struct common_quantity_impl, quantity, Rep> { using type = quantity>>, Rep>; }; } // namespace detail template> using common_quantity = detail::common_quantity_impl::type; // treat_as_floating_point template // TODO Conceptify that inline constexpr bool treat_as_floating_point = std::is_floating_point_v; // quantity_cast namespace detail { template struct quantity_cast_impl { template static constexpr To cast(const Q& q) { if constexpr(treat_as_floating_point) { return To(static_cast(static_cast(q.count()) * (static_cast(CR::num) / static_cast(CR::den)))); } else { return To( static_cast(static_cast(q.count()) * static_cast(CR::num) / static_cast(CR::den))); } } }; template struct quantity_cast_impl { template static constexpr To cast(const Q& q) { return To(static_cast(q.count())); } }; template struct quantity_cast_impl { template static constexpr To cast(const Q& q) { if constexpr(treat_as_floating_point) { return To(static_cast(static_cast(q.count()) * (CRep{1} / static_cast(CR::den)))); } else { return To(static_cast(static_cast(q.count()) / static_cast(CR::den))); } } }; template struct quantity_cast_impl { template static constexpr To cast(const Q& q) { return To(static_cast(static_cast(q.count()) * static_cast(CR::num))); } }; } // namespace detail template [[nodiscard]] constexpr To quantity_cast(const quantity& q) requires same_dim { using c_ratio = ratio_divide; using c_rep = std::common_type_t; using cast = detail::quantity_cast_impl; return cast::cast(q); } template [[nodiscard]] constexpr quantity quantity_cast(const quantity& q) { return quantity_cast>(q); } template [[nodiscard]] constexpr quantity quantity_cast(const quantity& q) { return quantity_cast>(q); } template [[nodiscard]] constexpr quantity quantity_cast(const quantity& q) { return quantity_cast>(q); } // quantity_values template struct quantity_values { static constexpr Rep zero() noexcept { return Rep(0); } static constexpr Rep one() noexcept { return Rep(1); } static constexpr Rep max() noexcept { return std::numeric_limits::max(); } static constexpr Rep min() noexcept { return std::numeric_limits::lowest(); } }; // quantity template class quantity { Rep value_; public: using unit = U; using rep = Rep; using dimension = U::dimension; quantity() = default; quantity(const quantity&) = default; template requires std::convertible_to && (treat_as_floating_point || (!treat_as_floating_point)) constexpr explicit quantity(const Rep2& r): value_{static_cast(r)} { } template requires same_dim && std::convertible_to && (treat_as_floating_point || (std::ratio_divide::den == 1 && !treat_as_floating_point)) constexpr quantity(const Q2& q): value_{quantity_cast(q).count()} { } quantity& operator=(const quantity& other) = default; [[nodiscard]] constexpr rep count() const noexcept { return value_; } [[nodiscard]] static constexpr quantity zero() noexcept { return quantity(quantity_values::zero()); } [[nodiscard]] static constexpr quantity one() noexcept { return quantity(quantity_values::one()); } [[nodiscard]] static constexpr quantity min() noexcept { return quantity(quantity_values::min()); } [[nodiscard]] static constexpr quantity max() noexcept { return quantity(quantity_values::max()); } [[nodiscard]] constexpr quantity operator+() const { return quantity(*this); } [[nodiscard]] constexpr quantity operator-() const { return quantity(-count()); } constexpr quantity& operator++() { ++value_; return *this; } constexpr quantity operator++(int) { return quantity(value_++); } constexpr quantity& operator--() { --value_; return *this; } constexpr quantity operator--(int) { return quantity(value_--); } constexpr quantity& operator+=(const quantity& q) { value_ += q.count(); return *this; } constexpr quantity& operator-=(const quantity& q) { value_ -= q.count(); return *this; } constexpr quantity& operator*=(const rep& rhs) { value_ *= rhs; return *this; } constexpr quantity& operator/=(const rep& rhs) { value_ /= rhs; return *this; } template constexpr quantity& operator%=(const Rep2& rhs) requires (!treat_as_floating_point && !treat_as_floating_point) { value_ %= rhs; return *this; } constexpr quantity& operator%=(const quantity& q) requires (!treat_as_floating_point) { value_ %= q.count(); return *this; } template friend std::basic_ostream& operator<<(std::basic_ostream& os, const quantity& q) { os << q.count() << " "; if constexpr(!detail::is_unit) { // print user-defined unit os << unit::symbol::c_str(); } else { using ratio = quantity::unit::ratio; using dim = quantity::unit::dimension; if constexpr(!detail::is_dimension) { // print as a prefix or ratio of a coherent unit symbol defined by the user using coherent_unit = downcast_target>>; detail::print_prefix_or_ratio(os); os << coherent_unit::symbol::c_str(); } else { // print as a ratio of a coherent unit detail::print_ratio(os); // print coherent unit dimensions and their exponents detail::print_dimensions(os, dim{}); } } return os; } }; template [[nodiscard]] constexpr Quantity AUTO operator+(const quantity& lhs, const quantity& rhs) requires same_dim { using common_rep = decltype(lhs.count() + rhs.count()); using ret = common_quantity, quantity, common_rep>; return ret(ret(lhs).count() + ret(rhs).count()); } template [[nodiscard]] constexpr Quantity AUTO operator-(const quantity& lhs, const quantity& rhs) requires same_dim { using common_rep = decltype(lhs.count() - rhs.count()); using ret = common_quantity, quantity, common_rep>; return ret(ret(lhs).count() - ret(rhs).count()); } template [[nodiscard]] constexpr Quantity AUTO operator*(const quantity& q, const Rep2& v) requires (!Quantity) { using common_rep = decltype(q.count() * v); using ret = quantity; return ret(ret(q).count() * v); } template [[nodiscard]] constexpr Quantity AUTO operator*(const Rep1& v, const quantity& q) requires (!Quantity) { return q * v; } template [[nodiscard]] constexpr Scalar AUTO operator*(const quantity& lhs, const quantity& rhs) requires same_dim> { using common_rep = decltype(lhs.count() * rhs.count()); using ratio = ratio_multiply; return common_rep(lhs.count()) * common_rep(rhs.count()) * common_rep(ratio::num) / common_rep(ratio::den); } template [[nodiscard]] constexpr Quantity AUTO operator*(const quantity& lhs, const quantity& rhs) requires (!same_dim>) && (treat_as_floating_point || (std::ratio_multiply::den == 1)) { using dim = dimension_multiply; using common_rep = decltype(lhs.count() * rhs.count()); using ret = quantity>>, common_rep>; return ret(lhs.count() * rhs.count()); } template [[nodiscard]] constexpr Quantity AUTO operator/(const Rep1& v, const quantity& q) requires (!Quantity) { Expects(q != std::remove_cvref_t(0)); using dim = dim_invert; using common_rep = decltype(v / q.count()); using ret = quantity>>, common_rep>; using den = quantity; return ret(v / den(q).count()); } template [[nodiscard]] constexpr Quantity AUTO operator/(const quantity& q, const Rep2& v) requires (!Quantity) { Expects(v != Rep2{0}); using common_rep = decltype(q.count() / v); using ret = quantity; return ret(ret(q).count() / v); } template [[nodiscard]] constexpr Scalar AUTO operator/(const quantity& lhs, const quantity& rhs) requires same_dim { Expects(rhs != std::remove_cvref_t(0)); using common_rep = decltype(lhs.count() / rhs.count()); using cq = common_quantity, quantity, common_rep>; return cq(lhs).count() / cq(rhs).count(); } template [[nodiscard]] constexpr Quantity AUTO operator/(const quantity& lhs, const quantity& rhs) requires (!same_dim) && (treat_as_floating_point || (ratio_divide::den == 1)) { Expects(rhs != std::remove_cvref_t(0)); using common_rep = decltype(lhs.count() / rhs.count()); using dim = dimension_divide; using ret = quantity>>, common_rep>; return ret(lhs.count() / rhs.count()); } template [[nodiscard]] constexpr Quantity AUTO operator%(const quantity& q, const Rep2& v) { using common_rep = decltype(q.count() % v); using ret = quantity; return ret(ret(q).count() % v); } template [[nodiscard]] constexpr Quantity AUTO operator%(const quantity& lhs, const quantity& rhs) { using common_rep = decltype(lhs.count() % rhs.count()); using ret = common_quantity, quantity, common_rep>; return ret(ret(lhs).count() % ret(rhs).count()); } template [[nodiscard]] constexpr bool operator==(const quantity& lhs, const quantity& rhs) requires same_dim { using ct = common_quantity, quantity>; return ct(lhs).count() == ct(rhs).count(); } template [[nodiscard]] constexpr bool operator!=(const quantity& lhs, const quantity& rhs) requires same_dim { return !(lhs == rhs); } template [[nodiscard]] constexpr bool operator<(const quantity& lhs, const quantity& rhs) requires same_dim { using ct = common_quantity, quantity>; return ct(lhs).count() < ct(rhs).count(); } template [[nodiscard]] constexpr bool operator<=(const quantity& lhs, const quantity& rhs) requires same_dim { return !(rhs < lhs); } template [[nodiscard]] constexpr bool operator>(const quantity& lhs, const quantity& rhs) requires same_dim { return rhs < lhs; } template [[nodiscard]] constexpr bool operator>=(const quantity& lhs, const quantity& rhs) requires same_dim { return !(lhs < rhs); } } // namespace units