feat: interoperability with std::chrono::duration and other units libraries

Resolves #33
This commit is contained in:
Mateusz Pusz
2020-10-07 12:02:08 +02:00
parent 043046c068
commit 2dd8c90250
11 changed files with 206 additions and 44 deletions

View File

@@ -3,6 +3,7 @@
- **0.7.0 WIP**
- (!) refactor: `ScalableNumber` renamed to `QuantityValue`
- refactor: basic concepts, `quantity` and `quantity_cast` refactored
- feat: interoperability with `std::chrono::duration` and other units libraries
- feat: CTAD for dimensionless quantity added
- feat: value initialization for quantity value removed (left with a default initialization)
- perf: preconditions check do not influence the runtime performance of a Release build

View File

@@ -57,16 +57,21 @@ Concepts
A concept matching all quantities in the library. Satisfied by all instantiations of :class:`quantity`.
.. 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.
.. concept:: template<typename T> WrappedQuantity
A concept matching types that wrap quantity objects. Satisfied by all wrapper types that
satisfy :expr:`Quantity<typename T::value_type>` recursively
satisfy :expr:`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:`(!Quantity<T>) && (!WrappedQuantity<T>) && std::regular<T>` and
by types that match :expr:`(!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>`

View File

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

View File

@@ -229,6 +229,19 @@ concept Quantity = detail::is_quantity<T>;
template<typename T>
concept QuantityPoint = detail::is_quantity_point<T>;
/**
* @brief A concept matching all quantity-like types
*
* Satisfied by all types for which a correct specialization of `quantity_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>;
};
// QuantityValue
@@ -266,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> = Quantity<typename T::value_type> || is_wrapped_quantity<typename T::value_type>;
inline constexpr bool is_wrapped_quantity<T> = QuantityLike<typename T::value_type> || is_wrapped_quantity<typename T::value_type>;
} // namespace detail
@@ -287,7 +300,7 @@ concept wrapped_quantity_ = // exposition only
*/
template<typename T>
concept QuantityValue =
(!Quantity<T>) &&
(!QuantityLike<T>) &&
(!wrapped_quantity_<T>) &&
std::regular<T> &&
scalable_<T>;

View File

@@ -0,0 +1,39 @@
// 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 <units/customization_points.h>
#include <units/physical/si/base/time.h>
#include <chrono>
namespace units {
template<typename Rep, typename Period>
struct quantity_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;
static constexpr rep count(const std::chrono::duration<Rep, Period>& q) { return q.count(); }
};
} // namespace units

View File

@@ -60,4 +60,17 @@ struct quantity_values {
static constexpr Rep max() noexcept { return std::numeric_limits<Rep>::max(); }
};
/**
* @brief Provides support for external quantity-like types
*
* The type trait should provide the following nested type aliases: @c dimension, @c unit, @c rep,
* and a static member function @c count(T) that will return the raw value of the quantity.
*
* Usage example can be found in units/chrono.h header file.
*
* @tparam T the type to provide support for
*/
template<typename T>
struct quantity_traits;
} // namespace units

View File

@@ -36,8 +36,8 @@ namespace units {
template<typename T>
concept floating_point_ = // exposition only
(Quantity<T> && treat_as_floating_point<typename T::rep>) ||
(!Quantity<T> && treat_as_floating_point<T>);
(QuantityLike<T> && treat_as_floating_point<typename quantity_traits<T>::rep>) ||
(!QuantityLike<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
Quantity<QFrom> &&
QuantityLike<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
Quantity<QFrom> &&
QuantityOf<QTo, typename QFrom::dimension> &&
scalable_with_<typename QFrom::rep, typename QTo::rep> &&
QuantityLike<QFrom> &&
QuantityOf<QTo, typename quantity_traits<QFrom>::dimension> &&
scalable_with_<typename quantity_traits<QFrom>::rep, typename QTo::rep> &&
(floating_point_<QTo> || (!floating_point_<QFrom> && harmonic_<QFrom, QTo>));
template<typename Func, typename T, typename U>
@@ -133,8 +133,8 @@ 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<safe_castable_to_<quantity> Q2>
constexpr quantity(const Q2& q) : value_(quantity_cast<quantity>(q).count()) {}
template<safe_castable_to_<quantity> Q>
explicit(!Quantity<Q>) constexpr quantity(const Q& q) : value_(quantity_cast<quantity>(q).count()) {}
quantity& operator=(const quantity&) = default;
quantity& operator=(quantity&&) = default;
@@ -325,9 +325,13 @@ public:
// CTAD
template<QuantityValue V>
/* implicit */ quantity(V) -> quantity<dim_one, one, V>;
explicit(false) quantity(V) -> quantity<dim_one, one, V>;
template<Quantity Q1, QuantityEquivalentTo<Q1> Q2>
template<QuantityLike Q>
explicit(!Quantity<Q>) quantity(Q) -> quantity<typename quantity_traits<Q>::dimension, typename quantity_traits<Q>::unit, typename quantity_traits<Q>::rep>;
// non-member binary operators
template<QuantityLike 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)
{
@@ -399,6 +403,15 @@ template<Quantity Q1, QuantityEquivalentTo<Q1> Q2>
return cq(lhs).count() == cq(rhs).count();
}
// 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,14 +44,17 @@ class quantity_point;
namespace detail {
template<typename D, typename U, typename Rep>
constexpr auto quantity_ratio(const quantity<D, U, Rep>&)
template<QuantityLike Q>
constexpr auto quantity_ratio(const Q&)
{
if constexpr(BaseDimension<D>) {
return U::ratio;
using traits = quantity_traits<Q>;
using dim = TYPENAME traits::dimension;
using unit = TYPENAME traits::unit;
if constexpr(BaseDimension<dim>) {
return unit::ratio;
}
else {
return D::base_units_ratio * U::ratio / D::coherent_unit::ratio;
return dim::base_units_ratio * unit::ratio / dim::coherent_unit::ratio;
}
}
@@ -102,29 +105,34 @@ struct cast_traits<From, To> {
*
* @tparam To a target quantity type to cast to
*/
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)
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)
{
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 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());
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());
if constexpr (treat_as_floating_point<rep_type>) {
return ret(static_cast<TYPENAME To::rep>(static_cast<rep_type>(q.count()) *
return ret(static_cast<TYPENAME To::rep>(static_cast<rep_type>(traits::count(q)) *
(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>(q.count()) *
return ret(static_cast<TYPENAME To::rep>(static_cast<rep_type>(traits::count(q)) *
(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>(q.count()) *
return ret(static_cast<TYPENAME To::rep>(static_cast<rep_type>(traits::count(q)) *
static_cast<ratio_type>(c_ratio.num) /
(static_cast<ratio_type>(c_ratio.den) * static_cast<ratio_type>(detail::ipow10(-c_ratio.exp)))));
}
@@ -143,11 +151,11 @@ template<Quantity To, typename D, typename U, scalable_with_<typename To::rep> R
*
* @tparam ToD a dimension type to use for a target quantity
*/
template<Dimension ToD, typename D, typename U, typename Rep>
requires equivalent<ToD, D>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q)
template<Dimension ToD, QuantityLike From>
requires equivalent<ToD, typename quantity_traits<From>::dimension>
[[nodiscard]] constexpr auto quantity_cast(const From& q)
{
return quantity_cast<quantity<ToD, dimension_unit<ToD>, Rep>>(q);
return quantity_cast<quantity<ToD, dimension_unit<ToD>, typename quantity_traits<From>::rep>>(q);
}
/**
@@ -162,11 +170,11 @@ template<Dimension ToD, typename D, typename U, typename Rep>
*
* @tparam ToU a unit type to use for a target quantity
*/
template<Unit ToU, typename D, typename U, typename Rep>
requires UnitOf<ToU, D>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q)
template<Unit ToU, QuantityLike From>
requires UnitOf<ToU, typename quantity_traits<From>::dimension>
[[nodiscard]] constexpr auto quantity_cast(const From& q)
{
return quantity_cast<quantity<D, ToU, Rep>>(q);
return quantity_cast<quantity<typename quantity_traits<From>::dimension, ToU, typename quantity_traits<From>::rep>>(q);
}
/**
@@ -185,11 +193,11 @@ template<Unit ToU, typename D, typename U, typename Rep>
* @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, typename D, typename U, typename Rep>
requires equivalent<ToD, D> && UnitOf<ToU, ToD>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q)
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)
{
return quantity_cast<quantity<ToD, ToU, Rep>>(q);
return quantity_cast<quantity<ToD, ToU, typename quantity_traits<From>::rep>>(q);
}
/**
@@ -204,10 +212,11 @@ template<Dimension ToD, Unit ToU, typename D, typename U, typename Rep>
*
* @tparam ToRep a representation type to use for a target quantity
*/
template<QuantityValue ToRep, typename D, typename U, scalable_with_<ToRep> Rep>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q)
template<QuantityValue ToRep, QuantityLike From>
requires scalable_with_<typename quantity_traits<From>::rep, ToRep>
[[nodiscard]] constexpr auto quantity_cast(const From& q)
{
return quantity_cast<quantity<D, U, ToRep>>(q);
return quantity_cast<quantity<typename quantity_traits<From>::dimension, typename quantity_traits<From>::unit, ToRep>>(q);
}
/**

View File

@@ -24,6 +24,7 @@ cmake_minimum_required(VERSION 3.12)
add_library(unit_tests_static
cgs_test.cpp
chrono_test.cpp
concepts_test.cpp
custom_rep_test_min_expl.cpp
custom_rep_test_min_impl.cpp

View File

@@ -0,0 +1,57 @@
// 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.
#include "test_tools.h"
#include <units/chrono.h>
#include <units/physical/si/derived/speed.h>
namespace {
using namespace units;
using namespace units::physical;
using namespace units::physical::si::literals;
using namespace std::chrono_literals;
// construction
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::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::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>>);
// operators
static_assert(quantity{1s} + 1_q_s == 2_q_s);
static_assert(quantity{1s} + 1_q_min == 61_q_s);
static_assert(10_q_m / quantity{2s} == 5_q_m_per_s);
} // namespace

View File

@@ -24,6 +24,7 @@
#include "units/physical/si/derived/speed.h"
#include "units/physical/si/fps/derived/speed.h"
#include "units/quantity_point.h"
#include "units/chrono.h"
#include <chrono>
#include <complex>
#include <mutex>
@@ -113,6 +114,13 @@ static_assert(QuantityPoint<quantity_point<si::dim_length, si::metre>>);
static_assert(!QuantityPoint<si::length<si::metre>>);
static_assert(!QuantityPoint<std::chrono::seconds>);
// QuantityLike
static_assert(QuantityLike<std::chrono::seconds>);
static_assert(QuantityLike<std::chrono::hours>);
static_assert(QuantityLike<si::time<si::second>>);
static_assert(!QuantityLike<int>);
// WrappedQuantity
static_assert(wrapped_quantity_<std::optional<si::length<si::metre>>>);