feat: quantity_spec support added

This commit is contained in:
Mateusz Pusz committed 2022-12-16 18:15:48 +01:00
1 parent e0101f14d9
commit 92c70f1a4e
32 files changed
+1524 -827

No files matched your search

+21
View File
@@ -24,6 +24,7 @@
#include <units/bits/external/hacks.h> // IWYU pragma: keep
#include <compare>
#include <initializer_list>
#include <iterator>
#include <ranges>
@@ -111,6 +112,26 @@ constexpr auto lexicographical_compare_three_way(I1 f1, I1 l1, I2 f2, I2 l2)
return ::units::detail::lexicographical_compare_three_way(f1, l1, f2, l2, std::compare_three_way());
}
template<class ForwardIt>
constexpr ForwardIt max_element(ForwardIt first, ForwardIt last)
{
if (first == last) return last;
ForwardIt largest = first;
++first;
for (; first != last; ++first)
if (*largest < *first) largest = first;
return largest;
}
template<class T>
constexpr T max(std::initializer_list<T> ilist)
{
return *max_element(ilist.begin(), ilist.end());
}
template<class I, class O>
struct in_out_result {
[[no_unique_address]] I in;
@@ -104,6 +104,19 @@ struct power {
namespace detail {
template<typename T>
struct expr_type_impl : std::type_identity<T> {};
template<typename T, int... Ints>
struct expr_type_impl<power<T, Ints...>> : std::type_identity<T> {};
} // namespace detail
template<typename T>
using expr_type = TYPENAME detail::expr_type_impl<T>::type;
namespace detail {
template<typename T>
inline constexpr bool is_specialization_of_power = false;
@@ -256,16 +269,7 @@ struct expr_simplify<type_list<power<T, Ints1...>, NRest...>, type_list<power<T,
// expr_less
template<typename Lhs, typename Rhs, template<typename, typename> typename Pred>
struct expr_less_impl : Pred<Lhs, Rhs> {};
template<typename Lhs, int... Ints1, typename Rhs, int... Ints2, template<typename, typename> typename Pred>
struct expr_less_impl<power<Lhs, Ints1...>, power<Rhs, Ints2...>, Pred> : Pred<Lhs, Rhs> {};
template<typename Lhs, int... Ints, typename Rhs, template<typename, typename> typename Pred>
struct expr_less_impl<power<Lhs, Ints...>, Rhs, Pred> : Pred<Lhs, Rhs> {};
template<typename Lhs, typename Rhs, int... Ints, template<typename, typename> typename Pred>
struct expr_less_impl<Lhs, power<Rhs, Ints...>, Pred> : Pred<Lhs, Rhs> {};
struct expr_less_impl : Pred<expr_type<Lhs>, expr_type<Rhs>> {};
template<typename T, int... Ints, template<typename, typename> typename Pred>
struct expr_less_impl<T, power<T, Ints...>, Pred> : std::true_type {};
@@ -504,19 +508,29 @@ template<typename T, template<typename> typename Proj>
concept expr_projectable = requires {
typename T::_num_;
typename T::_den_;
requires type_list_size<typename T::_num_> + type_list_size<typename T::_den_> > 0;
requires expr_projectable_impl<typename T::_num_, Proj>;
requires expr_projectable_impl<typename T::_den_, Proj>;
};
template<typename T>
[[nodiscard]] consteval auto map_power(T t)
{
return t;
}
template<typename T, auto... Ints>
[[nodiscard]] consteval auto map_power(power<T, Ints...>)
{
return pow<Ints...>(T{});
}
template<template<typename> typename Proj, template<typename...> typename To, typename OneType,
template<typename, typename> typename Pred, expr_type_projectable<Proj>... Nums,
expr_type_projectable<Proj>... Dens>
[[nodiscard]] consteval auto expr_map_impl(type_list<Nums...>, type_list<Dens...>)
{
using nums = type_list_sort<type_list<typename expr_type_map<std::remove_const_t<Nums>, Proj>::type...>, Pred>;
using dens = type_list_sort<type_list<typename expr_type_map<std::remove_const_t<Dens>, Proj>::type...>, Pred>;
return detail::get_optimized_expression<nums, dens, OneType, Pred, To>();
return (OneType{} * ... * map_power(typename expr_type_map<std::remove_const_t<Nums>, Proj>::type{})) /
(OneType{} * ... * map_power(typename expr_type_map<std::remove_const_t<Dens>, Proj>::type{}));
}
/**
@@ -532,7 +546,10 @@ template<template<typename> typename Proj, template<typename...> typename To, ty
template<typename, typename> typename Pred, expr_projectable<Proj> T>
[[nodiscard]] consteval auto expr_map(T)
{
return expr_map_impl<Proj, To, OneType, Pred>(typename T::_num_{}, typename T::_den_{});
if constexpr (type_list_size<typename T::_num_> + type_list_size<typename T::_den_> == 0)
return OneType{};
else
return expr_map_impl<Proj, To, OneType, Pred>(typename T::_num_{}, typename T::_den_{});
}
} // namespace detail
+1 -1
View File
@@ -78,6 +78,6 @@ template<typename T, template<typename...> typename Type>
concept is_derived_from_specialization_of = requires(T* t) { detail::to_base_specialization_of<Type>(t); };
template<typename T, typename... Ts>
concept one_of = (... || std::same_as<T, Ts>);
concept one_of = (false || ... || std::same_as<T, Ts>);
} // namespace units
+14 -11
View File
@@ -22,17 +22,14 @@
#pragma once
// IWYU pragma: begin_exports
// #include <units/bits/basic_concepts.h>
// #include <units/bits/quantity_of.h>
// IWYU pragma: end_exports
#include <units/bits/external/type_traits.h>
#include <units/dimension.h>
#include <units/quantity_spec.h>
#include <units/unit.h>
namespace units {
template<Dimension auto D, Unit auto U>
template<QuantitySpec auto Q, Unit auto U>
struct reference;
namespace detail {
@@ -40,8 +37,8 @@ namespace detail {
template<typename T>
inline constexpr bool is_specialization_of_reference = false;
template<Dimension auto D, Unit auto U>
inline constexpr bool is_specialization_of_reference<reference<D, U>> = true;
template<auto Q, auto U>
inline constexpr bool is_specialization_of_reference<reference<Q, U>> = true;
} // namespace detail
@@ -92,11 +89,15 @@ class quantity;
namespace detail {
// TODO make the below code from the comment to compile and replace it
template<auto R, typename Rep>
inline constexpr bool is_quantity<quantity<R, Rep>> = true;
// template<auto R, typename Rep>
// void to_base_specialization_of_quantity(const volatile quantity<R, Rep>*);
// template<typename T>
// requires units::is_derived_from_specialization_of<T, units::quantity>
// requires requires(T* t) { to_base_specialization_of_quantity(t); }
// inline constexpr bool is_quantity<T> = true;
} // namespace detail
@@ -109,6 +110,7 @@ inline constexpr bool is_quantity<quantity<R, Rep>> = true;
*/
template<typename Q, auto V>
concept quantity_of = Quantity<Q> && ((Dimension<std::remove_const_t<decltype(V)>> && Q::dimension == V) ||
(QuantitySpec<std::remove_const_t<decltype(V)>> && Q::quantity_spec == V) ||
(Reference<std::remove_const_t<decltype(V)>> && Q::reference == V));
/**
@@ -119,8 +121,9 @@ concept quantity_of = Quantity<Q> && ((Dimension<std::remove_const_t<decltype(V)
*/
template<typename Q, auto V>
concept weak_quantity_of = Quantity<Q> &&
((Dimension<std::remove_const_t<decltype(V)>> && interconvertible(Q::dimension, V)) ||
(Reference<std::remove_const_t<decltype(V)>> &&
interconvertible(Q::dimension, V.dimension) && Q::unit == V.unit));
((Dimension<std::remove_const_t<decltype(V)>> && Q::dimension == V) ||
(QuantitySpec<std::remove_const_t<decltype(V)>> && interconvertible(Q::quantity_spec, V)) ||
(Reference<std::remove_const_t<decltype(V)>> && Q::dimension == V.dimension &&
Q::unit == V.unit));
} // namespace units
+72 -245
View File
@@ -23,49 +23,11 @@
#pragma once
#include <units/bits/expression_template.h>
#include <units/bits/external/fixed_string.h>
#include <units/bits/external/type_traits.h>
#include <units/unit.h>
#include <units/symbol_text.h>
namespace units {
namespace detail {
template<typename T>
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<typename T>
concept DerivedDimension = detail::is_derived_dimension<T>;
/**
* @brief A concept matching all dimensions in the library.
*
* Satisfied by all dimension types for which either `BaseDimension<T>` or `DerivedDimension<T>` is `true`.
*/
template<typename T>
concept Dimension = BaseDimension<T> || DerivedDimension<T>;
namespace detail {
template<Unit auto U, Dimension auto Dim>
inline constexpr bool is_valid_unit_for_dimension = false;
}
template<typename U, typename Dim>
concept valid_unit_for_dimension = Unit<U> && Dimension<Dim> && detail::is_valid_unit_for_dimension<U{}, Dim{}>;
template<Dimension auto D, Unit auto U>
struct reference;
/**
* @brief A dimension of a base quantity
*
@@ -74,17 +36,17 @@ struct reference;
* 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
* `Symbol` template parameter 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 : base_dimension<"L"> {} length;
* inline constexpr struct time : base_dimension<"T"> {} time;
* inline constexpr struct mass : base_dimension<"M"> {} mass;
* inline constexpr struct dim_length : base_dimension<"L"> {} dim_length;
* inline constexpr struct dim_time : base_dimension<"T"> {} dim_time;
* inline constexpr struct dim_mass : base_dimension<"M"> {} dim_mass;
* @endcode
*
* @note A common convention in this library is to assign the same name for a type and an object of this type.
@@ -94,28 +56,35 @@ struct reference;
*
* @tparam Symbol an unique identifier of the base dimension used to provide dimensional analysis support
*/
#ifdef __cpp_explicit_this_parameter
template<basic_fixed_string Symbol>
#else
template<typename Self, basic_fixed_string Symbol>
#endif
template<basic_symbol_text Symbol>
struct base_dimension {
static constexpr auto symbol = Symbol; ///< Unique base dimension identifier
#ifdef __cpp_explicit_this_parameter
template<typename Self, valid_unit_for_dimension<Self> U>
[[nodiscard]] constexpr auto operator[](this const Self, U)
#else
template<valid_unit_for_dimension<Self> U>
[[nodiscard]] constexpr auto operator[](U) const
#endif
{
return reference<Self{}, U{}>{};
}
};
namespace detail {
template<basic_symbol_text Symbol>
void to_base_base_dimension(const volatile base_dimension<Symbol>*);
template<typename T>
inline constexpr bool is_specialization_of_base_dimension = false;
template<basic_symbol_text Symbol>
inline constexpr bool is_specialization_of_base_dimension<base_dimension<Symbol>> = 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<typename T>
concept BaseDimension = requires(T* t) { detail::to_base_base_dimension(t); } &&
(!detail::is_specialization_of_base_dimension<T>);
namespace detail {
template<BaseDimension Lhs, BaseDimension Rhs>
struct base_dimension_less : std::bool_constant<(Lhs::symbol < Rhs::symbol)> {};
@@ -140,22 +109,9 @@ inline constexpr bool is_per_of_dims<per<Ts...>> =
} // namespace detail
template<typename T>
concept DerivedDimensionSpec =
concept DerivedDimensionExpr =
BaseDimension<T> || detail::is_dimension_one<T> || detail::is_power_of_dim<T> || detail::is_per_of_dims<T>;
template<typename...>
struct derived_dimension;
namespace detail {
template<typename... Ds>
struct derived_dimension_impl : detail::expr_fractions<derived_dimension<>, Ds...> {
using _type_ = derived_dimension<Ds...>; // exposition only
};
} // namespace detail
/**
* @brief A dimension of a derived quantity
*
@@ -172,99 +128,41 @@ struct derived_dimension_impl : detail::expr_fractions<derived_dimension<>, Ds..
* For example:
*
* @code{.cpp}
* inline constexpr struct frequency : decltype(1 / time) {} frequency;
* inline constexpr struct speed : decltype(length / time) {} speed;
* inline constexpr struct acceleration : decltype(speed / time) {} acceleration;
* inline constexpr struct force : decltype(mass * acceleration) {} force;
* inline constexpr struct energy : decltype(force * length) {} energy;
* inline constexpr struct moment_of_force : decltype(length * force) {} moment_of_force;
* inline constexpr struct torque : decltype(moment_of_force) {} torque;
* using frequency = decltype(1 / dim_time);
* using speed = decltype(dim_length / dim_time);
* using acceleration = decltype(dim_speed / dim_time);
* using force = decltype(dim_mass * dim_acceleration);
* using energy = decltype(dim_force * dim_length);
* using moment_of_force = decltype(dim_length * dim_force);
* using torque = decltype(dim_moment_of_force);
* @endcode
*
* - `frequency` will be derived from type `derived_dimension<dimension_one, per<time>>`
* - `speed` will be derived from type `derived_dimension<length, per<time>>`
* - `acceleration` will be derived from type `derived_dimension<length, per<power<time, 2>>>`
* - `force` will be derived from type `derived_dimension<length, mass, per<power<time, 2>>>`
* - `energy` will be derived from type `derived_dimension<power<length, 2>, mass, per<power<time, 2>>>`
* - `frequency` will be derived from type `derived_dimension<dimension_one, per<dim_time>>`
* - `speed` will be derived from type `derived_dimension<dim_length, per<dim_time>>`
* - `acceleration` will be derived from type `derived_dimension<dim_length, per<power<dim_time, 2>>>`
* - `force` will be derived from type `derived_dimension<dim_length, dim_mass, per<power<dim_time, 2>>>`
* - `energy` will be derived from type `derived_dimension<power<dim_length, 2>, dim_mass, per<power<dim_time, 2>>>`
*
* @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` and
* `derived_dimension<length, per<time>>` are considered not equal. They are convertible though.
* User can implicitly convert up and down the inheritance hierarchy between those two.
* `torque` and `moment_of_force` are convertible as well. However, `energy` and `torque`
* 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, `dimension_one`, `power<Dim, Num, Den>`, `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.
*/
#ifdef __cpp_explicit_this_parameter
template<DerivedDimensionSpec... Ds>
struct derived_dimension : detail::derived_dimension_impl<Ds...> {
template<typename Self, Unit U>
requires valid_unit_for_dimension<U, Self> || (sizeof...(Ds) == 0 && interconvertible(U{}, one))
[[nodiscard]] constexpr auto operator[](this const Self, U)
{
return reference<Self{}, U{}>{};
}
};
#else
template<typename...>
struct derived_dimension;
template<DerivedDimensionSpec... Ds>
struct derived_dimension<Ds...> : detail::derived_dimension_impl<Ds...> {
template<Unit U>
requires valid_unit_for_dimension<U, derived_dimension> || (sizeof...(Ds) == 0 && interconvertible(U{}, one))
[[nodiscard]] constexpr auto operator[](U) const
{
return reference<derived_dimension{}, U{}>{};
}
};
template<typename Self, DerivedDimension D>
struct derived_dimension<Self, D> : D {
template<Unit U>
requires valid_unit_for_dimension<U, Self> ||
(interconvertible(derived_dimension{}, derived_dimension<>{}) && interconvertible(U{}, one))
[[nodiscard]] constexpr auto operator[](U) const
{
return reference<Self{}, U{}>{};
}
};
#endif
namespace detail {
template<typename... Ds>
void to_base_specialization_of_derived_dimension(const volatile derived_dimension<Ds...>*);
template<typename T>
inline constexpr bool is_derived_from_specialization_of_derived_dimension =
requires(T * t) { to_base_specialization_of_derived_dimension(t); };
template<typename T>
requires is_derived_from_specialization_of_derived_dimension<T>
inline constexpr bool is_derived_dimension<T> = true;
} // namespace detail
template<DerivedDimensionExpr... Ds>
struct derived_dimension : detail::expr_fractions<derived_dimension<>, Ds...> {};
/**
* @brief Dimension one
*
* Dimension for which all the exponents of the factors corresponding to the base
* dimensions are zero. Also commonly named as "dimensionless".
* dimensions are zero. It is a dimension of a quantity of dimension one also known as
* "dimensionless".
*/
inline constexpr struct dimension_one : derived_dimension<> {
} dimension_one;
@@ -274,21 +172,32 @@ namespace detail {
template<>
inline constexpr bool is_dimension_one<struct dimension_one> = true;
template<Dimension T>
struct dim_type_impl {
using type = T;
};
template<typename... Ds>
void to_base_specialization_of_derived_dimension(const volatile derived_dimension<Ds...>*);
template<DerivedDimension T>
struct dim_type_impl<T> {
using type = TYPENAME T::_type_;
};
template<Dimension T>
using dim_type = TYPENAME dim_type_impl<T>::type;
template<typename T>
inline constexpr bool is_derived_from_specialization_of_derived_dimension =
requires(T * t) { to_base_specialization_of_derived_dimension(t); };
} // namespace detail
/**
* @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 (inheritance needed to properly handle `dimension_one`).
*/
template<typename T>
concept DerivedDimension = detail::is_derived_from_specialization_of_derived_dimension<T>;
/**
* @brief A concept matching all dimensions in the library.
*
* Satisfied by all dimension types for which either `BaseDimension<T>` or `DerivedDimension<T>` is `true`.
*/
template<typename T>
concept Dimension = BaseDimension<T> || DerivedDimension<T>;
// Operators
@@ -296,21 +205,21 @@ template<Dimension Lhs, Dimension Rhs>
[[nodiscard]] consteval Dimension auto operator*(Lhs, Rhs)
{
return detail::expr_multiply<derived_dimension, struct dimension_one, detail::type_list_of_base_dimension_less>(
detail::dim_type<Lhs>{}, detail::dim_type<Rhs>{});
Lhs{}, Rhs{});
}
template<Dimension Lhs, Dimension Rhs>
[[nodiscard]] consteval Dimension auto operator/(Lhs, Rhs)
{
return detail::expr_divide<derived_dimension, struct dimension_one, detail::type_list_of_base_dimension_less>(
detail::dim_type<Lhs>{}, detail::dim_type<Rhs>{});
return detail::expr_divide<derived_dimension, struct dimension_one, detail::type_list_of_base_dimension_less>(Lhs{},
Rhs{});
}
template<Dimension D>
[[nodiscard]] consteval Dimension auto operator/(int value, D)
{
gsl_Expects(value == 1);
return detail::expr_invert<derived_dimension, struct dimension_one>(detail::dim_type<D>{});
return detail::expr_invert<derived_dimension, struct dimension_one>(D{});
}
template<Dimension D>
@@ -322,31 +231,6 @@ template<Dimension Lhs, Dimension Rhs>
return is_same_v<Lhs, Rhs>;
}
template<Dimension D1, Dimension D2>
[[nodiscard]] consteval bool interconvertible(D1, D2)
{
return std::derived_from<D1, D2> || std::derived_from<D2, D1>;
}
[[nodiscard]] consteval auto common_dimension(Dimension auto d) { return d; }
template<Dimension D1, Dimension D2>
[[nodiscard]] consteval auto common_dimension(D1 d1, D2 d2)
requires(interconvertible(d1, d2))
{
if constexpr (std::derived_from<D1, D2>)
return d1;
else
return d2;
}
[[nodiscard]] consteval auto common_dimension(Dimension auto d1, Dimension auto d2, Dimension auto d3,
Dimension auto... rest)
requires requires { common_dimension(common_dimension(d1, d2), d3, rest...); }
{
return common_dimension(common_dimension(d1, d2), d3, rest...);
}
/**
* @brief Computes the value of a dimension raised to the `Num/Den` power
*
@@ -370,63 +254,6 @@ template<std::intmax_t Num, std::intmax_t Den = 1, Dimension D>
detail::type_list_of_base_dimension_less>(d);
}
namespace detail {
template<Unit U>
[[nodiscard]] consteval Dimension auto get_dimension_for_impl(U)
requires requires { U::base_dimension; }
{
return U::base_dimension;
}
template<Unit U>
requires requires { U::base_dimension; }
using to_base_dimension = std::remove_const_t<decltype(U::base_dimension)>;
template<typename... Us>
[[nodiscard]] consteval Dimension auto get_dimension_for_impl(const derived_unit<Us...>& u)
requires detail::expr_projectable<derived_unit<Us...>, to_base_dimension>
{
return detail::expr_map<to_base_dimension, derived_dimension, struct dimension_one,
detail::type_list_of_base_dimension_less>(u);
}
template<typename U>
concept associated_unit = Unit<U> && requires(U u) { get_dimension_for_impl(get_canonical_unit(u).reference_unit); };
[[nodiscard]] consteval Dimension auto get_dimension_for(associated_unit auto u)
{
return get_dimension_for_impl(get_canonical_unit(u).reference_unit);
}
template<Unit auto U, Dimension auto Dim>
requires requires { detail::get_dimension_for(U); } && (interconvertible(Dim, detail::get_dimension_for(U)))
inline constexpr bool is_valid_unit_for_dimension<U, Dim> = true;
} // namespace detail
// TODO consider adding the support for text output of the dimensional equation
} // namespace units
#ifdef __cpp_explicit_this_parameter
#define BASE_DIMENSION(name, symbol) \
inline constexpr struct name : base_dimension<symbol> { \
} name
#define DERIVED_DIMENSION(name, base) \
inline constexpr struct name : base { \
} name
#else
#define BASE_DIMENSION(name, symbol) \
inline constexpr struct name : base_dimension<name, symbol> { \
} name
#define DERIVED_DIMENSION(name, base) \
inline constexpr struct name : derived_dimension<name, base> { \
} name
#endif
@@ -27,7 +27,7 @@
namespace units {
struct dimension_one; // defined in <units/dimension.h>
struct dimensionless; // defined in <units/quantity_spec.h>
struct one; // defined in <units/unit.h>
// clang-format off
+71 -173
View File
@@ -22,10 +22,7 @@
#pragma once
#include <units/bits/dimension_op.h>
#include <units/bits/external/hacks.h>
#include <units/generic/angle.h>
#include <units/generic/dimensionless.h>
#include <units/quantity.h>
#include <units/unit.h>
@@ -39,9 +36,9 @@
namespace units {
/**
* @brief Computes the value of a quantity raised to the power `N`
* @brief Computes the value of a quantity raised to the `Num/Den` power
*
* Both the quantity value and its dimension are the base of the operation.
* Both the quantity value and its quantity specification are the base of the operation.
*
* @tparam Num Exponent numerator
* @tparam Den Exponent denominator
@@ -56,11 +53,11 @@ template<std::intmax_t Num, std::intmax_t Den = 1, Quantity Q>
using rep = TYPENAME Q::rep;
if constexpr (Num == 0) {
return rep(1);
} else if constexpr (ratio{Num, Den} == 1) {
return q;
} else {
using dim = dimension_pow<typename Q::dimension, Num, Den>;
using unit = downcast_unit<dim, pow<ratio{Num, Den}>(Q::unit::mag)>;
using std::pow;
return quantity<dim, unit, rep>(
return quantity<reference<pow<Num, Den>(Q::quantity_spec), pow<Num, Den>(Q::unit)>{}, rep>(
static_cast<rep>(pow(q.number(), static_cast<double>(Num) / static_cast<double>(Den))));
}
}
@@ -68,7 +65,7 @@ template<std::intmax_t Num, std::intmax_t Den = 1, Quantity Q>
/**
* @brief Computes the square root of a quantity
*
* Both the quantity value and its dimension are the base of the operation.
* Both the quantity value and its quantity specification are the base of the operation.
*
* @param q Quantity being the base of the operation
* @return Quantity The result of computation
@@ -77,17 +74,16 @@ template<Quantity Q>
[[nodiscard]] inline Quantity auto sqrt(const Q& q) noexcept
requires requires { sqrt(q.number()); } || requires { std::sqrt(q.number()); }
{
using dim = dimension_pow<typename Q::dimension, 1, 2>;
using unit = downcast_unit<dim, pow<ratio{1, 2}>(Q::unit::mag)>;
using rep = TYPENAME Q::rep;
using std::sqrt;
return quantity<dim, unit, rep>(static_cast<rep>(sqrt(q.number())));
return quantity<reference<pow<1, 2>(Q::quantity_spec), pow<1, 2>(Q::unit)>{}, rep>(
static_cast<rep>(sqrt(q.number())));
}
/**
* @brief Computes the cubic root of a quantity
*
* Both the quantity value and its dimension are the base of the operation.
* Both the quantity value and its quantity specification are the base of the operation.
*
* @param q Quantity being the base of the operation
* @return Quantity The result of computation
@@ -96,11 +92,10 @@ template<Quantity Q>
[[nodiscard]] inline Quantity auto cbrt(const Q& q) noexcept
requires requires { cbrt(q.number()); } || requires { std::cbrt(q.number()); }
{
using dim = dimension_pow<typename Q::dimension, 1, 3>;
using unit = downcast_unit<dim, pow<ratio{1, 3}>(Q::unit::mag)>;
using rep = TYPENAME Q::rep;
using std::cbrt;
return quantity<dim, unit, rep>(static_cast<rep>(cbrt(q.number())));
return quantity<reference<pow<1, 3>(Q::quantity_spec), pow<1, 3>(Q::unit)>{}, rep>(
static_cast<rep>(cbrt(q.number())));
}
/**
@@ -111,12 +106,12 @@ template<Quantity Q>
* @param q Quantity being the base of the operation
* @return Quantity The value of the same quantity type
*/
template<typename U, typename Rep>
[[nodiscard]] inline dimensionless<U, Rep> exp(const dimensionless<U, Rep>& q)
template<quantity_of<dimensionless> Q, typename Rep>
[[nodiscard]] inline Q exp(const Q& q)
requires requires { exp(q.number()); } || requires { std::exp(q.number()); }
{
using std::exp;
return quantity_cast<U>(dimensionless<one, Rep>(exp(quantity_cast<one>(q).number())));
return quantity_cast<Q::unit>(exp(quantity_cast<one>(q).number()) * dimensionless[one]);
}
/**
@@ -125,12 +120,12 @@ template<typename U, typename Rep>
* @param q Quantity being the base of the operation
* @return Quantity The absolute value of a provided quantity
*/
template<typename D, typename U, typename Rep>
[[nodiscard]] inline quantity<D, U, Rep> abs(const quantity<D, U, Rep>& q) noexcept
template<Quantity Q>
[[nodiscard]] inline Q abs(const Q& q) noexcept
requires requires { abs(q.number()); } || requires { std::abs(q.number()); }
{
using std::abs;
return quantity<D, U, Rep>(abs(q.number()));
return Q(abs(q.number()));
}
/**
@@ -141,11 +136,11 @@ template<typename D, typename U, typename Rep>
* @tparam Q Quantity type being the base of the operation
* @return Quantity The epsilon value for quantity's representation type
*/
template<Quantity Q>
requires requires { std::numeric_limits<typename Q::rep>::epsilon(); }
[[nodiscard]] constexpr Quantity auto epsilon() noexcept
template<Representation Rep, Reference R>
requires requires { std::numeric_limits<Rep>::epsilon(); }
[[nodiscard]] constexpr Quantity auto epsilon(R r) noexcept
{
return Q(std::numeric_limits<typename Q::rep>::epsilon());
return std::numeric_limits<Rep>::epsilon() * r;
}
/**
@@ -154,14 +149,14 @@ template<Quantity Q>
* @tparam q Quantity being the base of the operation
* @return Quantity The rounded quantity with unit type To
*/
template<Unit To, typename D, typename U, typename Rep>
[[nodiscard]] constexpr quantity<D, To, Rep> floor(const quantity<D, U, Rep>& q) noexcept
template<Unit auto To, auto R, typename Rep>
[[nodiscard]] constexpr quantity<reference<R.quantity_spec, To>{}, Rep> floor(const quantity<R, Rep>& q) noexcept
requires((!treat_as_floating_point<Rep>) || requires { floor(q.number()); } ||
requires { std::floor(q.number()); }) &&
(std::same_as<To, U> || requires {
::units::quantity_cast<To>(q);
quantity<D, To, Rep>::one();
})
(To == R.unit || requires {
::units::quantity_cast<To>(q);
quantity<reference<R.quantity_spec, To>{}, Rep>::one();
})
{
const auto handle_signed_results = [&]<typename T>(const T& res) {
if (res > q) {
@@ -171,46 +166,34 @@ template<Unit To, typename D, typename U, typename Rep>
};
if constexpr (treat_as_floating_point<Rep>) {
using std::floor;
if constexpr (std::is_same_v<To, U>) {
return quantity<D, To, Rep>(floor(q.number()));
if constexpr (To == R.unit) {
return quantity<reference<R.quantity_spec, To>{}, Rep>(floor(q.number()));
} else {
return handle_signed_results(quantity<D, To, Rep>(floor(quantity_cast<To>(q).number())));
return handle_signed_results(
quantity<reference<R.quantity_spec, To>{}, Rep>(floor(quantity_cast<To>(q).number())));
}
} else {
if constexpr (std::is_same_v<To, U>) {
return q;
if constexpr (To == R.unit) {
return quantity_cast<To>(q);
} else {
return handle_signed_results(quantity_cast<To>(q));
}
}
}
/**
* @brief Overload of @c ::units::floor<Unit>() using the unit type of To
*
* @tparam q Quantity being the base of the operation
* @return Quantity The rounded quantity with unit type of quantity To
*/
template<Quantity To, std::same_as<typename To::dimension> D, typename U, std::same_as<typename To::rep> Rep>
[[nodiscard]] constexpr quantity<D, typename To::unit, Rep> floor(const quantity<D, U, Rep>& q) noexcept
requires requires { ::units::floor<typename To::unit>(q); }
{
return ::units::floor<typename To::unit>(q);
}
/**
* @brief Computes the smallest quantity with integer representation and unit type To with its number not less than q
*
* @tparam q Quantity being the base of the operation
* @return Quantity The rounded quantity with unit type To
*/
template<Unit To, typename D, typename U, typename Rep>
[[nodiscard]] constexpr quantity<D, To, Rep> ceil(const quantity<D, U, Rep>& q) noexcept
template<Unit auto To, auto R, typename Rep>
[[nodiscard]] constexpr quantity<reference<R.quantity_spec, To>{}, Rep> ceil(const quantity<R, Rep>& q) noexcept
requires((!treat_as_floating_point<Rep>) || requires { ceil(q.number()); } || requires { std::ceil(q.number()); }) &&
(std::same_as<To, U> || requires {
::units::quantity_cast<To>(q);
quantity<D, To, Rep>::one();
})
(To == R.unit || requires {
::units::quantity_cast<To>(q);
quantity<reference<R.quantity_spec, To>{}, Rep>::one();
})
{
const auto handle_signed_results = [&]<typename T>(const T& res) {
if (res < q) {
@@ -220,33 +203,21 @@ template<Unit To, typename D, typename U, typename Rep>
};
if constexpr (treat_as_floating_point<Rep>) {
using std::ceil;
if constexpr (std::is_same_v<To, U>) {
return quantity<D, To, Rep>(ceil(q.number()));
if constexpr (To == R.unit) {
return quantity<reference<R.quantity_spec, To>{}, Rep>(ceil(q.number()));
} else {
return handle_signed_results(quantity<D, To, Rep>(ceil(quantity_cast<To>(q).number())));
return handle_signed_results(
quantity<reference<R.quantity_spec, To>{}, Rep>(ceil(quantity_cast<To>(q).number())));
}
} else {
if constexpr (std::is_same_v<To, U>) {
return q;
if constexpr (To == R.unit) {
return quantity_cast<To>(q);
} else {
return handle_signed_results(quantity_cast<To>(q));
}
}
}
/**
* @brief Overload of @c ::units::ceil<Unit>() using the unit type of To
*
* @tparam q Quantity being the base of the operation
* @return Quantity The rounded quantity with unit type of quantity To
*/
template<Quantity To, std::same_as<typename To::dimension> D, typename U, std::same_as<typename To::rep> Rep>
[[nodiscard]] constexpr quantity<D, typename To::unit, Rep> ceil(const quantity<D, U, Rep>& q) noexcept
requires requires { ::units::ceil<typename To::unit>(q); }
{
return ::units::ceil<typename To::unit>(q);
}
/**
* @brief Computes the nearest quantity with integer representation and unit type To to q
*
@@ -255,25 +226,25 @@ template<Quantity To, std::same_as<typename To::dimension> D, typename U, std::s
* @tparam q Quantity being the base of the operation
* @return Quantity The rounded quantity with unit type To
*/
template<Unit To, typename D, typename U, typename Rep>
[[nodiscard]] constexpr quantity<D, To, Rep> round(const quantity<D, U, Rep>& q) noexcept
template<Unit auto To, auto R, typename Rep>
[[nodiscard]] constexpr quantity<reference<R.quantity_spec, To>{}, Rep> round(const quantity<R, Rep>& q) noexcept
requires((!treat_as_floating_point<Rep>) || requires { round(q.number()); } ||
requires { std::round(q.number()); }) &&
(std::same_as<To, U> || requires {
::units::floor<To>(q);
quantity<D, To, Rep>::one();
})
(To == R.unit || requires {
::units::floor<To>(q);
quantity<reference<R.quantity_spec, To>{}, Rep>::one();
})
{
if constexpr (std::is_same_v<To, U>) {
if constexpr (To == R.unit) {
if constexpr (treat_as_floating_point<Rep>) {
using std::round;
return quantity<D, To, Rep>(round(q.number()));
return quantity<reference<R.quantity_spec, To>{}, Rep>(round(q.number()));
} else {
return q;
return quantity_cast<To>(q);
}
} else {
const auto res_low = units::floor<To>(q);
const auto res_high = res_low + decltype(res_low)::one();
const auto res_high = res_low + res_low.one();
const auto diff0 = q - res_low;
const auto diff1 = res_high - q;
if (diff0 == diff1) {
@@ -289,35 +260,20 @@ template<Unit To, typename D, typename U, typename Rep>
}
}
/**
* @brief Overload of @c ::units::round<Unit>() using the unit type of To
*
* @tparam q Quantity being the base of the operation
* @return Quantity The rounded quantity with unit type of quantity To
*/
template<Quantity To, std::same_as<typename To::dimension> D, typename U, std::same_as<typename To::rep> Rep>
[[nodiscard]] constexpr quantity<D, typename To::unit, Rep> round(const quantity<D, U, Rep>& q) noexcept
requires requires { ::units::round<typename To::unit>(q); }
{
return ::units::round<typename To::unit>(q);
}
/**
* @brief Computes the square root of the sum of the squares of x and y,
* without undue overflow or underflow at intermediate stages of the computation
*/
template<Quantity Q1, Quantity Q2>
[[nodiscard]] inline std::common_type_t<Q1, Q2> hypot(const Q1& x, const Q2& y) noexcept
requires requires { typename std::common_type_t<Q1, Q2>; } &&
requires(std::common_type_t<Q1, Q2> q) {
requires requires { hypot(q.number(), q.number()); } || requires { std::hypot(q.number(), q.number()); };
}
[[nodiscard]] inline quantity_of<common_reference(Q1::reference, Q2::reference)> auto hypot(const Q1& x,
const Q2& y) noexcept
requires requires { common_reference(Q1::reference, Q2::reference); } &&
(
requires { hypot(x.number(), y.number()); } || requires { std::hypot(x.number(), y.number()); })
{
using type = std::common_type_t<Q1, Q2>;
type xx = x;
type yy = y;
using std::hypot;
return type(hypot(xx.number(), yy.number()));
using type = quantity<common_reference(Q1::reference, Q2::reference), decltype(hypot(x.number(), y.number()))>;
return type{hypot(x.number(), y.number())};
}
/**
@@ -325,75 +281,17 @@ template<Quantity Q1, Quantity Q2>
* without undue overflow or underflow at intermediate stages of the computation
*/
template<Quantity Q1, Quantity Q2, Quantity Q3>
[[nodiscard]] inline std::common_type_t<Q1, Q2, Q3> hypot(const Q1& x, const Q2& y, const Q3& z) noexcept
requires requires { typename std::common_type_t<Q1, Q2, Q3>; } &&
requires(std::common_type_t<Q1, Q2, Q3> q) {
requires requires { hypot(q.number(), q.number(), q.number()); } ||
requires { std::hypot(q.number(), q.number(), q.number()); };
}
[[nodiscard]] inline quantity_of<common_reference(Q1::reference, Q2::reference, Q3::reference)> auto hypot(
const Q1& x, const Q2& y, const Q3& z) noexcept
requires requires { common_reference(Q1::reference, Q2::reference, Q3::reference); } &&
(
requires { hypot(x.number(), y.number(), z.number()); } ||
requires { std::hypot(x.number(), y.number(), z.number()); })
{
using type = std::common_type_t<Q1, Q2, Q3>;
type xx = x;
type yy = y;
type zz = z;
using std::hypot;
return type(hypot(xx.number(), yy.number(), zz.number()));
}
template<typename U, typename Rep>
requires treat_as_floating_point<Rep>
[[nodiscard]] inline dimensionless<one, Rep> sin(const angle<U, Rep>& q) noexcept
requires requires { sin(q.number()); } || requires { std::sin(q.number()); }
{
using std::sin;
return sin(quantity_cast<radian>(q).number());
}
template<typename U, typename Rep>
requires treat_as_floating_point<Rep>
[[nodiscard]] inline dimensionless<one, Rep> cos(const angle<U, Rep>& q) noexcept
requires requires { cos(q.number()); } || requires { std::cos(q.number()); }
{
using std::cos;
return cos(quantity_cast<radian>(q).number());
}
template<typename U, typename Rep>
requires treat_as_floating_point<Rep>
[[nodiscard]] inline dimensionless<one, Rep> tan(const angle<U, Rep>& q) noexcept
requires requires { tan(q.number()); } || requires { std::tan(q.number()); }
{
using std::tan;
return tan(quantity_cast<radian>(q).number());
}
template<typename U, typename Rep>
requires treat_as_floating_point<Rep>
[[nodiscard]] inline angle<radian, Rep> asin(const dimensionless<U, Rep>& q) noexcept
requires requires { asin(q.number()); } || requires { std::asin(q.number()); }
{
using std::asin;
return angle<radian, Rep>(asin(quantity_cast<one>(q).number()));
}
template<typename U, typename Rep>
requires treat_as_floating_point<Rep>
[[nodiscard]] inline angle<radian, Rep> acos(const dimensionless<U, Rep>& q) noexcept
requires requires { acos(q.number()); } || requires { std::acos(q.number()); }
{
using std::acos;
return angle<radian, Rep>(acos(quantity_cast<one>(q).number()));
}
template<typename U, typename Rep>
requires treat_as_floating_point<Rep>
[[nodiscard]] inline angle<radian, Rep> atan(const dimensionless<U, Rep>& q) noexcept
requires requires { atan(q.number()); } || requires { std::atan(q.number()); }
{
using std::atan;
return angle<radian, Rep>(atan(quantity_cast<one>(q).number()));
using type = quantity<common_reference(Q1::reference, Q2::reference, Q3::reference),
decltype(hypot(x.number(), y.number(), z.number()))>;
return type{hypot(x.number(), y.number(), z.number())};
}
} // namespace units
+10 -13
View File
@@ -29,6 +29,7 @@
#include <units/concepts.h>
#include <units/customization_points.h>
#include <units/dimension.h>
#include <units/quantity_spec.h>
#include <units/reference.h>
#include <units/unit.h>
#include <compare>
@@ -115,9 +116,10 @@ class quantity {
public:
// member types and values
using rep = Rep;
static constexpr auto reference = R;
static constexpr auto dimension = R.dimension;
static constexpr auto unit = R.unit;
static constexpr Reference auto reference = R;
static constexpr QuantitySpec auto quantity_spec = R.quantity_spec;
static constexpr Dimension auto dimension = R.dimension;
static constexpr Unit auto unit = R.unit;
// static member functions
[[nodiscard]] static constexpr quantity zero() noexcept
@@ -176,10 +178,10 @@ public:
[[nodiscard]] constexpr const rep&& number() const&& noexcept { return std::move(number_); }
template<Unit U>
requires quantity_convertible_to_<quantity, quantity<::units::reference<dimension, U{}>{}, Rep>>
[[nodiscard]] constexpr quantity<::units::reference<dimension, U{}>{}, Rep> operator[](U) const
requires quantity_convertible_to_<quantity, quantity<::units::reference<quantity_spec, U{}>{}, Rep>>
[[nodiscard]] constexpr quantity<::units::reference<quantity_spec, U{}>{}, Rep> operator[](U) const
{
return quantity<::units::reference<dimension, U{}>{}, Rep>{*this};
return quantity<::units::reference<quantity_spec, U{}>{}, Rep>{*this};
}
// member unary operators
@@ -424,7 +426,7 @@ public:
requires(!Quantity<Value>) && invoke_result_convertible_to_<rep, std::divides<>, const Value&, rep>
[[nodiscard]] friend constexpr Quantity auto operator/(const Value& v, const quantity& q)
{
return detail::make_quantity<dimension_one[::units::one] / reference>(v / q.number());
return detail::make_quantity<dimensionless[::units::one] / reference>(v / q.number());
}
template<typename Value>
@@ -459,16 +461,11 @@ public:
};
// CTAD
#if !UNITS_COMP_CLANG || UNITS_COMP_CLANG > 16
template<auto R, typename Rep>
explicit(false) quantity(Rep&&) -> quantity<R, Rep>;
#endif
template<auto R, typename Rep>
explicit(false) quantity(quantity<R, Rep>) -> quantity<R, Rep>;
template<Representation Rep>
explicit(false) quantity(Rep)->quantity<dimension_one[one], Rep>;
explicit(false) quantity(Rep)->quantity<dimensionless[one], Rep>;
template<QuantityLike Q>
explicit quantity(Q) -> quantity<reference<quantity_like_traits<Q>::dimension, quantity_like_traits<Q>::unit>{},
+20 -23
View File
@@ -52,11 +52,12 @@ class quantity;
* @brief Explicit cast of a quantity
*
* Implicit conversions between quantities of different types are allowed only for "safe"
* (i.e. non-truncating) conversion. In such cases an explicit cast have to be used.
* (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used.
*
* This cast gets the target quantity type to cast to. For example:
*
* auto q1 = units::quantity_cast<units::isq::si::time<units::isq::si::second>>(1_q_ms);
* auto q1 = 1234. * isq::length[mm];
* auto q2 = quantity_cast<quantity<isq::height[m], int>>(q1);
*
* @tparam To a target quantity type to cast to
*/
@@ -110,17 +111,13 @@ template<Quantity To, auto R, typename Rep>
* @brief Explicit cast of a quantity
*
* Implicit conversions between quantities of different types are allowed only for "safe"
* (i.e. non-truncating) conversion. In such cases an explicit cast have to be used.
* (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used.
*
* This cast gets both the target dimension and unit to cast to. For example:
* This cast gets a target reference to cast to. For example:
*
* auto q1 = units::quantity_cast<units::isq::si::dim_speed, units::isq::si::kilometre_per_hour>(v1);
* auto v = quantity_cast<isq::velocity[km / h]>(q);
*
* @note This cast is especially useful when working with quantities of unknown dimensions
* (@c unknown_dimension).
*
* @tparam ToD a dimension type to use for a target quantity
* @tparam ToU a unit type to use for a target quantity
* @tparam ToR a reference to use for a target quantity
*/
template<Reference auto ToR, auto R, typename Rep>
requires(interconvertible(ToR, R))
@@ -134,19 +131,19 @@ template<Reference auto ToR, auto R, typename Rep>
* @brief Explicit cast of a quantity
*
* Implicit conversions between quantities of different types are allowed only for "safe"
* (i.e. non-truncating) conversion. In such cases an explicit cast have to be used.
* (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used.
*
* This cast gets only the target dimension to cast to. For example:
* This cast gets only the target quantity specification to cast to. For example:
*
* auto q1 = units::quantity_cast<units::isq::si::dim_acceleration>(200_q_Gal);
* auto v = quantity_cast<isq::velocity>(120 * isq::length[km] / (2 * isq::time[h]));
*
* @tparam ToD a dimension type to use for a target quantity
* @tparam ToQS a quantity specification to use for a target quantity
*/
template<Dimension auto ToD, auto R, typename Rep>
requires(interconvertible(ToD, R.dimension))
template<QuantitySpec auto ToQS, auto R, typename Rep>
requires(interconvertible(ToQS, R.quantity_spec))
[[nodiscard]] constexpr auto quantity_cast(const quantity<R, Rep>& q)
{
constexpr reference<ToD, quantity<R, Rep>::unit> r;
constexpr reference<ToQS, quantity<R, Rep>::unit> r;
return quantity_cast<quantity<r, Rep>>(q);
}
@@ -154,19 +151,19 @@ template<Dimension auto ToD, auto R, typename Rep>
* @brief Explicit cast of a quantity
*
* Implicit conversions between quantities of different types are allowed only for "safe"
* (i.e. non-truncating) conversion. In such cases an explicit cast have to be used.
* (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used.
*
* This cast gets only the target unit to cast to. For example:
*
* auto q1 = units::quantity_cast<units::isq::si::second>(1_q_ms);
* auto d = quantity_cast<si::second>(1234 * isq::time[ms]);
*
* @tparam ToU a unit type to use for a target quantity
* @tparam ToU a unit to use for a target quantity
*/
template<Unit auto ToU, auto R, typename Rep>
requires(interconvertible(ToU, R.unit))
[[nodiscard]] constexpr auto quantity_cast(const quantity<R, Rep>& q)
{
constexpr reference<quantity<R, Rep>::dimension, ToU> r;
constexpr reference<quantity<R, Rep>::quantity_spec, ToU> r;
return quantity_cast<quantity<r, Rep>>(q);
}
@@ -174,11 +171,11 @@ template<Unit auto ToU, auto R, typename Rep>
* @brief Explicit cast of a quantity
*
* Implicit conversions between quantities of different types are allowed only for "safe"
* (i.e. non-truncating) conversion. In such cases an explicit cast have to be used.
* (i.e. non-truncating) conversion. In truncating cases an explicit cast have to be used.
*
* This cast gets only representation to cast to. For example:
*
* auto q1 = units::quantity_cast<int>(1_q_ms);
* auto q = quantity_cast<int>(1.23 * isq::time[ms]);
*
* @tparam ToRep a representation type to use for a target quantity
*/
+518
View File
@@ -0,0 +1,518 @@
// 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/bits/algorithm.h>
#include <units/bits/expression_template.h>
#include <units/bits/external/type_name.h>
#include <units/bits/external/type_traits.h>
#include <units/dimension.h>
#include <units/unit.h>
#include <tuple>
namespace units {
/**
* @brief Quantity character
*
* Scalars, vectors and tensors are mathematical objects that can be used to
* denote certain physical quantities and their values. They are as such
* independent of the particular choice of a coordinate system, whereas
* each scalar component of a vector or a tensor and each component vector and
* component tensor depend on that choice.
*
* A scalar is a physical quantity that has magnitude but no direction.
*
* Vectors are physical quantities that possess both magnitude and direction
* and whose operations obey the axioms of a vector space.
*
* Tensors can be used to describe more general physical quantities.
* For example, the Cauchy stress tensor possess magnitude, direction,
* and orientation qualities.
*/
enum class quantity_character { scalar, vector, tensor };
namespace detail {
// TODO revise the note in the below comment
/**
* @brief Returns the most restrictive character from the list
*
* @note `vector * vector` returns vector (not tensor)
*/
template<std::same_as<quantity_character>... Ts>
[[nodiscard]] consteval quantity_character common_quantity_character(Ts... args)
{
return detail::max({args...});
}
template<typename... Qs1, typename... Qs2>
[[nodiscard]] consteval quantity_character derived_quantity_character(const type_list<Qs1...>&,
const type_list<Qs2...>&)
{
quantity_character num = common_quantity_character(quantity_character::scalar, expr_type<Qs1>::character...);
quantity_character den = common_quantity_character(quantity_character::scalar, expr_type<Qs2>::character...);
return common_quantity_character(num, den);
}
/**
* @brief Initializes quantity character
*
* If a quantity character value is present in template parameters, this value will be used.
* Otherwise, an inherited/derived value provided through the function argument is returned.
*/
template<auto... Args>
[[nodiscard]] consteval quantity_character quantity_character_init(quantity_character ch)
{
if constexpr (one_of<quantity_character, std::remove_const_t<decltype(Args)>...>)
return std::get<quantity_character>(std::make_tuple(Args...));
else
return ch;
}
template<typename T>
inline constexpr bool is_specialization_of_derived_quantity_spec = false;
template<typename T>
inline constexpr bool is_dimensionless = false;
template<typename T>
inline constexpr bool is_power_of_quantity_spec = requires {
requires is_specialization_of_power<T> &&
(NamedQuantitySpec<typename T::factor> || is_dimensionless<typename T::factor>);
};
template<typename T>
inline constexpr bool is_per_of_quantity_specs = false;
template<typename... Ts>
inline constexpr bool is_per_of_quantity_specs<per<Ts...>> =
(... && (NamedQuantitySpec<Ts> || is_dimensionless<Ts> || is_power_of_quantity_spec<Ts>));
} // namespace detail
/**
* @brief Concept matching quantity specification types
*
* Satisfied by all `derived_quantity_spec` specializations.
*/
template<typename T>
concept DerivedQuantitySpec = detail::is_specialization_of_derived_quantity_spec<T>;
template<typename T>
concept QuantitySpec = NamedQuantitySpec<T> || DerivedQuantitySpec<T>;
template<typename T>
concept DerivedQuantitySpecExpr = NamedQuantitySpec<T> || detail::is_dimensionless<T> ||
detail::is_power_of_quantity_spec<T> || detail::is_per_of_quantity_specs<T>;
namespace detail {
template<NamedQuantitySpec Lhs, NamedQuantitySpec Rhs>
struct quantity_spec_less : std::bool_constant<type_name<Lhs>() < type_name<Rhs>()> {};
template<typename T1, typename T2>
using type_list_of_quantity_spec_less = expr_less<T1, T2, quantity_spec_less>;
template<NamedQuantitySpec Q>
requires requires { Q::dimension; }
using to_dimension = std::remove_const_t<decltype(Q::dimension)>;
template<Unit U>
requires requires { U::base_quantity.dimension; }
using to_base_dimension = std::remove_const_t<decltype(U::base_quantity.dimension)>;
template<Unit U>
[[nodiscard]] consteval Dimension auto get_dimension_for_impl(U)
requires requires { U::base_quantity.dimension; }
{
return U::base_quantity.dimension;
}
template<typename... Us>
[[nodiscard]] consteval Dimension auto get_dimension_for_impl(const derived_unit<Us...>& u)
requires detail::expr_projectable<derived_unit<Us...>, to_base_dimension>
{
return detail::expr_map<to_base_dimension, derived_dimension, struct dimension_one,
detail::type_list_of_base_dimension_less>(u);
}
template<typename U>
concept associated_unit = Unit<U> && requires(U u) { get_dimension_for_impl(get_canonical_unit(u).reference_unit); };
[[nodiscard]] consteval Dimension auto get_dimension_for(associated_unit auto u)
{
return get_dimension_for_impl(get_canonical_unit(u).reference_unit);
}
} // namespace detail
template<QuantitySpec auto Q, Unit auto U>
struct reference;
/**
* @brief A specification of a derived quantity
*
* Derived quantity is a quantity, in a system of quantities, defined in terms of other quantities of that system.
* Its 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 both for quantity specification and its dimension. 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<Dim, Num, Den>` class template. Otherwise, it is
* just put directly in the list without any wrapper. In case all of the exponents are negative than the
* `dimensionless`/`dimension_one` is put in the front to increase the readability.
*
* For example:
*
* @code{.cpp}
* auto frequency = 1 / period_duration;
* auto area = pow<2>(length);
* auto speed = distance / duration;
* auto velocity = position_vector / duration;
* auto acceleration = velocity / duration;
* @endcode
*
* - the type of `frequency` is `derived_quantity_spec<dimensionless, per<period_duration>>`
* - the dimension type of `frequency` is `derived_dimension<dimension_one, per<dim_time>>`
* - the type of `area` is `derived_quantity_spec<power<length, 2>>`
* - the dimension type of `area` is `derived_dimension<power<dim_length, 2>>`
* - the type of `speed` is `derived_quantity_spec<distance, per<duration>>`
* - the dimension type of `speed` is `derived_dimension<dim_length, per<dim_time>>`
* - the type of `velocity` is `derived_quantity_spec<position_vector, per<duration>>`
* - the dimension type of `velocity` is `derived_dimension<dim_length, per<dim_time>>`
* - the type of `acceleration` is `derived_quantity_spec<velocity, per<duration>>`
* - the dimension type of `acceleration` is `derived_dimension<dim_length, per<power<dim_time, 2>>>`
*
* @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.
*
* Derived quantity specification will have a character being the strongest ones from its ingredients.
*
* Binding a proper unit to a quantity specification via an indexing operator (`operator[]`) results
* in a quantity reference.
*
* @tparam Qs a parameter pack consisting tokens allowed in the quantity specification
* (named quantity specification, `dimensionless`, `power<Q, Num, Den>`, `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<DerivedQuantitySpecExpr... Qs>
struct derived_quantity_spec : detail::expr_fractions<derived_quantity_spec<>, Qs...> {
using base = detail::expr_fractions<derived_quantity_spec<>, Qs...>;
static constexpr Dimension auto dimension =
detail::expr_map<detail::to_dimension, derived_dimension, struct dimension_one,
detail::type_list_of_base_dimension_less>(base{});
static constexpr quantity_character character =
detail::derived_quantity_character(typename base::_num_{}, typename base::_num_{});
#ifdef __cpp_explicit_this_parameter
template<typename Self, Unit U>
[[nodiscard]] constexpr auto operator[](this const Self, U u)
requires(dimension == detail::get_dimension_for(u))
{
return reference<Self{}, u>{};
}
#else
template<Unit U>
[[nodiscard]] constexpr auto operator[](U u) const
requires(dimension == detail::get_dimension_for(u))
{
return reference<derived_quantity_spec{}, u>{};
}
#endif
};
namespace detail {
template<typename... Args>
inline constexpr bool is_specialization_of_derived_quantity_spec<derived_quantity_spec<Args...>> = true;
}
/**
* @brief Quantity Specification
*
* This type specifies all the properties of a quantity and allow modeling most of the quantities in the ISO 80000.
* It serves to define base and derived quantities as well as quantity kinds. Each quantity specification
* provides an information on how this quantity relates to other quantities, specifies its dimension and character.
*
* Quantity character can be derived from other quantities or explicitly overriden through a template parameter.
*
* Binding a proper unit to a quantity specification via an indexing operator (`operator[]`) results
* in a quantity reference.
*/
#ifdef __cpp_explicit_this_parameter
template<auto...>
#else
template<typename, auto...>
#endif
struct quantity_spec;
/**
* @brief Specialization defining 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.
*
* Base quantities have scalar character by default.
*
* 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 dim_length : base_dimension<"L"> {} dim_length;
* inline constexpr struct dim_mass : base_dimension<"M"> {} dim_mass;
* inline constexpr struct dim_time : base_dimension<"T"> {} dim_time;
*
* inline constexpr struct length : quantity_spec<dim_length> {} length;
* inline constexpr struct mass : quantity_spec<dim_mass> {} mass;
* inline constexpr struct time : quantity_spec<dim_time> {} time;
* @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 BaseDimension base dimension for which a base quantity is being defined
* @tparam Args optionally a value of a `quantity_character` in case the base quantity should not be scalar
*/
#ifdef __cpp_explicit_this_parameter
template<BaseDimension auto Dim, one_of<quantity_character> auto... Args>
struct quantity_spec<Dim, Args...> {
#else
template<typename Self, BaseDimension auto Dim, one_of<quantity_character> auto... Args>
struct quantity_spec<Self, Dim, Args...> {
#endif
static constexpr BaseDimension auto dimension = Dim;
static constexpr quantity_character character = detail::quantity_character_init<Args...>(quantity_character::scalar);
#ifdef __cpp_explicit_this_parameter
template<typename Self, Unit U>
[[nodiscard]] constexpr auto operator[](this const Self, U u)
requires(dimension == detail::get_dimension_for(u))
#else
template<Unit U>
[[nodiscard]] constexpr auto operator[](U u) const
requires(dimension == detail::get_dimension_for(u))
#endif
{
return reference<Self{}, u>{};
}
};
/**
* @brief Specialization defining a named derived quantity or a quantity kind
*
* The division of the concept "quantity" into several kinds is to some extent arbitrary.
* For example the quantities diameter, circumference, and wavelength are generally considered
* to be quantities of the same kind, namely, of the kind of quantity called length.
*
* Quantities of the same kind within a given system of quantities have the same quantity dimension.
* However, quantities of the same dimension are not necessarily of the same kind.
*
* The character of those quantities is derived from ingredients or overriden with a template parameter.
*
* User should derive a strong type from this class template rather than use it directly in the source code.
* For example:
*
* @code{.cpp}
* // quantity kinds
* inline constexpr struct width : quantity_spec<length> {} width;
* inline constexpr struct height : quantity_spec<length> {} height;
* inline constexpr struct thickness : quantity_spec<width> {} thickness;
* inline constexpr struct diameter : quantity_spec<width> {} diameter;
* inline constexpr struct position_vector : quantity_spec<length, quantity_character::vector> {} position_vector;
*
* // derived quantities
* inline constexpr struct area : quantity_spec<pow<2>(length)> {} area;
* inline constexpr struct volume : quantity_spec<pow<3>(length)> {} volume;
* inline constexpr struct velocity : quantity_spec<position_vector / duration> {} velocity; // vector
* inline constexpr struct speed : quantity_spec<distance / duration> {} speed;
* inline constexpr struct force : quantity_spec<mass * acceleration, quantity_character::vector> {} force;
* inline constexpr struct power : quantity_spec<force * velocity, quantity_character::scalar> {} power;
* @endcode
*
* Two quantity specifications are deemed equal when they are of the same type. With that, both strong
* types `speed` and `velocity` are considered not equal to `derived_dimension<length, per<time>>` or
* to each other. However, they are both convertible to `derived_dimension<length, per<time>>`.
* User can implicitly convert up and down the inheritance hierarchy between them. However, `speed` and
* `velocity` are not convertible as they do not inherit from each other. They are from two separate branches
* of dimensionally equivalent quantities.
*
* @tparam Q quantity specification of a parent quantity
* @tparam Args optionally a value of a `quantity_character` in case the base quantity should not be scalar
*/
#ifdef __cpp_explicit_this_parameter
template<QuantitySpec auto Q, one_of<quantity_character> auto... Args>
struct quantity_spec<Q, Args...> : std::remove_const_t<decltype(Q)> {
#else
template<typename Self, QuantitySpec auto Q, one_of<quantity_character> auto... Args>
struct quantity_spec<Self, Q, Args...> : std::remove_const_t<decltype(Q)> {
#endif
static constexpr auto kind_of = Q;
static constexpr quantity_character character = detail::quantity_character_init<Args...>(Q.character);
#ifndef __cpp_explicit_this_parameter
template<Unit U>
[[nodiscard]] constexpr auto operator[](U u) const
requires(this->dimension == detail::get_dimension_for(u))
{
return reference<Self{}, u>{};
}
#endif
};
#ifdef __cpp_explicit_this_parameter
#define QUANTITY_SPEC(name, ...) \
inline constexpr struct name : quantity_spec<##__VA_ARGS__> { \
} name
#else
#define QUANTITY_SPEC(name, ...) \
inline constexpr struct name : quantity_spec<name, ##__VA_ARGS__> { \
} name
#endif
// TODO Should a quantity_cast be allowed to cast between speed and velocity?
/**
* @brief Quantity of dimension one
*
* Quantity of dimension one also commonly named as "dimensionless" is a quantity with a dimension
* for which all the exponents of the factors corresponding to the base dimensions are zero.
*/
QUANTITY_SPEC(dimensionless, derived_quantity_spec<>{});
namespace detail {
template<>
inline constexpr bool is_dimensionless<struct dimensionless> = true;
} // namespace detail
// Operators
template<QuantitySpec Lhs, QuantitySpec Rhs>
[[nodiscard]] consteval QuantitySpec auto operator*(Lhs lhs, Rhs rhs)
{
return detail::expr_multiply<derived_quantity_spec, struct dimensionless, detail::type_list_of_quantity_spec_less>(
lhs, rhs);
}
template<QuantitySpec Lhs, QuantitySpec Rhs>
[[nodiscard]] consteval QuantitySpec auto operator/(Lhs lhs, Rhs rhs)
{
return detail::expr_divide<derived_quantity_spec, struct dimensionless, detail::type_list_of_quantity_spec_less>(lhs,
rhs);
}
template<QuantitySpec Q>
[[nodiscard]] consteval QuantitySpec auto operator/(int value, Q q)
{
gsl_Expects(value == 1);
return detail::expr_invert<derived_quantity_spec, struct dimensionless>(q);
}
template<QuantitySpec Q>
[[nodiscard]] consteval QuantitySpec auto operator/(Q, int) = delete;
template<QuantitySpec Lhs, QuantitySpec Rhs>
[[nodiscard]] consteval bool operator==(Lhs, Rhs)
{
return is_same_v<Lhs, Rhs>;
}
template<QuantitySpec Q1, QuantitySpec Q2>
[[nodiscard]] consteval bool interconvertible(Q1, Q2)
{
if constexpr (NamedQuantitySpec<Q1> && NamedQuantitySpec<Q2>)
// check if quantity kinds are convertible (across one hierarchy)
return std::derived_from<Q1, Q2> || std::derived_from<Q2, Q1>;
else {
// check weather the quantity spec's dimension is the same
// TODO Can we improve that to account for quantity kinds (i.e. `altitude` / `time` -> `sink_rate`)
return Q1::dimension == Q2::dimension;
}
}
[[nodiscard]] consteval auto common_quantity_spec(QuantitySpec auto q) { return q; }
template<QuantitySpec Q1, QuantitySpec Q2>
[[nodiscard]] consteval auto common_quantity_spec(Q1 q1, Q2 q2)
requires(interconvertible(q1, q2))
{
if constexpr (std::derived_from<Q1, Q2>)
return q1;
else if constexpr (std::derived_from<Q2, Q1>)
return q2;
else if constexpr (NamedQuantitySpec<Q1>)
return q1;
else
return q2;
}
[[nodiscard]] consteval auto common_quantity_spec(QuantitySpec auto q1, QuantitySpec auto q2, QuantitySpec auto q3,
QuantitySpec auto... rest)
requires requires { common_quantity_spec(common_quantity_spec(q1, q2), q3, rest...); }
{
return common_quantity_spec(common_quantity_spec(q1, q2), q3, rest...);
}
/**
* @brief Computes the value of a quantity specification raised to the `Num/Den` power
*
* @tparam Num Exponent numerator
* @tparam Den Exponent denominator
* @param q Quantity specification being the base of the operation
*
* @return QuantitySpec The result of computation
*/
template<std::intmax_t Num, std::intmax_t Den = 1, QuantitySpec Q>
requires detail::non_zero<Den>
[[nodiscard]] consteval QuantitySpec auto pow(Q q)
{
if constexpr (BaseQuantitySpec<Q>) {
if constexpr (Den == 1)
return derived_quantity_spec<power<Q, Num>>{};
else
return derived_quantity_spec<power<Q, Num, Den>>{};
} else
return detail::expr_pow<Num, Den, derived_quantity_spec, struct dimensionless,
detail::type_list_of_quantity_spec_less>(q);
}
} // namespace units
+23 -43
View File
@@ -23,72 +23,52 @@
#pragma once
#include <units/concepts.h>
#include <units/dimension.h>
#include <units/quantity_spec.h>
#include <units/unit.h>
namespace units {
/**
* @brief The type for quantity references
* @brief Quantity reference type
*
* This type is intended to be used in the quantity references definition:
* Quantity reference describes all the properties of a quantity besides its
* representation type.
*
* In most cases this class template is not explicitly instantiated by the user.
* It is implicitly instantiated by the library's framework while binding a quantity
* specification with a compatible unit.
*
* @code{.cpp}
* namespace length_references {
*
* inline constexpr auto m = reference<dim_length, metre>{};
* inline constexpr auto km = reference<dim_length, kilometre>{};
*
* }
*
* namespace references {
*
* using namespace length_references;
*
* }
* @endcode
*
* Quantity references simplify quantity creation:
*
* @code{.cpp}
* using namespace units::isq::si::references;
*
* auto d = 123 * m;
* auto v = 70 * (km / h);
* @endcode
*
* Also, it is allowed to define custom quantity references from existing ones:
*
* @code{.cpp}
* constexpr auto Nm = N * m;
* constexpr auto mph = mi / h;
* Reference auto kmph = isq::speed[km / h];
* quantity_of<isq::speed[km / h]> auto speed = 90 * kmph;
* @endcode
*
* The following syntaxes are not allowed:
* `2 / s`, `km * 3`, `s / 4`, `70 * km / h`.
* `2 / kmph`, `kmph * 3`, `kmph / 4`, `70 * isq::length[km] / isq:time[h]`.
*/
template<Dimension auto D, Unit auto U>
template<QuantitySpec auto Q, Unit auto U>
struct reference {
static constexpr auto dimension = D;
static constexpr auto unit = U;
static constexpr QuantitySpec auto quantity_spec = Q;
static constexpr Dimension auto dimension = Q.dimension;
static constexpr Unit auto unit = U;
};
// Reference
template<Magnitude M, Reference R>
[[nodiscard]] consteval reference<R::dimension, M{} * R::unit> operator*(M, R)
[[nodiscard]] consteval reference<R::quantity_spec, M{} * R::unit> operator*(M, R)
{
return {};
}
template<Reference R1, Reference R2>
[[nodiscard]] consteval reference<R1::dimension * R2::dimension, R1::unit * R2::unit> operator*(R1, R2)
[[nodiscard]] consteval reference<R1::quantity_spec * R2::quantity_spec, R1::unit * R2::unit> operator*(R1, R2)
{
return {};
}
template<Reference R1, Reference R2>
[[nodiscard]] consteval reference<R1::dimension / R2::dimension, R1::unit / R2::unit> operator/(R1, R2)
[[nodiscard]] consteval reference<R1::quantity_spec / R2::quantity_spec, R1::unit / R2::unit> operator/(R1, R2)
{
return {};
}
@@ -104,26 +84,26 @@ void /*Use `q * (1 * r)` rather than `q * r`.*/ operator*(Quantity auto, Referen
template<Reference R1, Reference R2>
[[nodiscard]] consteval bool operator==(R1, R2)
{
return R1::dimension == R2::dimension && R1::unit == R2::unit;
return R1::quantity_spec == R2::quantity_spec && R1::unit == R2::unit;
}
template<Reference R1, Reference R2>
[[nodiscard]] consteval bool interconvertible(R1, R2)
{
return interconvertible(R1::dimension, R2::dimension) && interconvertible(R1::unit, R2::unit);
return interconvertible(R1::quantity_spec, R2::quantity_spec) && interconvertible(R1::unit, R2::unit);
}
[[nodiscard]] consteval auto common_reference(Reference auto r1, Reference auto r2, Reference auto... rest)
requires requires {
{
common_dimension(r1.dimension, r2.dimension, rest.dimension...)
} -> Dimension;
common_quantity_spec(r1.quantity_spec, r2.quantity_spec, rest.quantity_spec...)
} -> QuantitySpec;
{
common_unit(r1.unit, r2.unit, rest.unit...)
} -> Unit;
}
{
return reference<common_dimension(r1.dimension, r2.dimension, rest.dimension...),
return reference<common_quantity_spec(r1.quantity_spec, r2.quantity_spec, rest.quantity_spec...),
common_unit(r1.unit, r2.unit, rest.unit...)>{};
}
+32 -4
View File
@@ -23,20 +23,48 @@
#pragma once
#include <units/concepts.h>
#include <units/quantity_spec.h>
#include <units/reference.h>
#include <units/unit.h>
namespace units {
template<Dimension auto Dim, Unit auto CoU>
requires(!detail::associated_unit<std::remove_const_t<decltype(CoU)>>)
/**
* @brief System-specific reference
*
* This type is used in rare cases where more than one base quantity in a specific
* system of units uses the same unit. For example in a hypothetical system of natural units
* where constant for speed of light `c = 1`, length and time could be measured in seconds.
* In such cases `system_reference` has to be used to explicitly express such a binding.
*
* For example:
*
* @code{.cpp}
* // hypothetical natural system of units for c=1
*
* inline constexpr struct second : named_unit<"s"> {} second;
* inline constexpr struct minute : named_unit<"min", mag<60> * second> {} minute;
* inline constexpr struct gram : named_unit<"g"> {} gram;
* inline constexpr struct kilogram : decltype(si::kilo<gram>) {} kilogram;
*
* inline constexpr struct time : system_reference<isq::time, second> {} time;
* inline constexpr struct length : system_reference<isq::length, second> {} length;
* inline constexpr struct speed : system_reference<isq::speed, second / second> {} speed;
* inline constexpr struct force : system_reference<isq::force, kilogram / second> {} force;
* @endcode
*
* @tparam Q quantity for which a unit is being assigned
* @tparam CoU coherent unit for a quantity in this system
*/
template<QuantitySpec auto Q, Unit auto CoU>
requires(!detail::associated_unit<std::remove_const_t<decltype(CoU)>>) || (CoU == one)
struct system_reference {
static constexpr auto dimension = Dim;
static constexpr auto quantity_spec = Q;
static constexpr auto coherent_unit = CoU;
template<Unit U>
requires(interconvertible(coherent_unit, U{}))
[[nodiscard]] constexpr reference<dimension, U{}> operator[](U) const
[[nodiscard]] constexpr reference<quantity_spec, U{}> operator[](U) const
{
return {};
}
+50 -34
View File
@@ -28,6 +28,7 @@
#include <units/bits/external/text_tools.h>
#include <units/bits/external/type_name.h>
#include <units/bits/external/type_traits.h>
#include <units/dimension.h>
#include <units/magnitude.h>
#include <units/ratio.h>
#include <units/symbol_text.h>
@@ -37,43 +38,57 @@
namespace units {
#ifdef __cpp_explicit_this_parameter
template<basic_fixed_string Symbol>
template<auto...>
#else
template<typename Self, basic_fixed_string Symbol>
template<typename, auto...>
#endif
struct base_dimension;
struct quantity_spec;
namespace detail {
#ifdef __cpp_explicit_this_parameter
template<basic_fixed_string Symbol>
void to_base_base_dimension(const volatile base_dimension<Symbol>*);
template<auto... Args>
void to_base_specialization_of_quantity_spec(const volatile quantity_spec<Args...>*);
#else
template<typename Self, basic_fixed_string Symbol>
void to_base_base_dimension(const volatile base_dimension<Self, Symbol>*);
template<typename T, auto... Args>
void to_base_specialization_of_quantity_spec(const volatile quantity_spec<T, Args...>*);
#endif
#ifdef __cpp_explicit_this_parameter
template<BaseDimension auto Dim, auto... Args>
template<auto... Args>
void to_base_specialization_of_base_quantity_spec(const volatile quantity_spec<Dim, Args...>*);
#else
template<typename Self, BaseDimension auto Dim, auto... Args>
void to_base_specialization_of_base_quantity_spec(const volatile quantity_spec<Self, Dim, Args...>*);
#endif
template<typename T>
inline constexpr bool is_specialization_of_base_dimension = false;
inline constexpr bool is_specialization_of_quantity_spec = false;
#ifdef __cpp_explicit_this_parameter
template<basic_fixed_string Symbol>
inline constexpr bool is_specialization_of_base_dimension<base_dimension<Symbol>> = true;
template<auto... Args>
inline constexpr bool is_specialization_of_quantity_spec<quantity_spec<Args...>> = true;
#else
template<typename Self, basic_fixed_string Symbol>
inline constexpr bool is_specialization_of_base_dimension<base_dimension<Self, Symbol>> = true;
template<typename T, auto... Args>
inline constexpr bool is_specialization_of_quantity_spec<quantity_spec<T, Args...>> = true;
#endif
} // namespace detail
/**
* @brief A concept matching all named base dimensions in the library.
* @brief Concept matching quantity specification types
*
* Satisfied by all dimension types derived from a specialization of `base_dimension`.
* Satisfied by all types that derive from `quantity_spec`.
*/
template<typename T>
concept BaseDimension = requires(T* t) { detail::to_base_base_dimension(t); } &&
(!detail::is_specialization_of_base_dimension<T>);
concept NamedQuantitySpec = requires(T* t) { detail::to_base_specialization_of_quantity_spec(t); } &&
(!detail::is_specialization_of_quantity_spec<T>);
template<typename T>
concept BaseQuantitySpec =
NamedQuantitySpec<T> && requires(T* t) { detail::to_base_specialization_of_base_quantity_spec(t); };
namespace detail {
@@ -146,12 +161,12 @@ template<basic_symbol_text Symbol, auto...>
struct named_unit;
/**
* @brief Specialization for unit of a specified base dimension
* @brief Specialization for unit of a specified base quantity
*
* Associates a unit with a specified base dimension.
* Associates a unit with a specified base quantity.
* For example `si::metre` is a unit to measure `isq::length` in the SI system.
*
* @note This is the preferred way to define a measurement unit for a specific base dimension.
* @note This is the preferred way to define a measurement unit for a specific base quantity.
*
* @note It does not have to (or sometimes even can't) be a proper system's base unit. For example
* a base unit of mass in the SI is `si::kilogram` but here you are about to provide an `si::gram`
@@ -159,19 +174,19 @@ struct named_unit;
* the `cgs::centimetre` that is a base unit for `isq::length` in the CGS system.
*
* @tparam Symbol a short text representation of the unit
* @tparam BaseDimension base dimension measured with this unit
* @tparam BaseQuantitySpec base quantity measured with this unit
*/
template<basic_symbol_text Symbol, BaseDimension auto D>
template<basic_symbol_text Symbol, BaseQuantitySpec auto Q>
requires(!Symbol.empty())
struct named_unit<Symbol, D> {
struct named_unit<Symbol, Q> {
static constexpr auto symbol = Symbol; ///< Unique base unit identifier
static constexpr auto base_dimension = D;
static constexpr auto base_quantity = Q;
};
/**
* @brief Specialization for a unit that can be reused by several base dimensions
* @brief Specialization for a unit that can be reused by several base quantities
*
* This specialization is used in rare cases where more than one base dimension in a specific
* This specialization is used in rare cases where more than one base quantity in a specific
* system of units uses the same unit. For example in a hypothetical system of natural units
* where constant for speed of light `c = 1`, length and time could be measured in seconds.
* In such cases `system_reference` has to be used to explicitly express such a binding.
@@ -328,7 +343,7 @@ inline constexpr bool is_per_of_units<per<Ts...>> = (... && (Unit<Ts> || is_powe
} // namespace detail
template<typename T>
concept DerivedUnitSpec = Unit<T> || detail::is_power_of_unit<T> || detail::is_per_of_units<T>;
concept DerivedUnitExpr = Unit<T> || detail::is_power_of_unit<T> || detail::is_per_of_units<T>;
/**
* @brief Measurement unit for a derived quantity
@@ -375,7 +390,7 @@ concept DerivedUnitSpec = Unit<T> || detail::is_power_of_unit<T> || detail::is_p
* @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 unit arithmetic equation provided by the user.
*/
template<DerivedUnitSpec... Us>
template<DerivedUnitExpr... Us>
struct derived_unit : detail::expr_fractions<derived_unit<>, Us...> {};
/**
@@ -424,8 +439,8 @@ struct canonical_unit {
U reference_unit;
};
template<Unit T, basic_symbol_text Symbol, BaseDimension auto D>
[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol, D>&);
template<Unit T, basic_symbol_text Symbol, BaseQuantitySpec auto Q>
[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol, Q>&);
template<Unit T, basic_symbol_text Symbol>
[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol>&);
@@ -446,8 +461,8 @@ template<Unit T, auto M, typename U>
return canonical_unit{M * base.mag, base.reference_unit};
}
template<Unit T, basic_symbol_text Symbol, BaseDimension auto D>
[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol, D>&)
template<Unit T, basic_symbol_text Symbol, BaseQuantitySpec auto Q>
[[nodiscard]] consteval auto get_canonical_unit_impl(T t, const named_unit<Symbol, Q>&)
{
return canonical_unit{mag<1>, t};
}
@@ -498,6 +513,7 @@ template<Unit Lhs, Unit Rhs>
(!is_derived_from_specialization_of_constant_unit<Lhs> && !is_derived_from_specialization_of_constant_unit<Rhs>))
return type_name<Lhs>() < type_name<Rhs>();
else
// put constants at the front of units list in the expression
return is_derived_from_specialization_of_constant_unit<Lhs>;
}
@@ -804,21 +820,21 @@ constexpr auto unit_symbol_impl(Out out, const power<F, Num, Den...>&, unit_symb
}
}
template<typename CharT, std::output_iterator<CharT> Out, DerivedUnitSpec M>
template<typename CharT, std::output_iterator<CharT> Out, DerivedUnitExpr M>
constexpr Out unit_symbol_impl(Out out, M m, std::size_t Idx, unit_symbol_formatting fmt, bool negative_power)
{
if (Idx > 0) out = print_separator<CharT>(out, fmt);
return unit_symbol_impl<CharT>(out, m, fmt, negative_power);
}
template<typename CharT, std::output_iterator<CharT> Out, DerivedUnitSpec... Ms, std::size_t... Idxs>
template<typename CharT, std::output_iterator<CharT> Out, DerivedUnitExpr... Ms, std::size_t... Idxs>
constexpr Out unit_symbol_impl(Out out, const type_list<Ms...>&, std::index_sequence<Idxs...>,
unit_symbol_formatting fmt, bool negative_power)
{
return (..., (out = unit_symbol_impl<CharT>(out, Ms{}, Idxs, fmt, negative_power)));
}
template<typename CharT, std::output_iterator<CharT> Out, DerivedUnitSpec... Nums, DerivedUnitSpec... Dens>
template<typename CharT, std::output_iterator<CharT> Out, DerivedUnitExpr... Nums, DerivedUnitExpr... Dens>
constexpr Out unit_symbol_impl(Out out, const type_list<Nums...>& nums, const type_list<Dens...>& dens,
unit_symbol_formatting fmt)
{