refactor: interoperability with quantity-like types simplified

Type trait specialization for each `quantity` type is not needed anymore.

Relates to #33.
This commit is contained in:
Mateusz Pusz
2020-10-07 14:29:12 +02:00
parent 2dd8c90250
commit d64d26a4c4
9 changed files with 66 additions and 78 deletions

View File

@ -59,20 +59,20 @@ Concepts
.. concept:: template<typename T> QuantityLike
A concept matching all quantity-like types. Satisfied by all types for which a correct specialization
of :class:`quantity_traits` type trait is provided.
A concept matching all quantity-like types other than specialization of :class:`quantity`. Satisfied by
all types for which a correct specialization of :class:`quantity_like_traits` type trait is provided.
.. concept:: template<typename T> WrappedQuantity
A concept matching types that wrap quantity objects. Satisfied by all wrapper types that
satisfy :expr:`QuantityLike<typename T::value_type>` recursively
satisfy :expr:`Quantity<typename T::value_type> || QuantityLike<typename T::value_type>` recursively
(i.e. ``std::optional<si::length<si::metre>>``).
.. concept:: template<typename T> QuantityValue
A concept matching types that can be used as a `Quantity` representation type. Satisfied
by types that match :expr:`(!QuantityLike<T>) && (!WrappedQuantity<T>) && std::regular<T>` and
satisfy one of the following:
by types that match :expr:`(!Quantity<T>) && (!QuantityLike<T>) && (!WrappedQuantity<T>) && std::regular<T>`
and satisfy one of the following:
- if :expr:`common_type_with<T, std::intmax_t>` is ``true``, then :expr:`std::common_type_t<T, std::intmax_t>`
must at least provide binary multiplication and division operators,

View File

@ -6,5 +6,5 @@ Customization Points
.. doxygenstruct:: units::quantity_values
:members:
.. doxygenstruct:: units::quantity_traits
.. doxygenstruct:: units::quantity_like_traits
:members:

View File

@ -216,7 +216,7 @@ inline constexpr bool is_quantity_point = false;
/**
* @brief A concept matching all quantities in the library.
*
* Satisfied by all specializations of :class:`quantity`.
* Satisfied by all specializations of @c quantity.
*/
template<typename T>
concept Quantity = detail::is_quantity<T>;
@ -224,23 +224,23 @@ concept Quantity = detail::is_quantity<T>;
/**
* @brief A concept matching all quantity points in the library.
*
* Satisfied by all specializations of :class:`quantity_point`.
* Satisfied by all specializations of @c quantity_point.
*/
template<typename T>
concept QuantityPoint = detail::is_quantity_point<T>;
/**
* @brief A concept matching all quantity-like types
* @brief A concept matching all quantity-like types (other than specialization of @c quantity)
*
* Satisfied by all types for which a correct specialization of `quantity_traits`
* Satisfied by all types for which a correct specialization of `quantity_like_traits`
* type trait is provided.
*/
template<typename T>
concept QuantityLike = requires(T q) {
typename quantity_traits<T>::dimension;
typename quantity_traits<T>::unit;
typename quantity_traits<T>::rep;
{ quantity_traits<T>::count(q) } -> std::convertible_to<typename quantity_traits<T>::rep>;
typename quantity_like_traits<T>::dimension;
typename quantity_like_traits<T>::unit;
typename quantity_like_traits<T>::rep;
{ quantity_like_traits<T>::count(q) } -> std::convertible_to<typename quantity_like_traits<T>::rep>;
};
// QuantityValue
@ -279,7 +279,7 @@ inline constexpr bool is_wrapped_quantity = false;
template<typename T>
requires requires { typename T::value_type; }
inline constexpr bool is_wrapped_quantity<T> = QuantityLike<typename T::value_type> || is_wrapped_quantity<typename T::value_type>;
inline constexpr bool is_wrapped_quantity<T> = Quantity<typename T::value_type> || QuantityLike<typename T::value_type> || is_wrapped_quantity<typename T::value_type>;
} // namespace detail
@ -300,6 +300,7 @@ concept wrapped_quantity_ = // exposition only
*/
template<typename T>
concept QuantityValue =
(!Quantity<T>) &&
(!QuantityLike<T>) &&
(!wrapped_quantity_<T>) &&
std::regular<T> &&

View File

@ -29,7 +29,7 @@
namespace units {
template<typename Rep, typename Period>
struct quantity_traits<std::chrono::duration<Rep, Period>> {
struct quantity_like_traits<std::chrono::duration<Rep, Period>> {
using dimension = physical::si::dim_time;
using unit = downcast_unit<dimension, ratio(Period::num, Period::den)>;
using rep = Rep;

View File

@ -71,6 +71,6 @@ struct quantity_values {
* @tparam T the type to provide support for
*/
template<typename T>
struct quantity_traits;
struct quantity_like_traits;
} // namespace units

View File

@ -36,8 +36,8 @@ namespace units {
template<typename T>
concept floating_point_ = // exposition only
(QuantityLike<T> && treat_as_floating_point<typename quantity_traits<T>::rep>) ||
(!QuantityLike<T> && treat_as_floating_point<T>);
(Quantity<T> && treat_as_floating_point<typename T::rep>) ||
(!Quantity<T> && treat_as_floating_point<T>);
template<typename From, typename To>
concept safe_convertible_to_ = // exposition only
@ -49,15 +49,15 @@ concept safe_convertible_to_ = // exposition only
// QFrom ratio is an exact multiple of QTo
template<typename QFrom, typename QTo>
concept harmonic_ = // exposition only
QuantityLike<QFrom> &&
Quantity<QFrom> &&
Quantity<QTo> &&
requires(QFrom from, QTo to) { requires is_integral(detail::quantity_ratio(from) / detail::quantity_ratio(to)); };
template<typename QFrom, typename QTo>
concept safe_castable_to_ = // exposition only
QuantityLike<QFrom> &&
QuantityOf<QTo, typename quantity_traits<QFrom>::dimension> &&
scalable_with_<typename quantity_traits<QFrom>::rep, typename QTo::rep> &&
Quantity<QFrom> &&
QuantityOf<QTo, typename QFrom::dimension> &&
scalable_with_<typename QFrom::rep, typename QTo::rep> &&
(floating_point_<QTo> || (!floating_point_<QFrom> && harmonic_<QFrom, QTo>));
template<typename Func, typename T, typename U>
@ -81,6 +81,9 @@ template<typename Func, Quantity Q1, QuantityEquivalentTo<Q1> Q2>
requires quantity_value_for_<Func, typename Q1::rep, typename Q2::rep>
using common_quantity_for = common_quantity<Q1, Q2, std::invoke_result_t<Func, typename Q1::rep, typename Q2::rep>>;
template<QuantityLike Q>
using quantity_like_type = quantity<typename quantity_like_traits<Q>::dimension, typename quantity_like_traits<Q>::unit, typename quantity_like_traits<Q>::rep>;
/**
* @brief A quantity
*
@ -133,8 +136,12 @@ public:
template<safe_convertible_to_<rep> Value>
explicit(!(equivalent<quantity, dimensionless<::units::one, rep>>)) constexpr quantity(const Value& v) : value_(static_cast<rep>(v)) {}
template<QuantityLike Q>
requires std::same_as<quantity, quantity_like_type<Q>>
explicit constexpr quantity(const Q& q) : value_(q.count()) {}
template<safe_castable_to_<quantity> Q>
explicit(!Quantity<Q>) constexpr quantity(const Q& q) : value_(quantity_cast<quantity>(q).count()) {}
constexpr quantity(const Q& q) : value_(quantity_cast<quantity>(q).count()) {}
quantity& operator=(const quantity&) = default;
quantity& operator=(quantity&&) = default;
@ -328,10 +335,10 @@ template<QuantityValue V>
explicit(false) quantity(V) -> quantity<dim_one, one, V>;
template<QuantityLike Q>
explicit(!Quantity<Q>) quantity(Q) -> quantity<typename quantity_traits<Q>::dimension, typename quantity_traits<Q>::unit, typename quantity_traits<Q>::rep>;
explicit quantity(Q) -> quantity_like_type<Q>;
// non-member binary operators
template<QuantityLike Q1, QuantityEquivalentTo<Q1> Q2>
template<Quantity Q1, QuantityEquivalentTo<Q1> Q2>
requires quantity_value_for_<std::plus<>, typename Q1::rep, typename Q2::rep>
[[nodiscard]] constexpr Quantity auto operator+(const Q1& lhs, const Q2& rhs)
{
@ -404,14 +411,6 @@ template<Quantity Q1, QuantityEquivalentTo<Q1> Q2>
}
// type traits
template<typename D, typename U, typename Rep>
struct quantity_traits<quantity<D, U, Rep>> {
using dimension = D;
using unit = U;
using rep = Rep;
static constexpr rep count(const quantity<D, U, Rep>& q) { return q.count(); }
};
namespace detail {
template<typename D, typename U, typename Rep>

View File

@ -44,17 +44,14 @@ class quantity_point;
namespace detail {
template<QuantityLike Q>
constexpr auto quantity_ratio(const Q&)
template<typename D, typename U, typename Rep>
constexpr auto quantity_ratio(const quantity<D, U, Rep>&)
{
using traits = quantity_traits<Q>;
using dim = TYPENAME traits::dimension;
using unit = TYPENAME traits::unit;
if constexpr(BaseDimension<dim>) {
return unit::ratio;
if constexpr(BaseDimension<D>) {
return U::ratio;
}
else {
return dim::base_units_ratio * unit::ratio / dim::coherent_unit::ratio;
return D::base_units_ratio * U::ratio / D::coherent_unit::ratio;
}
}
@ -105,34 +102,29 @@ struct cast_traits<From, To> {
*
* @tparam To a target quantity type to cast to
*/
template<Quantity To, QuantityLike From>
requires QuantityOf<To, typename quantity_traits<From>::dimension> &&
scalable_with_<typename quantity_traits<From>::rep, typename To::rep>
[[nodiscard]] constexpr auto quantity_cast(const From& q)
template<Quantity To, typename D, typename U, scalable_with_<typename To::rep> Rep>
requires QuantityOf<To, D>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q)
{
using traits = quantity_traits<From>;
using from_dim = TYPENAME traits::dimension;
using from_unit = TYPENAME traits::unit;
using from_rep = TYPENAME traits::rep;
using ret_unit = downcast_unit<typename To::dimension, To::unit::ratio>;
using ret = quantity<typename To::dimension, ret_unit, typename To::rep>;
using cast_traits = detail::cast_traits<from_rep, typename To::rep>;
using ratio_type = TYPENAME cast_traits::ratio_type;
using rep_type = TYPENAME cast_traits::rep_type;
constexpr auto c_ratio = detail::cast_ratio(quantity<from_dim, from_unit, from_rep>(), To());
using traits = detail::cast_traits<Rep, typename To::rep>;
using ratio_type = TYPENAME traits::ratio_type;
using rep_type = TYPENAME traits::rep_type;
constexpr auto c_ratio = detail::cast_ratio(quantity<D, U, Rep>(), To());
if constexpr (treat_as_floating_point<rep_type>) {
return ret(static_cast<TYPENAME To::rep>(static_cast<rep_type>(traits::count(q)) *
return ret(static_cast<TYPENAME To::rep>(static_cast<rep_type>(q.count()) *
(static_cast<ratio_type>(c_ratio.num) * detail::fpow10<ratio_type>(c_ratio.exp) / static_cast<ratio_type>(c_ratio.den))));
}
else {
if constexpr (c_ratio.exp > 0) {
return ret(static_cast<TYPENAME To::rep>(static_cast<rep_type>(traits::count(q)) *
return ret(static_cast<TYPENAME To::rep>(static_cast<rep_type>(q.count()) *
(static_cast<ratio_type>(c_ratio.num) * static_cast<ratio_type>(detail::ipow10(c_ratio.exp))) /
static_cast<ratio_type>(c_ratio.den)));
}
else {
return ret(static_cast<TYPENAME To::rep>(static_cast<rep_type>(traits::count(q)) *
return ret(static_cast<TYPENAME To::rep>(static_cast<rep_type>(q.count()) *
static_cast<ratio_type>(c_ratio.num) /
(static_cast<ratio_type>(c_ratio.den) * static_cast<ratio_type>(detail::ipow10(-c_ratio.exp)))));
}
@ -151,11 +143,11 @@ template<Quantity To, QuantityLike From>
*
* @tparam ToD a dimension type to use for a target quantity
*/
template<Dimension ToD, QuantityLike From>
requires equivalent<ToD, typename quantity_traits<From>::dimension>
[[nodiscard]] constexpr auto quantity_cast(const From& q)
template<Dimension ToD, typename D, typename U, typename Rep>
requires equivalent<ToD, D>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q)
{
return quantity_cast<quantity<ToD, dimension_unit<ToD>, typename quantity_traits<From>::rep>>(q);
return quantity_cast<quantity<ToD, dimension_unit<ToD>, Rep>>(q);
}
/**
@ -170,11 +162,11 @@ template<Dimension ToD, QuantityLike From>
*
* @tparam ToU a unit type to use for a target quantity
*/
template<Unit ToU, QuantityLike From>
requires UnitOf<ToU, typename quantity_traits<From>::dimension>
[[nodiscard]] constexpr auto quantity_cast(const From& q)
template<Unit ToU, typename D, typename U, typename Rep>
requires UnitOf<ToU, D>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q)
{
return quantity_cast<quantity<typename quantity_traits<From>::dimension, ToU, typename quantity_traits<From>::rep>>(q);
return quantity_cast<quantity<D, ToU, Rep>>(q);
}
/**
@ -193,11 +185,11 @@ template<Unit ToU, QuantityLike From>
* @tparam ToD a dimension type to use for a target quantity
* @tparam ToU a unit type to use for a target quantity
*/
template<Dimension ToD, Unit ToU, QuantityLike From>
requires equivalent<ToD, typename quantity_traits<From>::dimension> && UnitOf<ToU, ToD>
[[nodiscard]] constexpr auto quantity_cast(const From& q)
template<Dimension ToD, Unit ToU, typename D, typename U, typename Rep>
requires equivalent<ToD, D> && UnitOf<ToU, ToD>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q)
{
return quantity_cast<quantity<ToD, ToU, typename quantity_traits<From>::rep>>(q);
return quantity_cast<quantity<ToD, ToU, Rep>>(q);
}
/**
@ -212,11 +204,10 @@ template<Dimension ToD, Unit ToU, QuantityLike From>
*
* @tparam ToRep a representation type to use for a target quantity
*/
template<QuantityValue ToRep, QuantityLike From>
requires scalable_with_<typename quantity_traits<From>::rep, ToRep>
[[nodiscard]] constexpr auto quantity_cast(const From& q)
template<QuantityValue ToRep, typename D, typename U, scalable_with_<ToRep> Rep>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q)
{
return quantity_cast<quantity<typename quantity_traits<From>::dimension, typename quantity_traits<From>::unit, ToRep>>(q);
return quantity_cast<quantity<D, U, ToRep>>(q);
}
/**

View File

@ -37,14 +37,11 @@ static_assert(std::constructible_from<si::time<si::second, std::int64_t>, std::c
static_assert(!std::convertible_to<std::chrono::seconds, si::time<si::second, std::int64_t>>);
static_assert(std::constructible_from<si::time<si::second, std::int64_t>, std::chrono::seconds>);
static_assert(!std::convertible_to<std::chrono::seconds, si::time<si::second, std::int64_t>>);
static_assert(std::constructible_from<si::time<si::second, std::int64_t>, std::chrono::hours>);
static_assert(!std::constructible_from<si::time<si::second, std::int64_t>, std::chrono::hours>);
static_assert(!std::convertible_to<std::chrono::hours, si::time<si::second, std::int64_t>>);
static_assert(!std::constructible_from<si::time<si::hour, std::int64_t>, std::chrono::seconds>);
static_assert(!std::convertible_to<std::chrono::seconds, si::time<si::hour, std::int64_t>>);
// quantity_cast
static_assert(quantity_cast<si::time<si::hour, int>>(7200s).count() == 2);
// CTAD
static_assert(std::is_same_v<decltype(quantity{1s}), si::time<si::second, std::int64_t>>);
static_assert(std::is_same_v<decltype(quantity{1h}), si::time<si::hour, std::int64_t>>);

View File

@ -118,7 +118,7 @@ static_assert(!QuantityPoint<std::chrono::seconds>);
static_assert(QuantityLike<std::chrono::seconds>);
static_assert(QuantityLike<std::chrono::hours>);
static_assert(QuantityLike<si::time<si::second>>);
static_assert(!QuantityLike<si::time<si::second>>);
static_assert(!QuantityLike<int>);
// WrappedQuantity