refactor: units nearly done

This commit is contained in:
Mateusz Pusz
2022-10-18 21:24:09 +02:00
parent 6e8ca72678
commit a5c7934e0e
5 changed files with 565 additions and 434 deletions

View File

@@ -22,16 +22,6 @@
#include <units/si/si.h>
template<typename T>
consteval bool print();
template<typename T, typename Expr>
constexpr bool is_of_type(Expr)
{
return std::is_same_v<Expr, T>;
}
namespace {
using namespace units;
@@ -42,6 +32,18 @@ inline constexpr struct activity_dim : decltype(1 / isq::time_dim) {} activity_d
inline constexpr struct activity : system_reference<activity_dim, si::becquerel> {} activity;
// clang-format on
// check for invalid prefixes
template<Unit auto V1>
concept can_not_be_prefixed = !requires { typename si::milli_<V1>; };
static_assert(can_not_be_prefixed<si::degree_Celsius>);
static_assert(can_not_be_prefixed<si::minute>);
static_assert(can_not_be_prefixed<si::hour>);
static_assert(can_not_be_prefixed<si::day>);
static_assert(can_not_be_prefixed<si::kilogram>);
static_assert(can_not_be_prefixed<si::hectare>);
static_assert(can_not_be_prefixed<si::metre / si::second>);
// Named quantity/dimension and unit
static_assert(
@@ -68,24 +70,28 @@ static_assert(is_same_v<decltype(20 * si::speed[m / s] / (10 * si::length[m]) *
// Comparisons
// Same dimension type & different unit
// static_assert(1000 * si::length[m] == 1 * si::length[km]);
// Named and derived dimensions (same units)
static_assert(10 * si::length[m] / (2 * si::time[s]) == 5 * si::speed[m / s]);
static_assert(5 * si::speed[m / s] == 10 * si::length[m] / (2 * si::time[s]));
// Named and derived dimensions (different units)
// Same named dimension & different but equivalent unit
static_assert(10 * si::frequency[1 / s] == 10 * si::frequency[Hz]);
static_assert(10 * si::frequency[Hz] == 10 * si::frequency[1 / s]);
// Named and derived dimensions (different but equivalent units)
static_assert(10 / (2 * si::time[s]) == 5 * si::frequency[Hz]);
static_assert(5 * si::frequency[Hz] == 10 / (2 * si::time[s]));
static_assert(5 * si::force[N] * (2 * si::length[m]) == 10 * si::energy[J]);
static_assert(10 * si::energy[J] == 5 * si::force[N] * (2 * si::length[m]));
// Different named dimensions
template<Reference auto R1, Reference auto R2>
concept invalid_comparison = requires {
requires !requires { 2 * R1 == 2 * R2; };
requires !requires { 2 * R2 == 2 * R1; };
};
concept invalid_comparison = !requires { 2 * R1 == 2 * R2; } && !requires { 2 * R2 == 2 * R1; };
static_assert(invalid_comparison<activity[Bq], si::frequency[Hz]>);
// static_assert(print<decltype(10 * si::length[m] / (2 * si::time[s]) + 5 * si::speed[m / s])>());
// Arithmetics
// Named and derived dimensions (same units)
@@ -124,14 +130,28 @@ static_assert(is_same_v<decltype(5 * si::frequency[Hz] - 10 / (2 * si::time[s]))
template<typename... Ts>
consteval bool invalid_arithmetic(Ts... ts)
{
return requires {
requires !requires { (... + ts); };
requires !requires { (... - ts); };
};
return !requires { (... + ts); } && !requires { (... - ts); };
}
static_assert(invalid_arithmetic(5 * activity[Bq], 5 * si::frequency[Hz]));
static_assert(invalid_arithmetic(5 * activity[Bq], 10 / (2 * si::time[s]), 5 * si::frequency[Hz]));
// Implicit conversions allowed between quantities of `convertible` references
constexpr quantity<si::speed[km / h]> speed = 120 * si::length[km] / (2 * si::time[h]);
// Explicit casts allow changing all or only a part of the type
static_assert(
std::is_same_v<
decltype(quantity_cast<isq::speed_dim>(120 * si::length[km] / (2 * si::time[h]))),
quantity<reference<struct isq::speed_dim,
derived_unit<std::remove_const_t<decltype(si::kilo<si::metre>)>, per<struct si::hour>>>{},
int>>);
auto q3 = quantity_cast<m / s>(120 * si::length[km] / (2 * si::time[h]));
auto q4 = quantity_cast<si::speed[m / s]>(120 * si::length[km] / (2 * si::time[h]));
auto q5 = quantity_cast<double>(120 * si::length[km] / (2 * si::time[h]));
auto q6 = quantity_cast<quantity<si::speed[m / s], double>>(120 * si::length[km] / (2 * si::time[h]));
// cast 1 / time_dim to use Hz
// static_assert(quantity_of<decltype(60 * si::speed[km / h]), isq::speed_dim>);
// static_assert(quantity_of<decltype(120 * si::length[km] / (2 * si::time[h])), isq::speed_dim>);
// static_assert(quantity_of<decltype(120 * si::length[km] / (2 * si::time[h])), si::speed[km / h]>);
@@ -160,105 +180,6 @@ static_assert(invalid_arithmetic(5 * activity[Bq], 10 / (2 * si::time[s]), 5 * s
} // namespace
namespace units::si {
// derived unit expression template syntax verification
static_assert(is_of_type<derived_unit<struct one, per<struct second>>>(1 / second));
static_assert(is_of_type<struct second>(1 / (1 / second)));
static_assert(is_of_type<struct second>(one * second));
static_assert(is_of_type<struct second>(second * one));
static_assert(is_of_type<derived_unit<struct one, per<struct second>>>(one * (1 / second)));
static_assert(is_of_type<derived_unit<struct one, per<struct second>>>(1 / second * one));
static_assert(is_of_type<derived_unit<struct metre, struct second>>(metre * second));
static_assert(is_of_type<derived_unit<units::power<struct metre, 2>>>(metre * metre));
static_assert(is_of_type<derived_unit<units::power<struct metre, 2>, struct second>>(metre * metre * second));
static_assert(is_of_type<derived_unit<units::power<struct metre, 2>, struct second>>(metre * second * metre));
static_assert(is_of_type<derived_unit<units::power<struct metre, 2>, struct second>>(metre * (second * metre)));
static_assert(is_of_type<derived_unit<units::power<struct metre, 2>, struct second>>(second * (metre * metre)));
static_assert(is_of_type<derived_unit<struct metre, per<struct second>>>(1 / second * metre));
static_assert(is_of_type<struct one>(1 / second * second));
static_assert(is_of_type<struct second>(second / one));
static_assert(is_of_type<derived_unit<struct one, per<struct second>>>(1 / second / one));
static_assert(is_of_type<struct metre>(metre / second * second));
static_assert(is_of_type<derived_unit<struct one, per<units::power<struct second, 2>>>>(1 / second * (1 / second)));
static_assert(is_of_type<derived_unit<struct one, per<units::power<struct second, 2>>>>(1 / (second * second)));
static_assert(is_of_type<derived_unit<units::power<struct second, 2>>>(1 / (1 / (second * second))));
static_assert(is_of_type<derived_unit<struct metre, per<units::power<struct second, 2>>>>(metre / second *
(1 / second)));
static_assert(is_of_type<derived_unit<units::power<struct metre, 2>, per<units::power<struct second, 2>>>>(
metre / second * (metre / second)));
static_assert(is_of_type<struct one>(metre / second * (second / metre)));
static_assert(is_of_type<derived_unit<struct watt, per<struct joule>>>(watt / joule));
static_assert(is_of_type<derived_unit<struct joule, per<struct watt>>>(joule / watt));
// comparisons of equivalent units
static_assert(metre / metre == one);
// static_assert(metre * metre == square_metre);
// static_assert(second * second == second_squared);
// static_assert(second * second * second == second_cubed);
// static_assert(second * (second * second) == second_cubed);
// static_assert(second_squared * second == second_cubed);
// static_assert(second * second_squared == second_cubed);
// static_assert(1 / second * metre == metre / second);
// static_assert(metre * (1 / second) == metre / second);
// static_assert((metre / second) * (1 / second) == metre / second / second);
// static_assert((metre / second) * (1 / second) == metre / (second * second));
// static_assert((metre / second) * (1 / second) == metre / second_squared);
// static_assert(hertz == 1 / second);
// static_assert(newton == kilogram * metre / second_squared);
// static_assert(joule == kilogram * square_metre / second_squared);
// static_assert(joule == newton * metre);
// static_assert(watt == joule / second);
// static_assert(watt == kilogram * square_metre / second_cubed);
// static_assert(1 / frequency_dim == second);
// static_assert(frequency_dim * second == one);
// static_assert(metre * metre == area_dim);
// static_assert(metre * metre != volume_dim);
// static_assert(area_dim / metre == metre);
// static_assert(metre * metre * metre == volume_dim);
// static_assert(area_dim * metre == volume_dim);
// static_assert(volume_dim / metre == area_dim);
// static_assert(volume_dim / metre / metre == metre);
// static_assert(area_dim * area_dim / metre == volume_dim);
// static_assert(area_dim * (area_dim / metre) == volume_dim);
// static_assert(volume_dim / (metre * metre) == metre);
// static_assert(metre / second == speed_dim);
// static_assert(metre * second != speed_dim);
// static_assert(metre / second / second != speed_dim);
// static_assert(metre / speed_dim == second);
// static_assert(speed_dim * second == metre);
// static_assert(metre / second / second == acceleration_dim);
// static_assert(metre / (second * second) == acceleration_dim);
// static_assert(speed_dim / second == acceleration_dim);
// static_assert(speed_dim / acceleration_dim == second);
// static_assert(acceleration_dim * second == speed_dim);
// static_assert(acceleration_dim * (second * second) == metre);
// static_assert(acceleration_dim / speed_dim == frequency_dim);
// Bq + Hz should not compile
// Bq + Hz + 1/s should compile?
} // namespace units::si
namespace units {
template<typename T, Dimension auto D, Unit auto U>
@@ -331,3 +252,6 @@ int main()
// type of Rep{1} * (mag<ratio(662'607'015, 100'000'000)> * mag_power<10, -34> * energy[joule] * time[second])
// and inline constexpr auto planck_constant = Rep{1} * mag_planck * energy[joule] * time[second];
// quantity_cast on equivalent dimensions

View File

@@ -38,50 +38,12 @@
namespace units {
// namespace detail {
// template<typename>
// inline constexpr bool can_be_prefixed = false;
// } // namespace detail
/**
* @brief A common point for a hierarchy of units
*
* A unit is an entity defined and adopted by convention, with which any other quantity of
* the same kind can be compared to express the ratio of the second quantity to the first
* one as a number.
*
* All units of the same dimension can be convereted between each other. To allow this all of
* them are expressed as different ratios of the same one proprietary chosen reference unit
* (i.e. all length units are expressed in terms of meter, all mass units are expressed in
* terms of gram, ...)
*
* @tparam M a Magnitude representing the (relative) size of this unit
* @tparam U a unit to use as a reference for this dimension
*
* @note U cannot be constrained with Unit as for some specializations (i.e. named_unit)
* it gets the incomplete child's type with the CRTP idiom.
*/
template<Magnitude auto M, typename U>
struct scaled_unit {
static constexpr UNITS_MSVC_WORKAROUND(Magnitude) auto mag = M;
using reference = U;
};
// TODO: Remove when P1985 accepted
namespace detail {
template<Magnitude auto M, typename U>
void to_base_scaled_unit(const volatile scaled_unit<M, U>*);
template<typename T>
inline constexpr bool is_specialization_of_scaled_unit = false;
inline constexpr bool is_unit = false;
template<Magnitude auto M, typename U>
inline constexpr bool is_specialization_of_scaled_unit<scaled_unit<M, U>> = true;
} // namespace detail
}
/**
* @brief A concept matching all unit types in the library
@@ -89,129 +51,12 @@ inline constexpr bool is_specialization_of_scaled_unit<scaled_unit<M, U>> = true
* Satisfied by all unit types derived from an specialization of :class:`scaled_unit`.
*/
template<typename T>
concept Unit = requires(T* t) { detail::to_base_scaled_unit(t); };
namespace detail {
template<typename>
inline constexpr bool is_named = false;
}
template<typename T>
concept NamedUnit = Unit<T> && detail::is_named<T>;
template<Unit auto U1, Unit auto U2>
struct same_unit_reference : is_same<typename decltype(U1)::reference, typename decltype(U2)::reference> {};
namespace detail {
template<Unit U1, Unit U2>
struct unit_less : std::bool_constant<type_name<U1>() < type_name<U2>()> {};
template<typename T1, typename T2>
using type_list_of_unit_less = expr_less<T1, T2, unit_less>;
/**
* @brief Unpacks the list of potentially derived dimensions to a list containing only base dimensions
*
* @tparam Es Exponents of potentially derived dimensions
*/
// template<typename NumList, typename DenList>
// struct unit_extract;
// template<>
// struct unit_extract<type_list<>, type_list<>> {
// using num = type_list<>;
// using den = type_list<>;
// };
// template<typename T, typename... NRest, typename... Dens>
// requires BaseUnit<T> || BaseUnit<typename T::factor>
// struct unit_extract<type_list<T, NRest...>, type_list<Dens...>> {
// using impl = unit_extract<type_list<NRest...>, type_list<Dens...>>;
// using num = type_list_push_front<typename impl::num, T>;
// using den = TYPENAME impl::den;
// };
// template<typename T, typename... DRest>
// requires BaseUnit<T> || BaseUnit<typename T::factor>
// struct unit_extract<type_list<>, type_list<T, DRest...>> {
// using impl = unit_extract<type_list<>, type_list<DRest...>>;
// using num = TYPENAME impl::num;
// using den = type_list_push_front<typename impl::den, T>;
// };
// template<DerivedUnit T, typename... NRest, typename... Dens>
// struct unit_extract<type_list<T, NRest...>, type_list<Dens...>> :
// unit_extract<type_list_push_back<typename T::normalized_num, NRest...>,
// type_list_push_back<typename T::normalized_den, Dens...>> {};
// template<DerivedUnit T, int... Ints, typename... NRest, typename... Dens>
// struct unit_extract<type_list<power<T, Ints...>, NRest...>, type_list<Dens...>> :
// unit_extract<type_list_push_back<typename expr_power<typename T::normalized_num, power<T, Ints...>::num,
// power<T, Ints...>::den>::type,
// NRest...>,
// type_list_push_back<typename expr_power<typename T::normalized_den, power<T, Ints...>::num,
// power<T, Ints...>::den>::type,
// Dens...>> {};
// template<DerivedUnit T, typename... DRest>
// struct unit_extract<type_list<>, type_list<T, DRest...>> :
// unit_extract<typename T::normalized_den, type_list_push_back<typename T::normalized_num, DRest...>> {};
// template<DerivedUnit T, int... Ints, typename... DRest>
// struct unit_extract<type_list<>, type_list<power<T, Ints...>, DRest...>> :
// unit_extract<typename expr_power<typename T::normalized_den, power<T, Ints...>::num, power<T,
// Ints...>::den>::type,
// type_list_push_back<typename expr_power<typename T::normalized_num, power<T, Ints...>::num,
// power<T, Ints...>::den>::type,
// DRest...>> {};
/**
* @brief Converts user provided derived dimension specification into a valid units::normalized_dimension definition
*
* User provided definition of a derived dimension may contain the same base dimension repeated more than once on the
* list possibly hidden in other derived units provided by the user. The process here should:
* 1. Extract derived dimensions into exponents of base dimensions.
* 2. Sort the exponents so the same dimensions are placed next to each other.
* 3. Consolidate contiguous range of exponents of the same base dimensions to a one (or possibly zero) exponent for
* this base dimension.
*/
template<typename OneTypeBase, typename... Us>
struct normalized_unit : detail::expr_fractions<OneTypeBase, Us...> {
// private:
// using base = detail::expr_fractions<OneTypeBase, Us...>;
// using extracted = unit_extract<typename base::num, typename base::den>;
// using num_list = expr_consolidate<type_list_sort<typename extracted::num, type_list_of_unit_less>>;
// using den_list = expr_consolidate<type_list_sort<typename extracted::den, type_list_of_unit_less>>;
// using simple = expr_simplify<num_list, den_list, type_list_of_unit_less>;
// public:
// using normalized_num = TYPENAME simple::num;
// using normalized_den = TYPENAME simple::den;
};
} // namespace detail
// TODO add checking for `per` and power elements as well
template<typename T>
concept UnitSpec = Unit<T> || is_specialization_of<T, per> || detail::is_specialization_of_power<T>;
concept Unit = detail::is_unit<T>;
// User should not instantiate this type!!!
// It should not be exported from the module
template<UnitSpec... Us>
struct derived_unit : detail::normalized_unit<derived_unit<>, Us...>, scaled_unit<mag<1>, derived_unit<Us...>> {
static constexpr bool is_base = false;
};
/**
* @brief Unit one
*
* Unit of a dimensionless quantity.
*/
inline constexpr struct one : derived_unit<> {
} one;
template<Magnitude auto M, Unit U>
struct scaled_unit {};
/**
* @brief A named unit
@@ -225,85 +70,186 @@ template<basic_symbol_text Symbol, auto...>
struct named_unit;
template<basic_symbol_text Symbol>
struct named_unit<Symbol> : scaled_unit<mag<1>, named_unit<Symbol>> {
struct named_unit<Symbol> {
static constexpr auto symbol = Symbol;
static constexpr bool is_base = true;
};
template<basic_symbol_text Symbol, Unit auto U>
struct named_unit<Symbol, U> : decltype(U) {
struct named_unit<Symbol, U> {
static constexpr auto symbol = Symbol;
static constexpr bool is_base = decltype(U)::is_base;
};
namespace detail {
template<basic_symbol_text Symbol, auto... Args>
void to_base_specialization_of_named_unit(const volatile named_unit<Symbol, Args...>*);
} // namespace detail
template<typename T>
concept NamedUnit = Unit<T> && requires(T* t) { detail::to_base_specialization_of_named_unit(t); };
template<Unit auto V>
inline constexpr bool unit_can_be_prefixed = NamedUnit<std::remove_const_t<decltype(V)>>;
/**
* @brief A prefixed unit
*
* Defines a new unit that is a scaled version of another unit by the provided prefix. It is
* only possible to create such a unit if the given prefix type matches the one defined in a
* reference unit.
* coherent_unit unit.
*
* @tparam Child inherited class type used by the downcasting facility (CRTP Idiom)
* @tparam P prefix to be appied to the reference unit
* @tparam U reference unit
* @tparam P prefix to be appied to the coherent_unit unit
* @tparam U coherent_unit unit
*/
template<basic_symbol_text Symbol, Magnitude auto M, NamedUnit auto U>
// requires detail::can_be_prefixed<U>
struct prefixed_unit : scaled_unit<M* decltype(U)::mag, typename decltype(U)::reference> {
// static constexpr auto symbol = symbol + decltype(U)::symbol;
static constexpr bool is_base = decltype(U)::is_base;
requires unit_can_be_prefixed<U>
struct prefixed_unit : std::remove_const_t<decltype(M * U)> {
static constexpr auto symbol = Symbol + U.symbol;
};
/**
* @brief A coherent unit of a derived quantity
*
* Defines a new coherent unit of a derived quantity. It should be passed as a coherent unit
* in the dimension's definition for such a quantity.
*
* @tparam Child inherited class type used by the downcasting facility (CRTP Idiom)
*/
// template<typename Child>
// struct derived_unit : downcast_dispatch<Child, scaled_unit<mag<1>(), Child>> {};
namespace detail {
template<basic_symbol_text Symbol, auto... Args>
void is_named_impl(const volatile named_unit<Symbol, Args...>*);
template<typename T>
inline constexpr bool is_power_of_unit =
requires { requires is_specialization_of_power<T> && Unit<typename T::factor>; };
template<basic_symbol_text Symbol, Magnitude auto M, NamedUnit auto U>
void is_named_impl(const volatile prefixed_unit<Symbol, M, U>*);
template<typename T>
concept UnitLike = Unit<T> || is_power_of_unit<T>;
template<Unit U>
inline constexpr bool is_named<U> = requires(U * u) { is_named_impl(u); };
template<typename T>
inline constexpr bool is_per_of_units = false;
// template<typename Child, basic_symbol_text Symbol>
// void can_be_prefixed_impl(const volatile named_unit<Child, Symbol>*);
// template<typename Child, basic_symbol_text Symbol, Magnitude auto M, typename U>
// void can_be_prefixed_impl(const volatile named_scaled_unit<Child, Symbol, M, U>*);
// template<typename U, basic_symbol_text Symbol>
// void can_be_prefixed_impl(const volatile alias_unit<U, Symbol>*);
// template<Unit U>
// inline constexpr bool can_be_prefixed<U> = requires(U * u) { can_be_prefixed_impl(u); };
// template<Magnitude auto M, typename U>
// inline constexpr bool can_be_prefixed<scaled_unit<M, U>> = can_be_prefixed<typename U::reference>;
template<typename... Ts>
inline constexpr bool is_per_of_units<per<Ts...>> = (... && UnitLike<Ts>);
} // namespace detail
template<typename T>
concept UnitSpec = detail::UnitLike<T> || detail::is_per_of_units<T>;
// User should not instantiate this type!!!
// It should not be exported from the module
template<UnitSpec... Us>
struct derived_unit : detail::expr_fractions<derived_unit<>, Us...> {};
/**
* @brief Unit one
*
* Unit of a dimensionless quantity.
*/
inline constexpr struct one : derived_unit<> {
} one;
namespace detail {
template<auto M, typename U>
void is_unit_impl(const volatile scaled_unit<M, U>*);
template<basic_symbol_text Symbol, auto... Args>
void is_unit_impl(const volatile named_unit<Symbol, Args...>*);
template<typename... Us>
void is_unit_impl(const volatile derived_unit<Us...>*);
template<typename T>
requires requires(T* t) { is_unit_impl(t); }
inline constexpr bool is_unit<T> = true;
/**
* @brief A common point for a hierarchy of units
*
* A unit is an entity defined and adopted by convention, with which any other quantity of
* the same kind can be compared to express the ratio of the second quantity to the first
* one as a number.
*
* All units of the same dimension can be convereted between each other. To allow this all of
* them are expressed as different ratios of the same one proprietary chosen coherent_unit unit
* (i.e. all length units are expressed in terms of meter, all mass units are expressed in
* terms of gram, ...)
*
* @tparam M a Magnitude representing the (relative) size of this unit
* @tparam U a unit to use as a coherent_unit for this dimension
*
* @note U cannot be constrained with Unit as for some specializations (i.e. named_unit)
* it gets the incomplete child's type with the CRTP idiom.
*/
template<UnitLike U, Magnitude M>
struct canonical_unit {
U reference_unit;
M mag;
};
[[nodiscard]] constexpr auto get_canonical_unit(UnitLike auto u);
template<UnitLike T, auto M, typename U>
[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile scaled_unit<M, U>&)
{
auto base = get_canonical_unit(U{});
return canonical_unit{base.reference_unit, M * base.mag};
}
template<UnitLike T, basic_symbol_text Symbol>
[[nodiscard]] constexpr auto get_canonical_unit_impl(T t, const volatile named_unit<Symbol>&)
{
return canonical_unit{t, mag<1>};
}
template<UnitLike T, basic_symbol_text Symbol, Unit auto U>
[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile named_unit<Symbol, U>&)
{
return get_canonical_unit(U);
}
template<UnitLike T, typename F, int Num, int... Den>
[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile power<F, Num, Den...>&)
{
auto base = get_canonical_unit(F{});
return canonical_unit{
derived_unit<power_or_T<std::remove_const_t<decltype(base.reference_unit)>, power<F, Num, Den...>::exponent>>{},
pow<power<F, Num, Den...>::exponent>(base.mag)};
}
template<UnitLike T, UnitSpec... Us>
[[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile derived_unit<Us...>&)
{
if constexpr (type_list_size<typename derived_unit<Us...>::_den_> != 0) {
auto num = get_canonical_unit(type_list_map<typename derived_unit<Us...>::_num_, derived_unit>{});
auto den = get_canonical_unit(type_list_map<typename derived_unit<Us...>::_den_, derived_unit>{});
return canonical_unit{num.reference_unit / den.reference_unit, num.mag / den.mag};
} else {
auto num = (one * ... * get_canonical_unit(Us{}).reference_unit);
auto mag = (units::mag<1> * ... * get_canonical_unit(Us{}).mag);
return canonical_unit{num, mag};
}
}
[[nodiscard]] constexpr auto get_canonical_unit(UnitLike auto u) { return get_canonical_unit_impl(u, u); }
template<Unit U1, Unit U2>
struct unit_less : std::bool_constant<type_name<U1>() < type_name<U2>()> {};
template<typename T1, typename T2>
using type_list_of_unit_less = expr_less<T1, T2, unit_less>;
} // namespace detail
// Operators
template<Magnitude M, Unit U>
[[nodiscard]] consteval Unit auto operator*(M mag, U)
{
return scaled_unit<mag, U>{};
// TODO Try passing magnitude parameters rather than magnitude type itself in case of a trivial magnitude
// (single integer, ratio...)
return scaled_unit<mag, std::remove_const_t<U>>{};
}
template<Magnitude M1, Magnitude auto M2, typename U>
[[nodiscard]] consteval Unit auto operator*(M1 mag, scaled_unit<M2, U>)
{
return scaled_unit<mag * M2, U>{};
}
template<Magnitude M, Unit U>
[[nodiscard]] consteval Unit auto operator*(U, M) = delete;
template<Unit U1, Unit U2>
[[nodiscard]] consteval Unit auto operator*(U1, U2)
@@ -324,72 +270,46 @@ template<Unit U>
return detail::expr_invert<U, struct one, derived_unit>();
}
template<Unit U>
[[nodiscard]] consteval Unit auto operator/(U, int) = delete;
template<Unit U1, Unit U2>
[[nodiscard]] consteval bool operator==(U1, U2)
[[nodiscard]] consteval bool operator==(U1 lhs, U2 rhs)
{
return is_same_v<U1, U2>;
auto canonical_lhs = detail::get_canonical_unit(lhs);
auto canonical_rhs = detail::get_canonical_unit(rhs);
return is_same_v<decltype(canonical_lhs.reference_unit), decltype(canonical_rhs.reference_unit)> &&
canonical_lhs.mag == canonical_rhs.mag;
}
// template<BaseDimension D1, BaseDimension D2>
// constexpr bool operator==(D1, D2)
// {
// return D1::symbol == D2::symbol;
// }
// template<BaseDimension D1, Dimension D2>
// requires(type_list_size<typename D2::normalized_den> == 0) && (type_list_size<typename D2::normalized_num> == 1) &&
// BaseDimension<type_list_front<typename D2::normalized_num>>
// constexpr bool operator==(D1, D2)
// {
// return D1::symbol == type_list_front<typename D2::normalized_num>::symbol;
// }
// template<Dimension D1, BaseDimension D2>
// requires(type_list_size<typename D1::normalized_den> == 0) && (type_list_size<typename D1::normalized_num> == 1) &&
// BaseDimension<type_list_front<typename D1::normalized_num>>
// constexpr bool operator==(D1, D2)
// {
// return type_list_front<typename D1::normalized_num>::symbol == D2::symbol;
// }
// template<DerivedDimension D1, DerivedDimension D2>
// constexpr bool operator==(D1, D2)
// {
// return std::is_same_v<typename D1::normalized_num, typename D2::normalized_num> &&
// std::is_same_v<typename D1::normalized_den, typename D2::normalized_den>;
// }
// TODO implement this
// template<Dimension D1, Dimension D2>
// [[nodiscard]] consteval bool equivalent(D1, D2)
// {
// return is_same_v<detail::dim_type<D1>, detail::dim_type<D2>>;
// }
// Convertible
template<Unit U1, Unit U2>
[[nodiscard]] consteval bool convertible(U1, U2)
[[nodiscard]] consteval bool convertible(U1 lhs, U2 rhs)
{
// TODO implement this
return std::derived_from<U1, U2> || std::derived_from<U2, U1>;
auto canonical_lhs = detail::get_canonical_unit(lhs);
auto canonical_rhs = detail::get_canonical_unit(rhs);
return is_same_v<decltype(canonical_lhs.reference_unit), decltype(canonical_rhs.reference_unit)>;
}
// Helper types and variable factories
template<Unit U>
struct square_ : decltype(U{} * U{}) {};
template<Unit auto U>
inline constexpr square_<std::remove_const_t<decltype(U)>> square;
template<Unit U>
struct cubic_ : decltype(U{} * U{} * U{}) {};
// it is not allowed to use the same name for a variable and class template
// (even though it works for objects of regular class types)
template<Unit auto U>
inline constexpr square_<std::remove_const_t<decltype(U)>> square;
template<Unit auto U>
inline constexpr cubic_<std::remove_const_t<decltype(U)>> cubic;
} // namespace units
namespace std {
// TODO implement this
template<units::Unit U1, units::Unit U2>
requires(units::convertible(U1{}, U2{}))

View File

@@ -86,3 +86,16 @@ inline constexpr struct electronvolt : named_unit<"eV", mag<ratio{1'602'176'634,
// clang-format on
} // namespace units::si
namespace units {
template<>
inline constexpr bool unit_can_be_prefixed<si::degree_Celsius> = false;
template<>
inline constexpr bool unit_can_be_prefixed<si::minute> = false;
template<>
inline constexpr bool unit_can_be_prefixed<si::hour> = false;
template<>
inline constexpr bool unit_can_be_prefixed<si::day> = false;
} // namespace units

View File

@@ -35,6 +35,7 @@ cmake_minimum_required(VERSION 3.2)
add_library(
unit_tests_static
dimension_test.cpp
# angle_test.cpp
# cgs_test.cpp
# chrono_test.cpp
@@ -48,6 +49,7 @@ add_library(
# iec80000_test.cpp
# kind_test.cpp
magnitude_test.cpp
# math_test.cpp
# point_origin_test.cpp
# prime_test.cpp
@@ -59,7 +61,8 @@ add_library(
# si_hep_test.cpp
# symbol_text_test.cpp
# type_list_test.cpp
# unit_test.cpp
unit_test.cpp
# us_test.cpp
)

View File

@@ -20,22 +20,26 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "test_tools.h"
#include <units/base_dimension.h>
#include <units/bits/equivalent.h>
#include <units/bits/external/downcasting.h>
#include <units/derived_dimension.h>
#include <units/isq/si/prefixes.h>
#include <units/si/prefixes.h>
#include <units/unit.h>
namespace {
using namespace units;
using namespace units::detail;
template<auto V, typename T>
inline constexpr bool is_of_type = std::is_same_v<std::remove_cvref_t<decltype(V)>, T>;
using one_ = struct one;
// clang-format off
// base units
inline constexpr struct second_ : named_unit<"s"> {} second;
inline constexpr struct metre_ : named_unit<"m"> {} metre;
inline constexpr struct gram_ : named_unit<"g"> {} gram;
inline constexpr struct kilogram_ : decltype(kilo<gram>) {} kilogram;
inline constexpr struct kilogram_ : decltype(si::kilo<gram>) {} kilogram;
inline constexpr struct kelvin_ : named_unit<"K"> {} kelvin;
// derived named units
inline constexpr struct radian_ : named_unit<"rad", metre / metre> {} radian;
@@ -46,104 +50,371 @@ inline constexpr struct newton_ : named_unit<"N", kilogram * metre / square<seco
inline constexpr struct pascal_ : named_unit<"Pa", newton / square<metre>> {} pascal;
inline constexpr struct joule_ : named_unit<"J", newton * metre> {} joule;
inline constexpr struct watt_ : named_unit<"W", joule / second> {} watt;
inline constexpr struct degree_Celsius_ : named_unit<basic_symbol_text{"\u00B0C", "`C"}, kelvin> {} degree_Celsius;
inline constexpr struct minute_ : named_unit<"min", mag<60> * second> {} minute;
inline constexpr struct hour_ : named_unit<"h", mag<60> * minute> {} hour;
inline constexpr struct day_ : named_unit<"d", mag<24> * hour> {} day;
inline constexpr struct astronomical_unit_ : named_unit<"au", mag<149'597'870'700> * metre> {} astronomical_unit;
inline constexpr struct degree_ : named_unit<basic_symbol_text{"°", "deg"}, mag_pi / mag<180> * radian> {} degree;
inline constexpr struct are_ : named_unit<"a", square<deca<metre>>> {} are;
inline constexpr struct hectare_ : decltype(hecto<are>) {} hectare;
inline constexpr struct litre_ : named_unit<"l", cubic<deci<metre>>> {} litre;
inline constexpr struct are_ : named_unit<"a", square<si::deca<metre>>> {} are;
inline constexpr struct hectare_ : decltype(si::hecto<are>) {} hectare;
inline constexpr struct litre_ : named_unit<"l", cubic<si::deci<metre>>> {} litre;
inline constexpr struct tonne_ : named_unit<"t", mag<1000> * kilogram> {} tonne;
inline constexpr struct dalton_ : named_unit<"Da", mag<ratio{16'605'390'666'050, 10'000'000'000'000}> * mag_power<10, -27> * kilogram> {} dalton;
inline constexpr struct electronvolt_ : named_unit<"eV", mag<ratio{1'602'176'634, 1'000'000'000}> * mag_power<10, -19> * joule> {} electronvolt;
inline constexpr struct kilometre_ : decltype(kilo<metre>) {} kilometre;
inline constexpr struct kilometre_ : decltype(si::kilo<metre>) {} kilometre;
inline constexpr struct kilojoule_ : decltype(si::kilo<joule>) {} kilojoule;
// clang-format on
}
// concepts verification
static_assert(Unit<metre_>);
static_assert(Unit<kilogram_>);
static_assert(Unit<hertz_>);
static_assert(Unit<newton_>);
static_assert(Unit<minute_>);
static_assert(Unit<decltype(kilo<gram>)>);
static_assert(Unit<decltype(si::kilo<gram>)>);
static_assert(Unit<decltype(square<metre>)>);
static_assert(Unit<decltype(cubic<metre>)>);
static_assert(Unit<decltype(mag<60> * second)>);
static_assert(Unit<kilometre_>);
static_assert(NamedUnit<metre_>);
static_assert(NamedUnit<kilogram_>);
static_assert(NamedUnit<hertz_>);
static_assert(NamedUnit<newton_>);
static_assert(NamedUnit<minute_>);
static_assert(!NamedUnit<decltype(kilo<gram>)>);
static_assert(NamedUnit<radian_>);
static_assert(!NamedUnit<kilogram_>);
static_assert(!NamedUnit<kilojoule_>);
static_assert(!NamedUnit<hectare_>);
static_assert(!NamedUnit<decltype(si::kilo<gram>)>);
static_assert(!NamedUnit<decltype(square<metre>)>);
static_assert(!NamedUnit<decltype(cubic<metre>)>);
static_assert(!NamedUnit<decltype(mag<60> * second)>);
static_assert(!NamedUnit<kilometre_>);
template<typename T>
constexpr bool print();
static_assert(kilo<metre> == kilometre);
static_assert(mag<1000> * metre == kilo<metre>);
// named unit
static_assert(is_of_type<metre, metre_>);
static_assert(is_of_type<get_canonical_unit(metre).reference_unit, metre_>);
static_assert(get_canonical_unit(metre).mag == mag<1>);
static_assert(convertible(metre, metre));
static_assert(!convertible(metre, second));
static_assert(metre == metre);
static_assert(metre != second);
static_assert(is_of_type<degree_Celsius, degree_Celsius_>);
static_assert(is_of_type<get_canonical_unit(degree_Celsius).reference_unit, kelvin_>);
static_assert(get_canonical_unit(degree_Celsius).mag == mag<1>);
static_assert(convertible(degree_Celsius, kelvin));
static_assert(degree_Celsius == kelvin);
static_assert(is_of_type<radian, radian_>);
static_assert(is_of_type<get_canonical_unit(radian).reference_unit, one_>);
static_assert(get_canonical_unit(radian).mag == mag<1>);
static_assert(convertible(minute, second));
static_assert(minute != second);
static_assert(is_of_type<steradian, steradian_>);
static_assert(is_of_type<get_canonical_unit(steradian).reference_unit, one_>);
static_assert(get_canonical_unit(steradian).mag == mag<1>);
static_assert(convertible(radian, steradian)); // !!!
static_assert(radian == steradian); // !!!
static_assert(is_of_type<minute, minute_>);
static_assert(is_of_type<get_canonical_unit(minute).reference_unit, second_>);
static_assert(get_canonical_unit(minute).mag == mag<60>);
static_assert(convertible(minute, second));
static_assert(minute != second);
static_assert(is_of_type<hour, hour_>);
static_assert(is_of_type<get_canonical_unit(hour).reference_unit, second_>);
static_assert(get_canonical_unit(hour).mag == mag<3600>);
static_assert(convertible(hour, second));
static_assert(convertible(hour, minute));
static_assert(convertible(hour, hour));
static_assert(hour != second);
static_assert(hour != minute);
static_assert(hour == hour);
static_assert(is_of_type<newton, newton_>);
static_assert(
is_of_type<get_canonical_unit(newton).reference_unit, derived_unit<gram_, metre_, per<power<second_, 2>>>>);
static_assert(get_canonical_unit(newton).mag == mag<1000>); // !!! (because of kilogram)
static_assert(convertible(newton, newton));
static_assert(newton == newton);
static_assert(is_of_type<joule, joule_>);
static_assert(
is_of_type<get_canonical_unit(joule).reference_unit, derived_unit<gram_, power<metre_, 2>, per<power<second_, 2>>>>);
static_assert(get_canonical_unit(joule).mag == mag<1000>); // !!! (because of kilogram)
static_assert(convertible(joule, joule));
static_assert(joule == joule);
static_assert(joule != newton);
// prefixed_unit
static_assert(is_of_type<kilometre, kilometre_>);
static_assert(is_of_type<get_canonical_unit(kilometre).reference_unit, metre_>);
static_assert(get_canonical_unit(kilometre).mag == mag<1000>);
static_assert(convertible(kilometre, metre));
static_assert(kilometre != metre);
static_assert(kilometre.symbol == "km");
static_assert(is_of_type<kilojoule, kilojoule_>);
static_assert(is_of_type<get_canonical_unit(kilojoule).reference_unit,
derived_unit<gram_, power<metre_, 2>, per<power<second_, 2>>>>);
static_assert(get_canonical_unit(kilojoule).mag == mag<1'000'000>);
static_assert(convertible(kilojoule, joule));
static_assert(kilojoule != joule);
static_assert(kilojoule.symbol == "kJ");
static_assert(is_of_type<si::kilo<metre>, si::kilo_<metre>>);
static_assert(is_of_type<si::kilo<joule>, si::kilo_<joule>>);
// prefixes
static_assert(si::yocto<metre>.symbol == "ym");
static_assert(si::zepto<metre>.symbol == "zm");
static_assert(si::atto<metre>.symbol == "am");
static_assert(si::femto<metre>.symbol == "fm");
static_assert(si::pico<metre>.symbol == "pm");
static_assert(si::nano<metre>.symbol == "nm");
static_assert(si::micro<metre>.symbol == basic_symbol_text{"µm", "um"});
static_assert(si::milli<metre>.symbol == "mm");
static_assert(si::centi<metre>.symbol == "cm");
static_assert(si::deci<metre>.symbol == "dm");
static_assert(si::deca<metre>.symbol == "dam");
static_assert(si::hecto<metre>.symbol == "hm");
static_assert(si::kilo<metre>.symbol == "km");
static_assert(si::mega<metre>.symbol == "Mm");
static_assert(si::giga<metre>.symbol == "Gm");
static_assert(si::tera<metre>.symbol == "Tm");
static_assert(si::peta<metre>.symbol == "Pm");
static_assert(si::exa<metre>.symbol == "Em");
static_assert(si::zetta<metre>.symbol == "Zm");
static_assert(si::yotta<metre>.symbol == "Ym");
// scaled_unit
constexpr auto u1 = mag<1> * metre;
static_assert(is_of_type<u1, scaled_unit<mag<1>, metre_>>);
static_assert(is_of_type<get_canonical_unit(u1).reference_unit, metre_>);
static_assert(get_canonical_unit(u1).mag == mag<1>);
constexpr auto u2 = mag<2> * kilometre;
static_assert(is_of_type<u2, scaled_unit<mag<2>, kilometre_>>);
static_assert(is_of_type<get_canonical_unit(u2).reference_unit, metre_>);
static_assert(get_canonical_unit(u2).mag == mag<2000>);
constexpr auto u3 = mag<42> * si::kilo<joule>;
static_assert(is_of_type<u3, scaled_unit<mag<42>, si::kilo_<joule>>>);
static_assert(
is_of_type<get_canonical_unit(u3).reference_unit, derived_unit<gram_, power<metre_, 2>, per<power<second_, 2>>>>);
static_assert(get_canonical_unit(u3).mag == mag<42'000'000>);
// derived unit expression template syntax verification
static_assert(is_of_type<1 / second, derived_unit<one_, per<second_>>>);
static_assert(is_of_type<1 / (1 / second), second_>);
static_assert(is_of_type<one * second, second_>);
static_assert(is_of_type<second * one, second_>);
static_assert(is_of_type<one * (1 / second), derived_unit<one_, per<second_>>>);
static_assert(is_of_type<1 / second * one, derived_unit<one_, per<second_>>>);
static_assert(is_of_type<metre * second, derived_unit<metre_, second_>>);
static_assert(is_of_type<metre * metre, derived_unit<power<metre_, 2>>>);
static_assert(is_of_type<metre * metre * second, derived_unit<power<metre_, 2>, second_>>);
static_assert(is_of_type<metre * second * metre, derived_unit<power<metre_, 2>, second_>>);
static_assert(is_of_type<metre*(second* metre), derived_unit<power<metre_, 2>, second_>>);
static_assert(is_of_type<second*(metre* metre), derived_unit<power<metre_, 2>, second_>>);
static_assert(is_of_type<1 / second * metre, derived_unit<metre_, per<second_>>>);
static_assert(is_of_type<1 / second * second, one_>);
static_assert(is_of_type<second / one, second_>);
static_assert(is_of_type<1 / second / one, derived_unit<one_, per<second_>>>);
static_assert(is_of_type<metre / second * second, metre_>);
static_assert(is_of_type<1 / second * (1 / second), derived_unit<one_, per<power<second_, 2>>>>);
static_assert(is_of_type<1 / (second * second), derived_unit<one_, per<power<second_, 2>>>>);
static_assert(is_of_type<1 / (1 / (second * second)), derived_unit<power<second_, 2>>>);
static_assert(is_of_type<metre / second * (1 / second), derived_unit<metre_, per<power<second_, 2>>>>);
static_assert(is_of_type<metre / second*(metre / second), derived_unit<power<metre_, 2>, per<power<second_, 2>>>>);
static_assert(is_of_type<metre / second*(second / metre), one_>);
static_assert(is_of_type<watt / joule, derived_unit<watt_, per<joule_>>>);
static_assert(is_of_type<joule / watt, derived_unit<joule_, per<watt_>>>);
// derived unit normalization
constexpr auto u4 = metre / second;
static_assert(is_of_type<get_canonical_unit(u4).reference_unit, derived_unit<metre_, per<second_>>>);
static_assert(get_canonical_unit(u4).mag == mag<1>);
constexpr auto u5 = kilometre / second;
static_assert(is_of_type<u5, derived_unit<kilometre_, per<second_>>>);
static_assert(is_of_type<get_canonical_unit(u5).reference_unit, derived_unit<metre_, per<second_>>>);
static_assert(get_canonical_unit(u5).mag == mag<1000>);
constexpr auto u6 = kilometre / hour;
static_assert(is_of_type<u6, derived_unit<kilometre_, per<hour_>>>);
static_assert(is_of_type<get_canonical_unit(u6).reference_unit, derived_unit<metre_, per<second_>>>);
static_assert(get_canonical_unit(u6).mag == mag<ratio{1000, 3600}>);
constexpr auto u7 = mag<1000> * kilometre / hour;
static_assert(is_of_type<u7, derived_unit<scaled_unit<mag<1000>, kilometre_>, per<hour_>>>);
static_assert(is_of_type<get_canonical_unit(u7).reference_unit, derived_unit<metre_, per<second_>>>);
static_assert(get_canonical_unit(u7).mag == mag<ratio{1'000'000, 3'600}>);
constexpr auto u8 = mag<1000> * (kilometre / hour);
static_assert(is_of_type<u8, scaled_unit<mag<1000>, derived_unit<kilometre_, per<hour_>>>>);
static_assert(is_of_type<get_canonical_unit(u8).reference_unit, derived_unit<metre_, per<second_>>>);
static_assert(get_canonical_unit(u8).mag == mag<ratio{1'000'000, 3'600}>);
constexpr auto u9 = 1 / hour * (mag<1000> * kilometre);
static_assert(is_of_type<u9, derived_unit<scaled_unit<mag<1000>, kilometre_>, per<hour_>>>);
static_assert(is_of_type<get_canonical_unit(u9).reference_unit, derived_unit<metre_, per<second_>>>);
static_assert(get_canonical_unit(u9).mag == mag<ratio{1'000'000, 3'600}>);
// comparisons of the same units
static_assert(second == second);
static_assert(metre / second == metre / second);
// comparisons of equivalent units (named vs unnamed/derived)
static_assert(1 / second == hertz);
static_assert(convertible(1 / second, hertz));
// comparisons of equivalent but not convertible units
static_assert(hertz == becquerel);
static_assert(convertible(hertz, becquerel));
// comparisons of scaled units
static_assert(si::kilo<metre> == kilometre);
static_assert(mag<1000> * metre == si::kilo<metre>);
static_assert(mag<1000> * metre == kilometre);
static_assert(equivalent<kilo<metre>, kilometre>);
static_assert(equivalent<mag<1000> * metre, kilo<metre>>);
static_assert(equivalent<mag<1000> * metre, kilometre>);
static_assert(convertible(si::kilo<metre>, kilometre));
static_assert(convertible(mag<1000> * metre, si::kilo<metre>));
static_assert(convertible(mag<1000> * metre, kilometre));
static_assert(metre != kilometre);
static_assert(convertible(metre, kilometre));
static_assert(mag<100> * metre != kilometre);
static_assert(milli<metre> != kilometre);
static_assert(!equivalent<metre, kilometre>);
static_assert(!equivalent<mag<100> * metre, kilometre>);
static_assert(!equivalent<milli<metre>, kilometre>);
static_assert(convertible(mag<100> * metre, kilometre));
static_assert(si::milli<metre> != kilometre);
static_assert(convertible(si::milli<metre>, kilometre));
static_assert(1 / second != hertz);
static_assert(becquerel != hertz);
static_assert(equivalent<1 / second, hertz>);
static_assert(!equivalent<becquerel, hertz>);
// one
static_assert(metre / metre == one);
// static_assert(metre * metre == square_metre);
// static_assert(second * second == second_squared);
// static_assert(second * second * second == second_cubed);
// static_assert(second * (second * second) == second_cubed);
// static_assert(second_squared * second == second_cubed);
// static_assert(second * second_squared == second_cubed);
using namespace units;
using namespace units::isq;
// static_assert(1 / second * metre == metre / second);
// static_assert(metre * (1 / second) == metre / second);
// static_assert((metre / second) * (1 / second) == metre / second / second);
// static_assert((metre / second) * (1 / second) == metre / (second * second));
// static_assert((metre / second) * (1 / second) == metre / second_squared);
struct metre : named_unit<metre, "m"> {};
struct centimetre : prefixed_unit<centimetre, si::centi, metre> {};
struct kilometre : prefixed_unit<kilometre, si::kilo, metre> {};
struct yard : named_scaled_unit<yard, "yd", mag<ratio{9'144, 10'000}>(), metre> {};
struct foot : named_scaled_unit<foot, "ft", mag<ratio(1, 3)>(), yard> {};
struct dim_length : base_dimension<"length", metre> {};
// static_assert(hertz == 1 / second);
// static_assert(newton == kilogram * metre / second_squared);
// static_assert(joule == kilogram * square_metre / second_squared);
// static_assert(joule == newton * metre);
// static_assert(watt == joule / second);
// static_assert(watt == kilogram * square_metre / second_cubed);
struct second : named_unit<second, "s"> {};
struct hour : named_scaled_unit<hour, "h", mag<3600>(), second> {};
struct dim_time : base_dimension<"time", second> {};
// static_assert(1 / frequency_dim == second);
// static_assert(frequency_dim * second == one);
struct kelvin : named_unit<kelvin, "K"> {};
// static_assert(metre * metre == area_dim);
// static_assert(metre * metre != volume_dim);
// static_assert(area_dim / metre == metre);
#if !UNITS_COMP_MSVC
static_assert([]<Prefix P>(P) {
return !requires { typename prefixed_unit<struct kilokilometre, P, kilometre>; };
}(si::kilo{})); // no prefix allowed
#endif
// static_assert(metre * metre * metre == volume_dim);
// static_assert(area_dim * metre == volume_dim);
// static_assert(volume_dim / metre == area_dim);
// static_assert(volume_dim / metre / metre == metre);
// static_assert(area_dim * area_dim / metre == volume_dim);
// static_assert(area_dim * (area_dim / metre) == volume_dim);
// static_assert(volume_dim / (metre * metre) == metre);
struct metre_per_second : derived_unit<metre_per_second> {};
struct dim_speed :
derived_dimension<dim_speed, metre_per_second, units::exponent<dim_length, 1>, units::exponent<dim_time, -1>> {};
struct kilometre_per_hour : derived_scaled_unit<kilometre_per_hour, dim_speed, kilometre, hour> {};
// static_assert(metre / second == speed_dim);
// static_assert(metre * second != speed_dim);
// static_assert(metre / second / second != speed_dim);
// static_assert(metre / speed_dim == second);
// static_assert(speed_dim * second == metre);
static_assert(equivalent<metre::named_unit, metre>);
static_assert(equivalent<metre::scaled_unit, metre>);
static_assert(compare<downcast<scaled_unit<mag<1>(), metre>>, metre>);
static_assert(compare<downcast<scaled_unit<mag<ratio(1, 100)>(), metre>>, centimetre>);
static_assert(compare<downcast<scaled_unit<yard::mag, metre>>, yard>);
static_assert(compare<downcast<scaled_unit<yard::mag / mag<3>(), metre>>, foot>);
static_assert(compare<downcast<scaled_unit<kilometre::mag / hour::mag, metre_per_second>>, kilometre_per_hour>);
// static_assert(metre / second / second == acceleration_dim);
// static_assert(metre / (second * second) == acceleration_dim);
// static_assert(speed_dim / second == acceleration_dim);
// static_assert(speed_dim / acceleration_dim == second);
// static_assert(acceleration_dim * second == speed_dim);
// static_assert(acceleration_dim * (second * second) == metre);
// static_assert(acceleration_dim / speed_dim == frequency_dim);
// milli<metre> / milli<second> == micro<metre> / micro<second>;
// milli<metre> * kilo<metre> == deci<metre> * deca<metre>;
// Bq + Hz should not compile
// Bq + Hz + 1/s should compile?
// using namespace units;
// using namespace units::isq;
// struct metre : named_unit<metre, "m"> {};
// struct centimetre : prefixed_unit<centimetre, si::centi, metre> {};
// struct kilometre : prefixed_unit<kilometre, si::kilo, metre> {};
// struct yard : named_scaled_unit<yard, "yd", mag<ratio{9'144, 10'000}>(), metre> {};
// struct foot : named_scaled_unit<foot, "ft", mag<ratio(1, 3)>(), yard> {};
// struct dim_length : base_dimension<"length", metre> {};
// struct second : named_unit<second, "s"> {};
// struct hour : named_scaled_unit<hour, "h", mag<3600>(), second> {};
// struct dim_time : base_dimension<"time", second> {};
// struct kelvin : named_unit<kelvin, "K"> {};
// #if !UNITS_COMP_MSVC
// static_assert([]<Prefix P>(P) {
// return !requires { typename prefixed_unit<struct kilokilometre, P, kilometre>; };
// }(si::kilo{})); // no prefix allowed
// #endif
// struct metre_per_second : derived_unit<metre_per_second> {};
// struct dim_speed :
// derived_dimension<dim_speed, metre_per_second, units::exponent<dim_length, 1>, units::exponent<dim_time, -1>> {};
// struct kilometre_per_hour : derived_scaled_unit<kilometre_per_hour, dim_speed, kilometre, hour> {};
// static_assert(equivalent<metre::named_unit, metre>);
// static_assert(equivalent<metre::scaled_unit, metre>);
// static_assert(compare<downcast<scaled_unit<mag<1>(), metre>>, metre>);
// static_assert(compare<downcast<scaled_unit<mag<ratio(1, 100)>(), metre>>, centimetre>);
// static_assert(compare<downcast<scaled_unit<yard::mag, metre>>, yard>);
// static_assert(compare<downcast<scaled_unit<yard::mag / mag<3>(), metre>>, foot>);
// static_assert(compare<downcast<scaled_unit<kilometre::mag / hour::mag, metre_per_second>>, kilometre_per_hour>);
// static_assert(centimetre::symbol == "cm");
// static_assert(kilometre::symbol == "km");
// static_assert(kilometre_per_hour::symbol == "km/h");
// static_assert(si::metre != si::kilometre);
// static_assert(!equivalent(si::metre, si::kilometre));
// static_assert(convertible(si::metre, si::kilometre));
static_assert(centimetre::symbol == "cm");
static_assert(kilometre::symbol == "km");
static_assert(kilometre_per_hour::symbol == "km/h");
} // namespace