refactor: magnitude interface cleanup

This commit is contained in:
Mateusz Pusz
2024-10-04 17:22:15 +02:00
parent f07e518ff0
commit 07e4e795c1
9 changed files with 149 additions and 101 deletions

View File

@ -56,6 +56,7 @@ add_mp_units_module(
include/mp-units/framework/dimension_concepts.h
include/mp-units/framework/expression_template.h
include/mp-units/framework/magnitude.h
include/mp-units/framework/magnitude_concepts.h
include/mp-units/framework/quantity.h
include/mp-units/framework/quantity_cast.h
include/mp-units/framework/quantity_concepts.h

View File

@ -79,9 +79,9 @@ struct conversion_value_traits {
static constexpr Magnitude auto num = _numerator(M);
static constexpr Magnitude auto den = _denominator(M);
static constexpr Magnitude auto irr = M * (den / num);
static constexpr T num_mult = get_value<T>(num);
static constexpr T den_mult = get_value<T>(den);
static constexpr T irr_mult = get_value<T>(irr);
static constexpr T num_mult = _get_value<T>(num);
static constexpr T den_mult = _get_value<T>(den);
static constexpr T irr_mult = _get_value<T>(irr);
static constexpr T ratio = num_mult / den_mult * irr_mult;
};
@ -120,10 +120,10 @@ template<Quantity To, typename FwdFrom, Quantity From = std::remove_cvref_t<FwdF
};
// scale the number
if constexpr (is_integral(c_mag))
return scale([&](auto value) { return value * get_value<multiplier_type>(_numerator(c_mag)); });
else if constexpr (is_integral(pow<-1>(c_mag)))
return scale([&](auto value) { return value / get_value<multiplier_type>(_denominator(c_mag)); });
if constexpr (_is_integral(c_mag))
return scale([&](auto value) { return value * _get_value<multiplier_type>(_numerator(c_mag)); });
else if constexpr (_is_integral(_pow<-1>(c_mag)))
return scale([&](auto value) { return value / _get_value<multiplier_type>(_denominator(c_mag)); });
else {
using value_traits = conversion_value_traits<c_mag, multiplier_type>;
if constexpr (std::is_floating_point_v<multiplier_type>)

View File

@ -30,6 +30,7 @@
#include <mp-units/framework/dimension_concepts.h>
#include <mp-units/framework/expression_template.h>
#include <mp-units/framework/magnitude.h>
#include <mp-units/framework/magnitude_concepts.h>
#include <mp-units/framework/quantity.h>
#include <mp-units/framework/quantity_cast.h>
#include <mp-units/framework/quantity_concepts.h>

View File

@ -32,6 +32,7 @@
#include <mp-units/ext/type_traits.h>
#include <mp-units/framework/customization_points.h>
#include <mp-units/framework/expression_template.h>
#include <mp-units/framework/magnitude_concepts.h>
#include <mp-units/framework/symbol_text.h>
#include <mp-units/framework/unit_symbol_formatting.h>
@ -61,68 +62,37 @@ using factorizer = wheel_factorizer<4>;
MP_UNITS_EXPORT template<symbol_text Symbol>
struct mag_constant {
static constexpr auto symbol = Symbol;
static constexpr auto _symbol_ = Symbol;
};
#else
MP_UNITS_EXPORT template<symbol_text Symbol, auto Value>
MP_UNITS_EXPORT template<symbol_text Symbol, long double Value>
requires(Value > 0)
struct mag_constant {
static constexpr auto symbol = Symbol;
static constexpr auto value = Value;
static constexpr auto _symbol_ = Symbol;
static constexpr long double _value_ = Value;
};
#endif
MP_UNITS_EXPORT template<typename T>
concept MagConstant =
is_derived_from_specialization_of_v<T, mag_constant> && std::is_empty_v<T> && std::is_final_v<T> && requires {
{ +T::value } -> std::same_as<long double>;
};
namespace detail {
template<typename T>
concept MagArg = std::integral<T> || MagConstant<T>;
/**
* @brief Any type which can be used as a basis vector in a PowerV.
*
* We have two categories.
*
* The first is just an integral type (either `int` or `std::intmax_t`). This is for prime number bases.
* These can always be used directly as NTTPs.
*
* The second category is a _custom tag type_, which inherits from `mag_constant` and has a static member variable
* `value` of type `long double` that holds its value. We choose `long double` to get the greatest degree of precision;
* users who need a different type can convert from this at compile time. This category is for any irrational base
* we admit into our representation (on which, more details below).
*/
template<typename T>
concept PowerVBase = one_of<T, int, std::intmax_t> || MagConstant<T>;
}
// TODO Unify with `power` if UTPs (P1985) are accepted by the Committee
template<PowerVBase auto V, int Num, int... Den>
template<detail::PowerVBase auto V, int Num, int... Den>
requires(detail::valid_ratio<Num, Den...> && !detail::ratio_one<Num, Den...>)
struct power_v {
static constexpr auto base = V;
static constexpr detail::ratio exponent{Num, Den...};
};
template<typename T>
concept MagnitudeSpec = PowerVBase<T> || is_specialization_of_v<T, power_v>;
// TODO refactor to typenames?
template<MagnitudeSpec auto... Ms>
struct magnitude;
/**
* @brief Concept to detect whether T is a valid Magnitude.
*/
MP_UNITS_EXPORT template<typename T>
concept Magnitude = is_specialization_of_v<T, magnitude>;
namespace detail {
template<MagnitudeSpec Element>
template<MagnitudeSpecExpr Element>
[[nodiscard]] consteval auto get_base(Element element)
{
if constexpr (is_specialization_of_v<Element, power_v>)
@ -131,18 +101,18 @@ template<MagnitudeSpec Element>
return element;
}
template<MagnitudeSpec Element>
template<MagnitudeSpecExpr Element>
[[nodiscard]] consteval auto get_base_value(Element element)
{
if constexpr (is_specialization_of_v<Element, power_v>)
return get_base_value(Element::base);
else if constexpr (MagConstant<Element>)
return element.value;
return element._value_;
else
return element;
}
template<MagnitudeSpec Element>
template<MagnitudeSpecExpr Element>
[[nodiscard]] MP_UNITS_CONSTEVAL ratio get_exponent(Element)
{
if constexpr (is_specialization_of_v<Element, power_v>)
@ -164,7 +134,7 @@ template<PowerVBase auto V, ratio R>
}
}
template<MagnitudeSpec M>
template<MagnitudeSpecExpr M>
[[nodiscard]] consteval auto inverse(M)
{
return power_v_or_T<get_base(M{}), -1 * get_exponent(M{})>();
@ -178,7 +148,7 @@ using widen_t = conditional<std::is_arithmetic_v<T>,
T>;
template<typename T>
[[nodiscard]] consteval widen_t<T> compute_base_power(MagnitudeSpec auto el)
[[nodiscard]] consteval widen_t<T> compute_base_power(MagnitudeSpecExpr auto el)
{
// This utility can only handle integer powers. To compute rational powers at compile time, we'll
// need to write a custom function.
@ -210,23 +180,22 @@ template<typename T>
}
}
[[nodiscard]] consteval bool is_rational(MagnitudeSpec auto element)
[[nodiscard]] consteval bool is_rational(MagnitudeSpecExpr auto element)
{
static_assert(!Magnitude<decltype(element)>); // magnitudes are handles by another overload
return std::is_integral_v<decltype(get_base(element))> && (get_exponent(element).den == 1);
return std::is_integral_v<decltype(get_base(element))> && get_exponent(element).den == 1;
}
[[nodiscard]] consteval bool is_integral(MagnitudeSpec auto element)
[[nodiscard]] consteval bool is_integral(MagnitudeSpecExpr auto element)
{
static_assert(!Magnitude<decltype(element)>); // magnitudes are handles by another overload
return is_rational(element) && get_exponent(element).num > 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Magnitude product implementation.
[[nodiscard]] consteval bool less(MagnitudeSpec auto lhs, MagnitudeSpec auto rhs)
[[nodiscard]] consteval bool less(MagnitudeSpecExpr auto lhs, MagnitudeSpecExpr auto rhs)
{
// clang-arm64 raises "error: implicit conversion from 'long' to 'long double' may lose precision" so we need an explicit cast
// clang-arm64 raises "error: implicit conversion from 'long' to 'long double' may lose precision" so we need an
// explicit cast
using ct = std::common_type_t<decltype(get_base_value(lhs)), decltype(get_base_value(rhs))>;
return static_cast<ct>(get_base_value(lhs)) < static_cast<ct>(get_base_value(rhs));
}
@ -358,7 +327,7 @@ template<typename CharT, std::output_iterator<CharT> Out, auto M, auto... Rest>
bool negative_power)
{
auto to_symbol = [&]<typename T>(T v) {
out = copy_symbol<CharT>(get_base(v).symbol, fmt.encoding, negative_power, out);
out = copy_symbol<CharT>(get_base(v)._symbol_, fmt.encoding, negative_power, out);
constexpr ratio r = get_exponent(T{});
return copy_symbol_exponent<CharT, abs(r.num), r.den>(fmt.encoding, negative_power, out);
};
@ -370,7 +339,7 @@ template<typename CharT, Magnitude auto Num, Magnitude auto Den, Magnitude auto
constexpr Out magnitude_symbol_impl(Out out, const unit_symbol_formatting& fmt)
{
bool numerator = false;
constexpr auto num_value = get_value<std::intmax_t>(Num);
constexpr auto num_value = _get_value<std::intmax_t>(Num);
if constexpr (num_value != 1) {
constexpr auto num = detail::regular<num_value>();
out = copy_symbol<CharT>(num, fmt.encoding, false, out);
@ -386,7 +355,7 @@ constexpr Out magnitude_symbol_impl(Out out, const unit_symbol_formatting& fmt)
using enum unit_symbol_solidus;
bool denominator = false;
constexpr auto den_value = get_value<std::intmax_t>(Den);
constexpr auto den_value = _get_value<std::intmax_t>(Den);
constexpr auto den_constants_size = magnitude_list_size(DenConstants);
constexpr auto den_size = (den_value != 1) + den_constants_size;
auto start_denominator = [&]() {
@ -437,20 +406,8 @@ constexpr Out magnitude_symbol_impl(Out out, const unit_symbol_formatting& fmt)
* Magnitudes can be treated as values. Each type encodes exactly one value. Users can multiply, divide, raise to
* rational powers, and compare for equality.
*/
template<MagnitudeSpec auto... Ms>
template<detail::MagnitudeSpecExpr auto... Ms>
struct magnitude : detail::magnitude_base<magnitude<Ms...>> {
[[nodiscard]] friend consteval bool is_integral(const magnitude&)
{
using namespace detail; // needed for recursive case when magnitudes are in the MagnitudeSpec
return (is_integral(Ms) && ...);
}
[[nodiscard]] friend consteval bool is_rational(const magnitude&)
{
using namespace detail; // needed for recursive case when magnitudes are in the MagnitudeSpec
return (is_rational(Ms) && ...);
}
template<Magnitude M>
[[nodiscard]] friend consteval Magnitude auto operator*(magnitude m1, M m2)
{
@ -462,7 +419,7 @@ struct magnitude : detail::magnitude_base<magnitude<Ms...>> {
return _multiply_impl(m1, m2);
}
[[nodiscard]] friend consteval auto operator/(magnitude l, Magnitude auto r) { return l * pow<-1>(r); }
[[nodiscard]] friend consteval auto operator/(magnitude l, Magnitude auto r) { return l * _pow<-1>(r); }
template<Magnitude M2>
[[nodiscard]] friend consteval bool operator==(magnitude, M2)
@ -470,12 +427,17 @@ struct magnitude : detail::magnitude_base<magnitude<Ms...>> {
return std::is_same_v<magnitude, M2>;
}
private:
// all below functions should in fact be in a `detail` namespace but are placed here to benefit from the ADL
[[nodiscard]] friend consteval bool _is_integral(const magnitude&) { return (detail::is_integral(Ms) && ...); }
[[nodiscard]] friend consteval bool _is_rational(const magnitude&) { return (detail::is_rational(Ms) && ...); }
/**
* @brief The value of a Magnitude in a desired type T.
*/
template<typename T>
requires((detail::is_integral(Ms) && ...)) || treat_as_floating_point<T>
[[nodiscard]] friend consteval T get_value(const magnitude&)
[[nodiscard]] friend consteval T _get_value(const magnitude&)
{
// Force the expression to be evaluated in a constexpr context, to catch, e.g., overflow.
constexpr T result = detail::checked_static_cast<T>((detail::compute_base_power<T>(Ms) * ... * T{1}));
@ -485,7 +447,7 @@ struct magnitude : detail::magnitude_base<magnitude<Ms...>> {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Magnitude rational powers implementation.
template<int Num, int Den = 1>
[[nodiscard]] friend consteval auto pow(magnitude)
[[nodiscard]] friend consteval auto _pow(magnitude)
{
if constexpr (Num == 0) {
return magnitude<>{};
@ -495,10 +457,6 @@ struct magnitude : detail::magnitude_base<magnitude<Ms...>> {
}
}
[[nodiscard]] friend consteval auto sqrt(magnitude m) { return pow<1, 2>(m); }
[[nodiscard]] friend consteval auto cbrt(magnitude m) { return pow<1, 3>(m); }
private:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Magnitude numerator and denominator implementation.
[[nodiscard]] friend consteval auto _numerator(magnitude)
@ -506,7 +464,7 @@ private:
return (detail::integer_part(magnitude<Ms>{}) * ... * magnitude<>{});
}
[[nodiscard]] friend consteval auto _denominator(magnitude) { return _numerator(pow<-1>(magnitude{})); }
[[nodiscard]] friend consteval auto _denominator(magnitude) { return _numerator(_pow<-1>(magnitude{})); }
[[nodiscard]] friend consteval auto _remove_positive_powers(magnitude)
{
@ -690,10 +648,6 @@ struct prime_factorization<1> {
template<std::intmax_t N>
constexpr auto prime_factorization_v = prime_factorization<N>::value;
} // namespace detail
namespace detail {
template<MagArg auto V>
[[nodiscard]] consteval Magnitude auto make_magnitude()
{
@ -707,7 +661,7 @@ template<MagArg auto V>
MP_UNITS_EXPORT_BEGIN
template<MagArg auto V>
template<detail::MagArg auto V>
requires(detail::get_base_value(V) > 0)
constexpr Magnitude auto mag = detail::make_magnitude<V>();
@ -718,16 +672,16 @@ constexpr Magnitude auto mag_ratio = detail::prime_factorization_v<N> / detail::
/**
* @brief Create a Magnitude which is some rational number raised to a rational power.
*/
template<MagArg auto Base, int Num, int Den = 1>
template<detail::MagArg auto Base, int Num, int Den = 1>
requires(detail::get_base_value(Base) > 0)
constexpr Magnitude auto mag_power = pow<Num, Den>(mag<Base>);
constexpr Magnitude auto mag_power = _pow<Num, Den>(mag<Base>);
/**
* @brief A convenient Magnitude constant for pi, which we can manipulate like a regular number.
*/
#if defined MP_UNITS_COMP_CLANG || MP_UNITS_COMP_CLANG < 18
inline constexpr struct pi final : mag_constant<symbol_text{u8"𝜋", "pi"}> {
static constexpr auto value = std::numbers::pi_v<long double>;
static constexpr auto _value_ = std::numbers::pi_v<long double>;
#else
inline constexpr struct pi final : mag_constant<symbol_text{u8"𝜋", "pi"}, std::numbers::pi_v<long double>> {
#endif
@ -744,7 +698,7 @@ template<MagArg auto Base, int Num, int Den>
requires(detail::get_base_value(Base) > 0)
[[nodiscard]] consteval Magnitude auto mag_power_lazy()
{
return pow<Num, Den>(mag<Base>);
return _pow<Num, Den>(mag<Base>);
}
} // namespace detail

View File

@ -0,0 +1,92 @@
// 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
// IWYU pragma: private, include <mp-units/framework.h>
#include <mp-units/bits/module_macros.h>
#include <mp-units/ext/type_traits.h>
#include <mp-units/framework/expression_template.h>
#include <mp-units/framework/symbol_text.h>
#ifndef MP_UNITS_IN_MODULE_INTERFACE
#ifdef MP_UNITS_IMPORT_STD
import std;
#else
#include <concepts>
#endif
#endif
namespace mp_units {
#if defined MP_UNITS_COMP_CLANG || MP_UNITS_COMP_CLANG < 18
MP_UNITS_EXPORT template<symbol_text Symbol>
#else
MP_UNITS_EXPORT template<symbol_text Symbol, long double Value>
requires(Value > 0)
#endif
struct mag_constant;
MP_UNITS_EXPORT template<typename T>
concept MagConstant = detail::TagType<T> && is_derived_from_specialization_of_v<T, mag_constant>;
namespace detail {
/**
* @brief Any type which can be used as a basis vector in a PowerV.
*
* We have two categories.
*
* The first is just an integral type (either `int` or `std::intmax_t`). This is for prime number bases.
* These can always be used directly as NTTPs.
*
* The second category is a _custom tag type_, which inherits from `mag_constant` and has a static member variable
* `value` of type `long double` that holds its value. We choose `long double` to get the greatest degree of precision;
* users who need a different type can convert from this at compile time. This category is for any irrational base
* we admit into our representation (on which, more details below).
*/
template<typename T>
concept PowerVBase = one_of<T, int, std::intmax_t> || MagConstant<T>;
} // namespace detail
template<detail::PowerVBase auto V, int Num, int... Den>
requires(detail::valid_ratio<Num, Den...> && !detail::ratio_one<Num, Den...>)
struct power_v;
namespace detail {
template<typename T>
concept MagnitudeSpecExpr = PowerVBase<T> || is_specialization_of_v<T, power_v>;
}
template<detail::MagnitudeSpecExpr auto... Ms>
struct magnitude;
/**
* @brief Concept to detect whether T is a valid Magnitude.
*/
MP_UNITS_EXPORT template<typename T>
concept Magnitude = is_specialization_of_v<T, magnitude>;
} // namespace mp_units

View File

@ -56,7 +56,7 @@ template<Unit UFrom, Unit UTo>
if constexpr (is_same_v<UFrom, UTo>)
return true;
else
return is_integral(get_canonical_unit(from).mag / get_canonical_unit(to).mag);
return _is_integral(get_canonical_unit(from).mag / get_canonical_unit(to).mag);
}
template<typename T>

View File

@ -544,7 +544,7 @@ template<Unit T, symbol_text Symbol, Unit auto U, auto... Args>
template<typename F, int Num, int... Den, typename... Us>
[[nodiscard]] consteval auto get_canonical_unit_impl(const power<F, Num, Den...>&, const type_list<Us...>&)
{
auto mag = (mp_units::mag<1> * ... * pow<Num, Den...>(get_canonical_unit_impl(Us{}, Us{}).mag));
auto mag = (mp_units::mag<1> * ... * _pow<Num, Den...>(get_canonical_unit_impl(Us{}, Us{}).mag));
auto u = (one * ... * pow<Num, Den...>(get_canonical_unit_impl(Us{}, Us{}).reference_unit));
return canonical_unit{mag, u};
}
@ -556,9 +556,9 @@ template<typename T, typename F, int Num, int... Den>
if constexpr (requires { typename decltype(base.reference_unit)::_num_; }) {
auto num = get_canonical_unit_impl(power<F, Num, Den...>{}, typename decltype(base.reference_unit)::_num_{});
auto den = get_canonical_unit_impl(power<F, Num, Den...>{}, typename decltype(base.reference_unit)::_den_{});
return canonical_unit{pow<Num, Den...>(base.mag) * num.mag / den.mag, num.reference_unit / den.reference_unit};
return canonical_unit{_pow<Num, Den...>(base.mag) * num.mag / den.mag, num.reference_unit / den.reference_unit};
} else {
return canonical_unit{pow<Num, Den...>(base.mag),
return canonical_unit{_pow<Num, Den...>(base.mag),
derived_unit<power<decltype(base.reference_unit), Num, Den...>>{}};
}
}
@ -677,9 +677,9 @@ template<Unit U1, Unit U2>
constexpr auto canonical_lhs = get_canonical_unit(U1{});
constexpr auto canonical_rhs = get_canonical_unit(U2{});
if constexpr (is_integral(canonical_lhs.mag / canonical_rhs.mag))
if constexpr (_is_integral(canonical_lhs.mag / canonical_rhs.mag))
return u2;
else if constexpr (is_integral(canonical_rhs.mag / canonical_lhs.mag))
else if constexpr (_is_integral(canonical_rhs.mag / canonical_lhs.mag))
return u1;
else {
if constexpr (detail::unit_less<U1, U2>::value)

View File

@ -120,9 +120,9 @@ struct quantity_point_like_traits<std::chrono::time_point<C, std::chrono::durati
namespace detail {
[[nodiscard]] constexpr auto as_ratio(Magnitude auto m)
requires(is_rational(m))
requires(_is_rational(m))
{
return std::ratio<get_value<std::intmax_t>(_numerator(m)), get_value<std::intmax_t>(_denominator(m))>{};
return std::ratio<_get_value<std::intmax_t>(_numerator(m)), _get_value<std::intmax_t>(_denominator(m))>{};
}
} // namespace detail

View File

@ -143,7 +143,7 @@ static_assert(unit_symbol<usf{.encoding = ascii}>(mag_ratio<1, 18000> * (metre /
// magnitude constants
#if defined MP_UNITS_COMP_CLANG || MP_UNITS_COMP_CLANG < 18
inline constexpr struct e final : mag_constant<"e"> {
static constexpr long double value = std::numbers::e_v<long double>;
static constexpr long double _value_ = std::numbers::e_v<long double>;
#else
inline constexpr struct e final : mag_constant<"e", std::numbers::e_v<long double> > {
#endif