diff --git a/src/core/include/units/unit.h b/src/core/include/units/unit.h index d386dc96..7a1f99b8 100644 --- a/src/core/include/units/unit.h +++ b/src/core/include/units/unit.h @@ -220,6 +220,53 @@ template concept NamedUnit = Unit && requires(T* t) { detail::to_base_specialization_of_named_unit(t); } && (!detail::is_specialization_of_named_unit); + +/** + * @brief A unit of a physical constant + * + * Defines a unit of a physical constant together with its value encoded as a unit ratio. + * + * This allows moving all of the constant-related ratio manipulation to the compile-time domain + * (i.e. multiplying and then dividing by the same constant will eliminate the item from the final + * type). + * As a result we have faster runtime performance and no precision loss due to eager floating-point + * operations. Also, if the user prefers integral types for a quantity representation, this will + * not force the user to convert to a floating-point type right away. Only when a final quantity + * number needs to actually account for the constant value, the floating-point operation (if any) + * can be triggered lazily with the `quantity_cast()`. + * + * For example: + * + * @code{.cpp} + * inline constexpr struct standard_gravity_unit : + * constant_unit<"g", mag * metre / square> {} standard_gravity_unit; + * @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 constant + * + * @note Constant symbol is printed in the text output encapsulated inside square brackets `[]` + * and before any regular units + */ +template + requires(!Symbol.empty()) +struct constant_unit : named_unit<'[' + Symbol + ']', U> {}; + +namespace detail { + +template +void to_base_specialization_of_constant_unit(const volatile constant_unit*); + +template +inline constexpr bool is_derived_from_specialization_of_constant_unit = + requires(T * t) { to_base_specialization_of_constant_unit(t); }; + +} // namespace detail + /** * @brief Prevents assignment of a prefix to specific units * @@ -444,11 +491,22 @@ template [[nodiscard]] consteval auto get_canonical_unit(Unit auto u) { return get_canonical_unit_impl(u, u); } +template +[[nodiscard]] consteval bool less(Lhs, Rhs) +{ + if ((is_derived_from_specialization_of_constant_unit && is_derived_from_specialization_of_constant_unit) || + (!is_derived_from_specialization_of_constant_unit && !is_derived_from_specialization_of_constant_unit)) + return type_name() < type_name(); + else + return is_derived_from_specialization_of_constant_unit; +} + + // 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()> {}; +struct unit_less : std::bool_constant {}; template using type_list_of_unit_less = expr_less; diff --git a/test/unit_test/static/unit_test.cpp b/test/unit_test/static/unit_test.cpp index 0b470bd3..29524d82 100644 --- a/test/unit_test/static/unit_test.cpp +++ b/test/unit_test/static/unit_test.cpp @@ -80,6 +80,11 @@ inline constexpr struct mile_ : named_unit<"mi", mag<1760> * yard> {} mile; inline constexpr struct kilometre_ : decltype(si::kilo) {} kilometre; inline constexpr struct kilojoule_ : decltype(si::kilo) {} kilojoule; + +// physical constant units +inline constexpr struct standard_gravity_unit_ : constant_unit<"g", mag * metre / square> {} standard_gravity_unit; +inline constexpr struct speed_of_light_unit_ : constant_unit<"c", mag<299'792'458> * metre / second> {} speed_of_light_unit; + // clang-format on // concepts verification @@ -179,6 +184,17 @@ static_assert(joule != newton); static_assert(is_of_type); +// constant_unit +static_assert(is_of_type); +static_assert( + is_of_type>>>); +static_assert(get_canonical_unit(standard_gravity_unit).mag == mag); +static_assert(interconvertible(standard_gravity_unit, standard_gravity_unit)); +static_assert(interconvertible(standard_gravity_unit, metre / square)); +static_assert(standard_gravity_unit == standard_gravity_unit); +static_assert(standard_gravity_unit != metre / square); // magnitude is different +static_assert(standard_gravity_unit.symbol == "[g]"); + // prefixed_unit static_assert(is_of_type); static_assert(is_of_type); @@ -308,6 +324,16 @@ static_assert(is_of_type / gram, derived_unit / gram) * one, derived_unit, per>>); static_assert(is_of_type / gram * one, derived_unit, per>>); +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); +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); @@ -338,6 +364,14 @@ static_assert( static_assert( is_of_type, per>>); +static_assert( + is_of_type>>>); +static_assert(get_canonical_unit(standard_gravity_unit).mag == mag); +static_assert(is_of_type>>>); +static_assert(is_of_type>>); + // operations commutativity constexpr auto u1 = mag<1000> * kilometre / hour; static_assert(is_of_type, derived_unit>>>); @@ -496,6 +530,7 @@ static_assert(is_of_type, derived_unit>>>); static_assert(is_of_type, metre_>>); static_assert(is_of_type, metre_>>); +static_assert(is_of_type>>); // unit symbols #ifdef __cpp_lib_constexpr_string @@ -571,6 +606,11 @@ static_assert(unit_symbol(pow<1, 2>(metre)) == "m^(1/2)"); static_assert(unit_symbol(pow<3, 5>(metre)) == "m^(3/5)"); static_assert(unit_symbol(pow<1, 2>(metre / second)) == "m^(1/2)/s^(1/2)"); +// Physical constants +static_assert(unit_symbol(speed_of_light_unit) == "[c]"); +static_assert(unit_symbol(gram * standard_gravity_unit * speed_of_light_unit) == "[c] [g] g"); +static_assert(unit_symbol(gram / standard_gravity_unit) == "g/[g]"); + #endif // __cpp_lib_constexpr_string } // namespace