From 0b9b159695ff24bafddd2eb06ea9fc2207cff6dc Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Thu, 20 Oct 2022 14:03:15 +0200 Subject: [PATCH] fix: units design refactored and fixed --- src/core/include/units/dimension.h | 3 + src/core/include/units/unit.h | 279 +++++++++++++++++++++------- test/unit_test/static/unit_test.cpp | 209 ++++++++------------- 3 files changed, 292 insertions(+), 199 deletions(-) diff --git a/src/core/include/units/dimension.h b/src/core/include/units/dimension.h index ada246dc..9e17d108 100644 --- a/src/core/include/units/dimension.h +++ b/src/core/include/units/dimension.h @@ -246,6 +246,9 @@ using dim_type = dim_type_impl::type; } // namespace detail + +// Operators + template [[nodiscard]] consteval Dimension auto operator*(D1, D2) { diff --git a/src/core/include/units/unit.h b/src/core/include/units/unit.h index 68b465a5..5eff0503 100644 --- a/src/core/include/units/unit.h +++ b/src/core/include/units/unit.h @@ -43,40 +43,94 @@ namespace detail { template inline constexpr bool is_unit = false; -} +} // namespace detail /** * @brief A concept matching all unit types in the library * - * Satisfied by all unit types derived from an specialization of :class:`scaled_unit`. + * Satisfied by all unit types provided by the library. */ template concept Unit = detail::is_unit; -// User should not instantiate this type!!! -// It should not be exported from the module + +/** + * @brief Unit being a scaled version of another unit + * + * @tparam M magnitude describing the scale factor + * @tparam U reference unit being scaled + * + * @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 -struct scaled_unit {}; +struct scaled_unit { + static constexpr UNITS_MSVC_WORKAROUND(Magnitude) auto mag = M; + static constexpr U reference_unit{}; +}; + +namespace detail { + +template +inline constexpr bool is_specialization_of_scaled_unit = false; + +template +inline constexpr bool is_specialization_of_scaled_unit> = true; + +} // namespace detail /** * @brief A named unit * - * Defines a named (in most cases coherent) unit that is then passed to a dimension definition. - * A named unit may be composed with a prefix to create a prefixed_unit. + * Defines a unit with a special name. + * Most of the named units may be composed with a prefix to create a `prefixed_unit`. + * + * For example: + * + * @code{.cpp} + * inline constexpr struct second : named_unit<"s"> {} second; + * inline constexpr struct metre : named_unit<"m"> {} metre; + * inline constexpr struct hertz : named_unit<"Hz", 1 / second> {} hertz; + * inline constexpr struct newton : named_unit<"N", kilogram * metre / square> {} newton; + * inline constexpr struct degree_Celsius : named_unit {} degree_Celsius; + * inline constexpr struct minute : named_unit<"min", mag<60> * second> {} minute; + * @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 unit types in the source code. All operations + * are done on the objects. Contrarily, the unit 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 Symbol a short text representation of the unit */ template struct named_unit; +/** + * @brief Specialization for base unit + * + * Defines a base unit in the system of units (i.e. `metre`). + * or a name assigned to another scaled or derived unit (i.e. `hour`, `joule`). + * Most of the named units may be composed with a prefix to create a `prefixed_unit`. + * + * @tparam Symbol a short text representation of the unit + */ template struct named_unit { - static constexpr auto symbol = Symbol; + static constexpr auto symbol = Symbol; ///< Unique base unit identifier }; +/** + * @brief Specialization for a unit with special name + * + * Allows assigning a special name to another scaled or derived unit (i.e. `hour`, `joule`). + * + * @tparam Symbol a short text representation of the unit + * @tparam Unit a unit for which we provide a special name + */ template struct named_unit : std::remove_const_t { - static constexpr auto symbol = Symbol; + static constexpr auto symbol = Symbol; ///< Unique unit identifier }; namespace detail { @@ -84,27 +138,62 @@ namespace detail { template void to_base_specialization_of_named_unit(const volatile named_unit*); +template +inline constexpr bool is_specialization_of_named_unit = false; + +template +inline constexpr bool is_specialization_of_named_unit> = true; + } // namespace detail +/** + * @brief A concept matching all units with special names + * + * Satisfied by all unit types derived from the specialization of `named_unit`. + */ template -concept NamedUnit = Unit && requires(T* t) { detail::to_base_specialization_of_named_unit(t); }; +concept NamedUnit = Unit && requires(T* t) { detail::to_base_specialization_of_named_unit(t); } && + (!detail::is_specialization_of_named_unit); -template -inline constexpr bool unit_can_be_prefixed = NamedUnit>; +/** + * @brief Prevents assignment of a prefix to specific units + * + * By default all named units allow assigning a prefix for them. There are some notable exceptions like + * `hour` or `degree_Celsius`. For those a partial specialization with the value `false` should be + * provided. + */ +template +inline constexpr bool unit_can_be_prefixed = true; + +/** + * @brief A concept to be used to define prefixes for a unit + */ template concept PrefixableUnit = NamedUnit && unit_can_be_prefixed; + /** * @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 - * coherent_unit unit. + * Defines a new unit that is a scaled version of another unit with the scaling + * factor specified by a predefined prefix. * - * @tparam Child inherited class type used by the downcasting facility (CRTP Idiom) - * @tparam P prefix to be appied to the coherent_unit unit - * @tparam U coherent_unit unit + * For example: + * + * @code{.cpp} + * template + * struct kilo_ : prefixed_unit<"k", mag_power<10, 3>, U> {}; + * + * template + * inline constexpr kilo_ kilo; + * + * inline constexpr struct kilogram : decltype(si::kilo) {} kilogram; + * @endcode + * + * @tparam Symbol a prefix text to prepend to a unit symbol + * @tparam M scaling factor of the prefix + * @tparam U a named unit to be prefixed */ template struct prefixed_unit : std::remove_const_t { @@ -129,11 +218,54 @@ inline constexpr bool is_per_of_units> = (... && UnitLike); } // namespace detail template -concept UnitSpec = detail::UnitLike || detail::is_per_of_units; +concept DerivedUnitSpec = detail::UnitLike || detail::is_per_of_units; -// User should not instantiate this type!!! -// It should not be exported from the module -template +/** + * @brief Measurement unit for a derived quantity + * + * Derived units are defined as products of powers of the base units. + * + * Instead of using a raw list of exponents this library decided to use expression template syntax to make types + * more digestable for the user. 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 unit type is enclosed in + * `power` class template. Otherwise, it is just put directly in the list without any wrapper. There + * is also one special case. In case all of the exponents are negative then the `one` being a coherent unit of + * a dimensionless quantity is put in the front to increase the readability. + * + * For example: + * + * @code{.cpp} + * static_assert(is_of_type<1 / second, derived_unit>>); + * static_assert(is_of_type<1 / (1 / second), second>); + * static_assert(is_of_type); + * static_assert(is_of_type>>); + * static_assert(is_of_type>); + * static_assert(is_of_type>>); + * static_assert(is_of_type, derived_unit>>>); + * static_assert(is_of_type>>); + * @endcode + * + * Every unit in the library has its internal canonical representation being the list of exponents of named base units + * (with the exception of `kilogram` which is represented as `gram` here) and a scaling ratio represented with a + * magnitude. + * + * Two units are deemed convertible if their canonical version has units of the same type. + * Two units are equivalent when they are convertible and their canonical versions have the same scaling ratios. + * + * The above means that: + * - `1/s` and `Hz` are both convertible and equal + * - `m` and `km` are convertible but not equal + * - `m` and `m²` ane not convertible and not equal + * + * @note This also means that units like `hertz` and `becquerel` are also considered convertible and equal. + * + * @tparam Us a parameter pack consisting tokens allowed in the unit specification + * (units, `power`, `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 unit arithmetic equation provided by the user. + */ +template struct derived_unit : detail::expr_fractions, Us...> {}; /** @@ -161,27 +293,26 @@ template inline constexpr bool is_unit = true; /** - * @brief A common point for a hierarchy of units + * @brief A canonical representation of a unit * - * 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. + * A canonical representation of a unit consists of a `reference_unit` and its scaling + * factor represented by the magnitude `mag`. * - * 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, ...) + * `reference_unit` is a unit (possibly derived one) that consists only named base units. + * All of the intermediate derived units are extracted, prefixes and magnitudes of scaled + * units are stripped from them and accounted in the `mag`. * - * @tparam M a Magnitude representing the (relative) size of this unit - * @tparam U a unit to use as a coherent_unit for this dimension + * All units having the same canonical unit are deemed equal. + * All units having the same `reference_unit` are convertible (their `mag` may differ + * and is the subject of conversion). * - * @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. + * @tparam U a unit to use as a `reference_unit` + * @tparam M a Magnitude representing an absolute scaling factor of this unit */ -template +template struct canonical_unit { - U reference_unit; M mag; + U reference_unit; }; [[nodiscard]] constexpr auto get_canonical_unit(UnitLike auto u); @@ -190,13 +321,13 @@ template [[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile scaled_unit&) { auto base = get_canonical_unit(U{}); - return canonical_unit{base.reference_unit, M * base.mag}; + return canonical_unit{M * base.mag, base.reference_unit}; } template [[nodiscard]] constexpr auto get_canonical_unit_impl(T t, const volatile named_unit&) { - return canonical_unit{t, mag<1>}; + return canonical_unit{mag<1>, t}; } template @@ -210,27 +341,29 @@ template { auto base = get_canonical_unit(F{}); return canonical_unit{ - derived_unit, power::exponent>>{}, - pow::exponent>(base.mag)}; + pow::exponent>(base.mag), + derived_unit, power::exponent>>{}}; } -template +template [[nodiscard]] constexpr auto get_canonical_unit_impl(T, const volatile derived_unit&) { if constexpr (type_list_size::_den_> != 0) { auto num = get_canonical_unit(type_list_map::_num_, derived_unit>{}); auto den = get_canonical_unit(type_list_map::_den_, derived_unit>{}); - return canonical_unit{num.reference_unit / den.reference_unit, num.mag / den.mag}; + return canonical_unit{num.mag / den.mag, num.reference_unit / den.reference_unit}; } 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}; + return canonical_unit{mag, num}; } } [[nodiscard]] constexpr auto get_canonical_unit(UnitLike auto u) { return get_canonical_unit_impl(u, u); } - +// TODO What if the same unit will have different types (i.e. user will inherit its own type from `metre`)? +// Is there a better way to sort units here? Some of them may not have symbol at all (like all units of +// dimensionless quantities). template struct unit_less : std::bool_constant() < type_name()> {}; @@ -242,27 +375,55 @@ using type_list_of_unit_less = expr_less; // Operators +/** + * Multiplication by `1` returns the same unit, otherwise `scaled_unit` is being returned. + */ template -[[nodiscard]] consteval Unit auto operator*(M mag, U) +[[nodiscard]] consteval Unit auto operator*(M mag, U u) { - // TODO Try passing magnitude parameters rather than magnitude type itself in case of a trivial magnitude - // (single integer, ratio...) - return scaled_unit>{}; + if constexpr (mag == units::mag<1>) + return u; + else + return scaled_unit>{}; } template [[nodiscard]] consteval Unit auto operator*(U, M) = delete; +/** + * `scaled_unit` specializations have priority in this operation. This means that the library framework + * prevents passing it as an element to the `derived_unit`. In such case only the reference unit is passed + * to the derived unit and the magnitude remains outside forming another scaled unit as a result of the operation. + */ template -[[nodiscard]] consteval Unit auto operator*(U1, U2) +[[nodiscard]] consteval Unit auto operator*(U1 u1, U2 u2) { - return detail::expr_multiply(); + if constexpr (detail::is_specialization_of_scaled_unit && detail::is_specialization_of_scaled_unit) + return (U1::mag * U2::mag) * (U1::reference_unit * U2::reference_unit); + else if constexpr (detail::is_specialization_of_scaled_unit) + return U1::mag * (U1::reference_unit * u2); + else if constexpr (detail::is_specialization_of_scaled_unit) + return U2::mag * (u1 * U2::reference_unit); + else + return detail::expr_multiply(); } +/** + * `scaled_unit` specializations have priority in this operation. This means that the library framework + * prevents passing it as an element to the `derived_unit`. In such case only the reference unit is passed + * to the derived unit and the magnitude remains outside forming another scaled unit as a result of the operation. + */ template -[[nodiscard]] consteval Unit auto operator/(U1, U2) +[[nodiscard]] consteval Unit auto operator/(U1 u1, U2 u2) { - return detail::expr_divide(); + if constexpr (detail::is_specialization_of_scaled_unit && detail::is_specialization_of_scaled_unit) + return (U1::mag / U2::mag) * (U1::reference_unit / U2::reference_unit); + else if constexpr (detail::is_specialization_of_scaled_unit) + return U1::mag * (U1::reference_unit / u2); + else if constexpr (detail::is_specialization_of_scaled_unit) + return U2::mag * (u1 / U2::reference_unit); + else + return detail::expr_divide(); } template @@ -294,20 +455,12 @@ template return is_same_v; } -// Helper types and variable factories -template -struct square_ : decltype(U{} * U{}) {}; - -template -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) +// Helper variable templates to create common powers template -inline constexpr square_> square; +inline constexpr decltype(U * U) square; template -inline constexpr cubic_> cubic; +inline constexpr decltype(U * U * U) cubic; } // namespace units diff --git a/test/unit_test/static/unit_test.cpp b/test/unit_test/static/unit_test.cpp index 6d3701de..6d8b74d8 100644 --- a/test/unit_test/static/unit_test.cpp +++ b/test/unit_test/static/unit_test.cpp @@ -45,7 +45,7 @@ inline constexpr struct kelvin_ : named_unit<"K"> {} kelvin; inline constexpr struct radian_ : named_unit<"rad", metre / metre> {} radian; inline constexpr struct steradian_ : named_unit<"sr", square / square> {} steradian; inline constexpr struct hertz_ : named_unit<"Hz", 1 / second> {} hertz; -inline constexpr struct becquerel : named_unit<"Bq", 1 / second> {} becquerel; +inline constexpr struct becquerel_ : named_unit<"Bq", 1 / second> {} becquerel; inline constexpr struct newton_ : named_unit<"N", kilogram * metre / square> {} newton; inline constexpr struct pascal_ : named_unit<"Pa", newton / square> {} pascal; inline constexpr struct joule_ : named_unit<"J", newton * metre> {} joule; @@ -94,9 +94,6 @@ static_assert(!NamedUnit)>); static_assert(!NamedUnit * second)>); static_assert(!NamedUnit); -template -constexpr bool print(); - // named unit static_assert(is_of_type); static_assert(is_of_type); @@ -201,21 +198,26 @@ static_assert(si::yotta.symbol == "Ym"); // scaled_unit -constexpr auto u1 = mag<1> * metre; -static_assert(is_of_type, metre_>>); -static_assert(is_of_type); -static_assert(get_canonical_unit(u1).mag == mag<1>); +constexpr auto m_1 = mag<1> * metre; +static_assert(is_of_type); +static_assert(is_of_type); +static_assert(get_canonical_unit(m_1).mag == mag<1>); -constexpr auto u2 = mag<2> * kilometre; -static_assert(is_of_type, kilometre_>>); -static_assert(is_of_type); -static_assert(get_canonical_unit(u2).mag == mag<2000>); +constexpr auto m_2 = mag<2> * metre; +static_assert(is_of_type, metre_>>); +static_assert(is_of_type); +static_assert(get_canonical_unit(m_2).mag == mag<2>); -constexpr auto u3 = mag<42> * si::kilo; -static_assert(is_of_type, si::kilo_>>); +constexpr auto km_2 = mag<2> * kilometre; +static_assert(is_of_type, kilometre_>>); +static_assert(is_of_type); +static_assert(get_canonical_unit(km_2).mag == mag<2000>); + +constexpr auto kJ_42 = mag<42> * si::kilo; +static_assert(is_of_type, si::kilo_>>); static_assert( - is_of_type, per>>>); -static_assert(get_canonical_unit(u3).mag == mag<42'000'000>); + is_of_type, per>>>); +static_assert(get_canonical_unit(kJ_42).mag == mag<42'000'000>); // derived unit expression template syntax verification @@ -229,6 +231,14 @@ static_assert(is_of_type<1 / second * one, derived_unit>>); static_assert(is_of_type>); static_assert(is_of_type>>); +static_assert(is_of_type, derived_unit>>); +static_assert(is_of_type, derived_unit>>); +static_assert(is_of_type * metre, derived_unit>>); +static_assert(is_of_type, derived_unit>>); + +static_assert(is_of_type>>); +static_assert(is_of_type, derived_unit>>>); +static_assert(is_of_type / second, derived_unit>>>); static_assert(is_of_type, second_>>); static_assert(is_of_type, second_>>); @@ -254,40 +264,52 @@ static_assert(is_of_type); static_assert(is_of_type>>); static_assert(is_of_type>>); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v)>); + // derived unit normalization -constexpr auto u4 = metre / second; -static_assert(is_of_type>>); -static_assert(get_canonical_unit(u4).mag == mag<1>); +constexpr auto m_per_s = metre / second; +static_assert(is_of_type>>); +static_assert(get_canonical_unit(m_per_s).mag == mag<1>); -constexpr auto u5 = kilometre / second; -static_assert(is_of_type>>); -static_assert(is_of_type>>); -static_assert(get_canonical_unit(u5).mag == mag<1000>); +constexpr auto km_per_s = kilometre / second; +static_assert(is_of_type>>); +static_assert(is_of_type>>); +static_assert(get_canonical_unit(km_per_s).mag == mag<1000>); -constexpr auto u6 = kilometre / hour; -static_assert(is_of_type>>); -static_assert(is_of_type>>); -static_assert(get_canonical_unit(u6).mag == mag); +constexpr auto km_per_h = kilometre / hour; +static_assert(is_of_type>>); +static_assert(is_of_type>>); +static_assert(get_canonical_unit(km_per_h).mag == mag); -constexpr auto u7 = mag<1000> * kilometre / hour; -static_assert(is_of_type, kilometre_>, per>>); -static_assert(is_of_type>>); -static_assert(get_canonical_unit(u7).mag == mag); +// operations commutativity +constexpr auto u1 = mag<1000> * kilometre / hour; +static_assert(is_of_type, derived_unit>>>); +static_assert(is_of_type>>); +static_assert(get_canonical_unit(u1).mag == mag); -constexpr auto u8 = mag<1000> * (kilometre / hour); -static_assert(is_of_type, derived_unit>>>); -static_assert(is_of_type>>); -static_assert(get_canonical_unit(u8).mag == mag); +constexpr auto u2 = mag<1000> * (kilometre / hour); +static_assert(is_of_type, derived_unit>>>); +static_assert(is_of_type>>); +static_assert(get_canonical_unit(u2).mag == mag); -constexpr auto u9 = 1 / hour * (mag<1000> * kilometre); -static_assert(is_of_type, kilometre_>, per>>); -static_assert(is_of_type>>); -static_assert(get_canonical_unit(u9).mag == mag); +constexpr auto u3 = 1 / hour * (mag<1000> * kilometre); +static_assert(is_of_type, derived_unit>>>); +static_assert(is_of_type>>); +static_assert(get_canonical_unit(u3).mag == mag); // comparisons of the same units static_assert(second == second); static_assert(metre / second == metre / second); +static_assert(si::milli / si::milli == si::micro / si::micro); +static_assert(si::milli / si::micro == si::micro / si::nano); +static_assert(si::micro / si::milli == si::nano / si::micro); +static_assert(si::milli * si::kilo == si::deci * si::deca); +static_assert(si::kilo * si::milli == si::deca * si::deci); // comparisons of equivalent units (named vs unnamed/derived) static_assert(1 / second == hertz); @@ -312,109 +334,24 @@ static_assert(convertible(mag<100> * metre, kilometre)); static_assert(si::milli != kilometre); static_assert(convertible(si::milli, kilometre)); +// comparisons of non-convertible units +static_assert(metre != metre * metre); +static_assert(!convertible(metre, metre* metre)); + // one +static_assert(is_of_type); 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(hertz * second == one); -// 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); - - -// milli / milli == micro / micro; -// milli * kilo == deci * deca; - -// Bq + Hz should not compile - -// Bq + Hz + 1/s should compile? - - -// using namespace units; -// using namespace units::isq; - -// struct metre : named_unit {}; -// struct centimetre : prefixed_unit {}; -// struct kilometre : prefixed_unit {}; -// struct yard : named_scaled_unit(), metre> {}; -// struct foot : named_scaled_unit(), yard> {}; -// struct dim_length : base_dimension<"length", metre> {}; - -// struct second : named_unit {}; -// struct hour : named_scaled_unit(), second> {}; -// struct dim_time : base_dimension<"time", second> {}; - -// struct kelvin : named_unit {}; - -// #if !UNITS_COMP_MSVC -// static_assert([](P) { -// return !requires { typename prefixed_unit; }; -// }(si::kilo{})); // no prefix allowed -// #endif - -// struct metre_per_second : derived_unit {}; -// struct dim_speed : -// derived_dimension, units::exponent> {}; -// struct kilometre_per_hour : derived_scaled_unit {}; - -// static_assert(equivalent); -// static_assert(equivalent); -// static_assert(compare(), metre>>, metre>); -// static_assert(compare(), metre>>, centimetre>); -// static_assert(compare>, yard>); -// static_assert(compare(), metre>>, foot>); -// static_assert(compare>, kilometre_per_hour>); +static_assert(hertz == 1 / second); +static_assert(newton == kilogram * metre / square); +static_assert(joule == kilogram * square / square); +static_assert(joule == newton * metre); +static_assert(watt == joule / second); +static_assert(watt == kilogram * square / cubic); // 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)); - - } // namespace