diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index e69de29b..00000000 diff --git a/cmake/warnings.cmake b/cmake/warnings.cmake index 03c8fac3..ce7feda0 100644 --- a/cmake/warnings.cmake +++ b/cmake/warnings.cmake @@ -39,7 +39,7 @@ function(set_warnings target scope) set(MSVC_WARNINGS /W4 # Baseline reasonable warnings /w14062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled - /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data + # /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data /w14263 # 'function': member function does not override any base class virtual member function /w14265 # 'classname': class has virtual functions, but destructor is not diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3e8b05ee..d3d7b985 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,8 +3,10 @@ - **0.7.0 WIP** - (!) refactor: `ScalableNumber` renamed to `QuantityValue` - (!) refactor: output stream operators moved to the `units/quantity_io.h` header file + - refactor: `quantity_point` (partially) updated to reflect latest changes to `quantity` - refactor: basic concepts, `quantity` and `quantity_cast` refactored - refactor: `abs()` definition refactored to be more explicit about the return type + - feat: quantity (point) kind support added (thanks [@johelegp](https://github.com/johelegp)) - feat: unit constants support added (thanks [@johelegp](https://github.com/johelegp)) - feat: interoperability with `std::chrono::duration` and other units libraries - feat: CTAD for dimensionless quantity added diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 7e40163c..7769a311 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -70,6 +70,7 @@ set(unitsSphinxDocs "${CMAKE_CURRENT_SOURCE_DIR}/framework/dimensions.rst" "${CMAKE_CURRENT_SOURCE_DIR}/framework/quantities.rst" "${CMAKE_CURRENT_SOURCE_DIR}/framework/quantity_points.rst" + "${CMAKE_CURRENT_SOURCE_DIR}/framework/quantity_kinds.rst" "${CMAKE_CURRENT_SOURCE_DIR}/framework/text_output.rst" "${CMAKE_CURRENT_SOURCE_DIR}/framework/units.rst" diff --git a/docs/_static/img/concepts.png b/docs/_static/img/concepts.png index 3bf55b5b..31b56e8e 100644 Binary files a/docs/_static/img/concepts.png and b/docs/_static/img/concepts.png differ diff --git a/docs/examples/glide_computer.rst b/docs/examples/glide_computer.rst index fbf340d2..df59ce76 100644 --- a/docs/examples/glide_computer.rst +++ b/docs/examples/glide_computer.rst @@ -5,18 +5,18 @@ glide_computer This example presents the usage of: -- different units for length, time, and velocity, +- different kinds for length, time, and velocity, - quantities text output formatting, -- `quantity_point` to mark "absolute" values like ``altitude`` (as opposed to ``height``), -- a simple ``vector`` class to show how to handle 3D space for aviation needs and how to build abstractions - on top of a `Quantity` concept. +- the use of quantity kinds to show how to handle 3D space for aviation needs, +- `quantity_point_kind` to mark "absolute" values like ``altitude`` (as opposed to ``height``). -``vector`` class template provides a strong type that divides quantities and quantity points to the ones -on a horizontal (X, Y) plane and vertical (Z) axis. Its purpose is to make different kinds of quantity -(i.e. length) separated strong types (i.e. distance, height). Additionally, it wraps both a `quantity` -and `quantity_point` types which means that we can define a ``height`` and ``altitude`` respectively -as different strong types. Some of its function are defined only for quantities (additional operators) -and some for quantity points (additional constructor). +The use of `quantity_kind` and `quantity_point_kind` provide strong typing +that divide quantities and quantity points to the ones on a horizontal (X, Y) plane and vertical (Z) axis. +Their purpose is to make different kinds of quantity +(i.e. length) separate strong types (i.e. distance, height). +Additionally, separating a quantity from its quantity point +means that we can define a ``height`` and ``altitude`` as different strong types. +A quantity point provides a more restricted interface meant for absolute calculations. .. literalinclude:: ../../example/glide_computer.cpp :caption: glide_computer.cpp diff --git a/docs/framework/basic_concepts.rst b/docs/framework/basic_concepts.rst index 74db7a8a..f2561ea7 100644 --- a/docs/framework/basic_concepts.rst +++ b/docs/framework/basic_concepts.rst @@ -4,7 +4,7 @@ Basic Concepts ============== The most important concepts in the library are `Unit`, `Dimension`, -`Quantity`, and `QuantityPoint`: +`Quantity`, `QuantityPoint`, `QuantityKind`, and `QuantityPointKind`: .. image:: /_static/img/concepts.png :align: center @@ -18,15 +18,35 @@ The most important concepts in the library are `Unit`, `Dimension`, [exponent]<-[derived_dimension] ] + [Quantity| + [quantity] + ] + [QuantityPoint| [quantity_point] ] + [QuantityKind| + [quantity_kind] + ] + + [QuantityPointKind| + [quantity_point_kind] + ] + [Unit]<-[Dimension] [Dimension]<-[Quantity] [Unit]<-[Quantity] [Quantity]<-[QuantityPoint] + [Kind]<-[QuantityKind] + [Dimension]<-[Kind] + [Quantity]<-[QuantityKind] + + [PointKind]<-[QuantityPointKind] + [Kind]<-[PointKind] + [QuantityKind]-<[QuantityPointKind] + `Unit` is a basic building block of the library. Every dimension works with a concrete hierarchy of units. Such hierarchy defines a reference unit and often a few scaled versions of it. @@ -40,3 +60,7 @@ derived dimensions. specific representation. `QuantityPoint` is an absolute `Quantity` with respect to some origin. + +`QuantityKind` is a `Quantity` with more specific usage. + +`QuantityPointKind` is an absolute `QuantityKind` with respect to some origin. diff --git a/docs/framework/conversions_and_casting.rst b/docs/framework/conversions_and_casting.rst index 52fe490f..7f6deaac 100644 --- a/docs/framework/conversions_and_casting.rst +++ b/docs/framework/conversions_and_casting.rst @@ -56,7 +56,8 @@ Explicit -------- Explicit conversions are available with -the `quantity_cast` and `quantity_point_cast` function templates. +the `quantity_cast`, `quantity_point_cast`, +`quantity_kind_cast`, and `quantity_point_kind_cast` function templates. They are especially useful to force a truncating conversion across quantities of the same dimension for integral representation types and ratios that may cause precision loss:: @@ -99,6 +100,13 @@ or a specific target `quantity_point`:: std::cout << "Point: " << quantity_point_cast(d) << '\n'; +For a single explicit template argument, +`quantity_point_kind_cast` is a superset of `quantity_kind_cast`, +which is also a superset of `quantity_cast`. +For the (dimension, unit) pair of explicit arguments case of `quantity_cast`, +`quantity_point_kind_cast` and `quantity_kind_cast` +accept a `PointKind` and `Kind` instead of a `Dimension`, respectively. + .. seealso:: For more information on conversion and casting and on how to extend the above "integral" @@ -115,14 +123,14 @@ represent numbers it would be highly uncomfortable to every time type:: const auto d1 = 10_q_km; const auto d2 = 3_q_km; - if(d1 / d2 > dimensionless) { + if(d1 / d2 > dimensionless(2)) { // ... } or:: const auto fill_time_left = (box.height / box.fill_level(measured_mass) - - dimensionless) * fill_time; + dimensionless(1)) * fill_time; This is why it was decided to allow the ``dimensionless`` quantity of any representation type to be implicitly constructible from this representation type. @@ -147,7 +155,7 @@ could be ambiguous. For example:: return d1 / d2 + 1; } -As long as we can reason about what such code means for ``foo(10_q_km, 2_q_km)`` it is not that obvious +As long as we can reason about what such code means for ``foo(10_q_km, 2_q_km)`` it is not that obvious at all in the case of ``foo(10_q_cm, 2_q_ft)``. To make such code to compile for every case we have to either change the type of the resulting unit to the one having ``ratio(1)`` (:term:`coherent derived unit`):: diff --git a/docs/framework/dimensions.rst b/docs/framework/dimensions.rst index 98004b52..279f83a6 100644 --- a/docs/framework/dimensions.rst +++ b/docs/framework/dimensions.rst @@ -50,7 +50,7 @@ probably will always end up in a quantity of a yet another dimension: However, please note that there is an exception from the above rule. In case we divide the same dimensions, or multiply by the inverted -dimension, than we will end up with just a scalable number type: +dimension, than we will end up with just a dimensionless quantity: .. code-block:: :emphasize-lines: 4-5 @@ -58,8 +58,23 @@ dimension, than we will end up with just a scalable number type: Time auto dur1 = 10_q_s; Time auto dur2 = 2_q_s; Frequency auto fr1 = 5_q_Hz; - QuantityValue auto v1 = dur1 / dur2; // 5 - QuantityValue auto v2 = dur1 * fr1; // 50 + Dimensionless auto v1 = dur1 / dur2; // quantity(5) + Dimensionless auto v2 = dur1 * fr1; // quantity(50) + +Quantity kinds behave the same as quantities for addition, substraction, and +multiplication with, and division by a :term:`scalable number`. + +Multiplication and divison with a quantity +(but not a quantity kind) is allowed. +The result is a quantity kind with the appropiate dimension +and related to the original quantity kind. + + struct height : kind {}; + struct rate_of_climb : derived_kind {}; + + quantity_kind h(height{}, 100 * m); + quantity_point_kind rate = h / (25 * s); + // `quantity_point_kind(4 * m / s)` Quantity points have a more restricted set of operations. Quantity points can't be added together, @@ -81,7 +96,7 @@ The result will always be a quantity point of the same dimension: .. code-block:: :emphasize-lines: 3 - + Length auto dist1 = 2_q_m; Length auto dist2 = 1_q_m; auto res1 = dist1 - quantity_point{dist2}; // ERROR @@ -98,6 +113,9 @@ The result is a relative quantity of the same dimension: That's it! You can't multiply nor divide quantity points with anything else. +The same restrictions of a quantity point with respect to its quantity +apply to a quantity point kind with respect to its quantity kind. + Base Dimensions --------------- diff --git a/docs/framework/quantity_kinds.rst b/docs/framework/quantity_kinds.rst new file mode 100644 index 00000000..3ebb8a43 --- /dev/null +++ b/docs/framework/quantity_kinds.rst @@ -0,0 +1,72 @@ +.. namespace:: units + +Quantity Kinds +============== + +A quantity kind is a quantity of more specific usage. +It is represented in the library with a `quantity_kind` class template. + + +.. _quantity-point-construction: + +Kind creation +------------- + +We need a `kind` to represent the more specific usage of a quantity. + + struct radius : kind {}; + +Quantities of kind `radius` represent a radius. +They are clearly distinct from more generic usages of length quantities. + + +Construction +------------ + +To create the quantity kind object from a `quantity` we just have to pass +the value to the `quantity_kind` class template constructor:: + + quantity_kind d(123 * km); + +.. note:: + + As the constructor without the kind argument is explicit, + the quantity object can be created from a quantity only via + `direct initialization `_. + This is why the code below using + `copy initialization `_ + **does not compile**:: + + quantity_kind d = 123 * km; // ERROR + + +Differences to quantity +----------------------- + +Unlike `quantity`, the library provides: + +- no kinds, such as ``radius`` or ``width``, therefore + * no UDLs or unit constants, + * no kind-specific concepts, such as ``Radius``, + (there's the generic `QuantityKind` and kind-specifiable `QuantityKindOf`), +- a slightly different set of operations on quantity kinds + (see the :ref:`Dimensions` chapter) + +Quantity point kind +------------------- + +A quantity point kind is the analogous of a quantity point for quantity kinds. +(see the :ref:`Quantity Points` chapter). + +They are represented in the library with a `quantity_point_kind` class template. + +First, we need a `point_kind` for a `kind`. + + struct width : kind {}; + struct x_coordinate : point_kind {}; + +Now x coordinates can be constructed: + + quantity_point_kind auto x_pos(123 * m); + // `QuantityPointKindOf` with `x_pos.relative()` + // equal to `quantity_kind(123 * m)` diff --git a/docs/framework/quantity_points.rst b/docs/framework/quantity_points.rst index 87fea78c..4652da66 100644 --- a/docs/framework/quantity_points.rst +++ b/docs/framework/quantity_points.rst @@ -40,4 +40,4 @@ Unlike `quantity`, the library provides: - no dimension-specific concepts, such as ``LengthPoint`` (there's the dimension-agnostic `QuantityPoint`), - a more limited set of operations on quantity points - (see the :ref:`Conversions and Casting` chapter) + (see the :ref:`Dimensions` chapter) diff --git a/docs/framework/text_output.rst b/docs/framework/text_output.rst index 9172d21b..a714e24f 100644 --- a/docs/framework/text_output.rst +++ b/docs/framework/text_output.rst @@ -8,13 +8,14 @@ also tries really hard to print any quantity in the most user friendly way. .. note:: - The library provides no text output for quantity points. + The library provides no text output for + quantity points or quantity (point) kinds. Output Streams -------------- .. tip:: - + The streaming support is provided via the ```` header file. diff --git a/docs/usage.rst b/docs/usage.rst index 6973adec..1cc3c9a6 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,7 +18,7 @@ This repository contains three independent CMake-based projects: but until then it depends on: - `{fmt} `_ to provide text formatting of quantities. - - `gsl-lite `_ to verify runtime contracts with ``Expects`` macro. + - `gsl-lite `_ to verify runtime contracts with the ``gsl_Expects`` macro. - *.* diff --git a/docs/use_cases/legacy_interfaces.rst b/docs/use_cases/legacy_interfaces.rst index 1f099753..4b6834b4 100644 --- a/docs/use_cases/legacy_interfaces.rst +++ b/docs/use_cases/legacy_interfaces.rst @@ -4,8 +4,12 @@ Working with Legacy Interfaces ============================== In case we are working with a legacy/unsafe interface we may be forced to -extract the :term:`value of a quantity` with :func:`quantity::count()` or to -extract the value of a `quantity_point` with :func:`quantity_point::relative()` +extract the :term:`value of a quantity` with :func:`quantity::count()` +(in addition +to the quantity of a `quantity_point` with :func:`quantity_point::relative()`, +or the quantity of a `quantity_kind` with :func:`quantity_kind::common()`, +or the quantity kind of a `quantity_point_kind` +with :func:`quantity_point_kind::relative()`) and pass it to the library's output: .. code-block:: diff --git a/example/glide_computer.cpp b/example/glide_computer.cpp index dc17d878..d3576ac9 100644 --- a/example/glide_computer.cpp +++ b/example/glide_computer.cpp @@ -24,130 +24,17 @@ #include #include #include -#include +#include #include #include #include -// horizontal/vertical vector -namespace { - -using namespace units; - -enum class direction { - horizontal, - vertical -}; - -template - requires Quantity || QuantityPoint -class vector { -public: - using value_type = Q; - using magnitude_type = Q; - static constexpr direction dir = D; - - vector() = default; - explicit constexpr vector(const Q& m): magnitude_(m) {} - - template - requires QuantityPoint && std::constructible_from - explicit constexpr vector(const QQ& q) : magnitude_(q) {} - - constexpr Q magnitude() const { return magnitude_; } - - [[nodiscard]] constexpr vector operator-() const - requires requires { -magnitude(); } - { - return vector(-magnitude()); - } - - template - constexpr vector& operator-=(const vector& v) - requires requires(Q q) { q -= v.magnitude(); } - { - magnitude_ -= v.magnitude(); - return *this; - } - - template - [[nodiscard]] friend constexpr auto operator+(const vector& lhs, const vector& rhs) - requires requires { lhs.magnitude() + rhs.magnitude(); } - { - using ret_type = decltype(lhs.magnitude() + rhs.magnitude()); - return vector(lhs.magnitude() + rhs.magnitude()); - } - - template - [[nodiscard]] friend constexpr auto operator-(const vector& lhs, const vector& rhs) - requires requires { lhs.magnitude() - rhs.magnitude(); } - { - using ret_type = decltype(lhs.magnitude() - rhs.magnitude()); - return vector(lhs.magnitude() - rhs.magnitude()); - } - - template - requires (QuantityValue || Dimensionless) - [[nodiscard]] friend constexpr auto operator*(const vector& lhs, const V& value) - requires requires { lhs.magnitude() * value; } - { - return vector(lhs.magnitude() * value); - } - - template - requires (QuantityValue || Dimensionless) - [[nodiscard]] friend constexpr auto operator*(const V& value, const vector& rhs) - requires requires { value * rhs.magnitude(); } - { - return vector(value * rhs.magnitude()); - } - - template - [[nodiscard]] friend constexpr auto operator/(const vector& lhs, const vector& rhs) - requires requires { lhs.magnitude() / rhs.magnitude(); } - { - return lhs.magnitude() / rhs.magnitude(); - } - - template - [[nodiscard]] friend constexpr auto operator<=>(const vector& lhs, const vector& rhs) - requires requires { lhs.magnitude() <=> rhs.magnitude(); } - { - return lhs.magnitude() <=> rhs.magnitude(); - } - - template - [[nodiscard]] friend constexpr bool operator==(const vector& lhs, const vector& rhs) - requires requires { lhs.magnitude() == rhs.magnitude(); } - { - return lhs.magnitude() == rhs.magnitude(); - } - - template - requires Quantity - friend std::basic_ostream& operator<<(std::basic_ostream& os, const vector& v) - { - return os << v.magnitude(); - } - -private: - Q magnitude_{}; -}; - - -template -inline constexpr bool is_vector = false; -template -inline constexpr bool is_vector> = true; - -} // namespace - -template -struct fmt::formatter> : formatter { +template +struct fmt::formatter : formatter { template - auto format(const vector& v, FormatContext& ctx) + auto format(const QK& v, FormatContext& ctx) { - return formatter::format(v.magnitude(), ctx); + return formatter::format(v.common(), ctx); } }; @@ -155,20 +42,27 @@ struct fmt::formatter> : formatter { namespace { using namespace units::physical; +using namespace units; -using distance = vector, direction::horizontal>; -using height = vector, direction::vertical>; -using altitude = vector, direction::vertical>; +struct horizontal_vector : kind {}; +struct vertical_vector : kind {}; +struct vertical_point : point_kind {}; +struct velocity_vector : derived_kind {}; +struct rate_of_climb_vector : derived_kind {}; + +using distance = quantity_kind; +using height = quantity_kind; +using altitude = quantity_point_kind; using duration = si::time; -using velocity = vector, direction::horizontal>; -using rate_of_climb = vector, direction::vertical>; +using velocity = quantity_kind; +using rate_of_climb = quantity_kind; template std::basic_ostream& operator<<(std::basic_ostream& os, const altitude& a) { - return os << a.magnitude().relative() << " AMSL"; + return os << a.relative().common() << " AMSL"; } } // namespace @@ -178,7 +72,7 @@ struct fmt::formatter : formatter> { template auto format(altitude a, FormatContext& ctx) { - formatter>::format(a.magnitude().relative(), ctx); + formatter>::format(a.relative().common(), ctx); return format_to(ctx.out(), " AMSL"); } }; @@ -201,8 +95,15 @@ struct fmt::formatter : formatter> { // gliders database namespace { -using namespace units::physical::si::literals; -using namespace units::physical::si::international::literals; +using namespace si::literals; +using namespace si::international::literals; +using namespace si::unit_constants; + +template +constexpr Quantity auto operator/(const QK1& lhs, const QK2& rhs) + requires requires { lhs.common() / rhs.common(); } { + return lhs.common() / rhs.common(); +} struct glider { struct polar_point { @@ -217,10 +118,10 @@ struct glider { auto get_gliders() { const std::array gliders = { - glider{"SZD-30 Pirat", {velocity(83_q_km_per_h), rate_of_climb(-0.7389_q_m_per_s)}}, - glider{"SZD-51 Junior", {velocity(80_q_km_per_h), rate_of_climb(-0.6349_q_m_per_s)}}, - glider{"SZD-48 Jantar Std 3", {velocity(110_q_km_per_h), rate_of_climb(-0.77355_q_m_per_s)}}, - glider{"SZD-56 Diana", {velocity(110_q_km_per_h), rate_of_climb(-0.63657_q_m_per_s)}} + glider{"SZD-30 Pirat", {velocity(83 * km / h), rate_of_climb(-0.7389 * m / s)}}, + glider{"SZD-51 Junior", {velocity(80 * km / h), rate_of_climb(-0.6349 * m / s)}}, + glider{"SZD-48 Jantar Std 3", {velocity(110 * km / h), rate_of_climb(-0.77355 * m / s)}}, + glider{"SZD-56 Diana", {velocity(110 * km / h), rate_of_climb(-0.63657 * m / s)}} }; return gliders; } @@ -235,7 +136,7 @@ template void print(const R& gliders) { std::cout << "Gliders:\n"; - std::cout << "========\n"; + std::cout << "========\n"; for(const auto& g : gliders) { std::cout << "- Name: " << g.name << "\n"; std::cout << "- Polar:\n"; @@ -258,9 +159,9 @@ struct weather { auto get_weather_conditions() { const std::array weather_conditions = { - std::pair("Good", weather{height(1900_q_m), rate_of_climb(4.3_q_m_per_s)}), - std::pair("Medium", weather{height(1550_q_m), rate_of_climb(2.8_q_m_per_s)}), - std::pair("Bad", weather{height(850_q_m), rate_of_climb(1.8_q_m_per_s)}) + std::pair("Good", weather{height(1900 * m), rate_of_climb(4.3 * m / s)}), + std::pair("Medium", weather{height(1550 * m), rate_of_climb(2.8 * m / s)}), + std::pair("Bad", weather{height(850 * m), rate_of_climb(1.8 * m / s)}) }; return weather_conditions; } @@ -270,7 +171,7 @@ template void print(const R& conditions) { std::cout << "Weather:\n"; - std::cout << "========\n"; + std::cout << "========\n"; for(const auto& c : conditions) { std::cout << "- Kind: " << c.first << "\n"; const auto& w = c.second; @@ -409,12 +310,12 @@ flight_point circle(const flight_point& point, const glider& g, const weather& w constexpr distance glide_distance(const flight_point& point, const glider& g, const task& t, const safety& s, altitude ground_alt) { const auto dist_to_finish = t.dist - point.dist; - return distance((ground_alt + s.min_agl_height - point.alt).magnitude() / ((ground_alt - t.finish.alt) / dist_to_finish - 1 / glide_ratio(g.polar[0]))); + return distance((ground_alt + s.min_agl_height - point.alt).common() / ((ground_alt - t.finish.alt) / dist_to_finish - 1 / glide_ratio(g.polar[0]))); } inline si::length length_3d(distance dist, height h) { - return sqrt(pow<2>(dist.magnitude()) + pow<2>(h.magnitude())); + return sqrt(pow<2>(dist.common()) + pow<2>(h.common())); } flight_point glide(const flight_point& point, const glider& g, const task& t, const safety& s) @@ -423,7 +324,7 @@ flight_point glide(const flight_point& point, const glider& g, const task& t, co const auto dist = glide_distance(point, g, t, s, ground_alt); const auto alt = ground_alt + s.min_agl_height; const auto l3d = length_3d(dist, point.alt - alt); - const duration d = l3d / g.polar[0].v.magnitude(); + const duration d = l3d / g.polar[0].v.common(); const flight_point new_point{point.dur + d, point.dist + dist, terrain_level_alt(t, point.dist + dist) + s.min_agl_height}; print("Glide", point, new_point); @@ -434,7 +335,7 @@ flight_point final_glide(const flight_point& point, const glider& g, const task& { const auto dist = t.dist - point.dist; const auto l3d = length_3d(dist, point.alt - t.finish.alt); - const duration d = l3d / g.polar[0].v.magnitude(); + const duration d = l3d / g.polar[0].v.common(); const flight_point new_point{point.dur + d, point.dist + dist, t.finish.alt}; print("Final Glide", point, new_point); @@ -450,7 +351,7 @@ void estimate(const glider& g, const weather& w, const task& t, const safety& s, point = tow(point, at); // estimate the altitude needed to reach the finish line from this place - const altitude final_glide_alt = t.finish.alt + height(t.dist.magnitude() / glide_ratio(g.polar[0])); + const altitude final_glide_alt = t.finish.alt + height(t.dist.common() / glide_ratio(g.polar[0])); // how much height we still need to gain in the thermalls to reach the destination? height height_to_gain = final_glide_alt - point.alt; @@ -462,7 +363,7 @@ void estimate(const glider& g, const weather& w, const task& t, const safety& s, // circle in a thermall to gain height point = circle(point, g, w, t, height_to_gain); } - while(height_to_gain > height(0_q_m)); + while(height_to_gain > height(0 * m)); // final glide point = final_glide(point, g, t); @@ -488,10 +389,10 @@ void example() }; print(t); - const safety s = {height(300_q_m)}; + const safety s = {height(300 * m)}; print(s); - const aircraft_tow tow = {height(400_q_m), rate_of_climb(1.6_q_m_per_s)}; + const aircraft_tow tow = {height(400 * m), rate_of_climb(1.6 * m / ::s)}; print(tow); for(const auto& g : gliders) { diff --git a/src/include/units/bits/basic_concepts.h b/src/include/units/bits/basic_concepts.h index cb068f29..43602c86 100644 --- a/src/include/units/bits/basic_concepts.h +++ b/src/include/units/bits/basic_concepts.h @@ -202,7 +202,51 @@ concept UnitOf = Dimension && std::same_as::reference>; -// Quantity, QuantityPoint +// Kind +namespace detail { + +template +struct _kind_base; + +} // namespace detail + +template typename Base> +concept kind_impl_ = + is_derived_from_specialization_of && + requires(T* t) { + typename T::base_kind; + typename T::dimension; + requires Dimension; + }; + +/** + * @brief A concept matching all kind types + * + * Satisfied by all kind types derived from an specialization of :class:`kind`. + */ +template +concept Kind = + kind_impl_ && + kind_impl_ && + std::same_as; + +// PointKind +namespace detail { + +template +struct _point_kind_base; + +} // namespace detail + +/** + * @brief A concept matching all point kind types + * + * Satisfied by all point kind types derived from an specialization of :class:`point_kind`. + */ +template +concept PointKind = kind_impl_; + +// Quantity, QuantityPoint, QuantityKind, QuantityPointKind namespace detail { template @@ -211,6 +255,12 @@ inline constexpr bool is_quantity = false; template inline constexpr bool is_quantity_point = false; +template +inline constexpr bool is_quantity_kind = false; + +template +inline constexpr bool is_quantity_point_kind = false; + } // namespace detail /** @@ -229,9 +279,27 @@ concept Quantity = detail::is_quantity; template concept QuantityPoint = detail::is_quantity_point; +/** + * @brief A concept matching all quantity kinds in the library. + * + * Satisfied by all specializations of @c quantity_kind. + */ +template +concept QuantityKind = detail::is_quantity_kind; + +/** + * @brief A concept matching all quantity point kinds in the library. + * + * Satisfied by all specializations of @c quantity_point_kind. + */ +template +concept QuantityPointKind = detail::is_quantity_point_kind; + +// QuantityLike + /** * @brief A concept matching all quantity-like types (other than specialization of @c quantity) - * + * * Satisfied by all types for which a correct specialization of `quantity_like_traits` * type trait is provided. */ @@ -261,12 +329,12 @@ concept castable_number_ = // exposition only common_type_with_ && scalable_number_>; -template +template concept scalable_ = // exposition only castable_number_ || (requires { typename T::value_type; } && castable_number_ && scalable_number_>); -template +template concept scalable_with_ = // exposition only common_type_with_ && scalable_>; @@ -281,6 +349,10 @@ template requires requires { typename T::value_type; } inline constexpr bool is_wrapped_quantity = Quantity || QuantityLike || is_wrapped_quantity; +template + requires requires { typename T::quantity_type; } +inline constexpr bool is_wrapped_quantity = Quantity; + } // namespace detail /** diff --git a/src/include/units/bits/common_quantity.h b/src/include/units/bits/common_quantity.h index 00c4b2b3..a7970816 100644 --- a/src/include/units/bits/common_quantity.h +++ b/src/include/units/bits/common_quantity.h @@ -34,6 +34,12 @@ class quantity; template U, QuantityValue Rep> class quantity_point; +template U, QuantityValue Rep> +class quantity_kind; + +template U, QuantityValue Rep> +class quantity_point_kind; + namespace detail { template @@ -65,33 +71,53 @@ struct common_quantity_impl, quantity, Rep> using type = quantity; }; -template -quantity_point common_quantity_point_impl(quantity); - } // namespace detail template Q2, QuantityValue Rep = std::common_type_t> using common_quantity = TYPENAME detail::common_quantity_impl::type; -template - requires requires { typename common_quantity; } -using common_quantity_point = decltype( - detail::common_quantity_point_impl(common_quantity{})); +template QP2> +using common_quantity_point = std::common_type_t; + +template QK2> +using common_quantity_kind = std::common_type_t; + +template QPK2> +using common_quantity_point_kind = std::common_type_t; } // namespace units namespace std { -template - requires units::equivalent +template Q2> + requires requires { typename common_type_t; } struct common_type { using type = units::common_quantity; }; -template - requires requires { typename units::common_quantity_point; } +template QP2> + requires requires { typename common_type_t; } struct common_type { - using type = units::common_quantity_point; + using type = units::quantity_point< + typename common_type_t::dimension, + typename common_type_t::unit, + typename common_type_t::rep>; +}; + +template QK2> + requires requires { typename common_type_t; } +struct common_type { + using type = units::quantity_kind::unit, + typename common_type_t::rep>; +}; + +template QPK2> + requires requires { typename common_type_t; } +struct common_type { + using type = units::quantity_point_kind::unit, + typename common_type_t::rep>; }; } diff --git a/src/include/units/bits/equivalent.h b/src/include/units/bits/equivalent.h index 898e6426..80825870 100644 --- a/src/include/units/bits/equivalent.h +++ b/src/include/units/bits/equivalent.h @@ -41,7 +41,7 @@ struct equivalent_impl : std::true_type { // units template -struct equivalent_impl : std::is_base_of, std::is_base_of {}; +struct equivalent_impl : std::disjunction, std::is_base_of> {}; // dimensions @@ -81,7 +81,17 @@ template struct equivalent_unit : std::disjunction, std::bool_constant::ratio == U2::ratio / dimension_unit::ratio>> {}; -// quantities and quantity points + +// (point) kinds + +template + requires (Kind && Kind) || (PointKind && PointKind) +struct equivalent_impl : + std::conjunction, + equivalent_impl> {}; + + +// quantities, quantity points, quantity (point) kinds template requires (Quantity && Quantity) || (QuantityPoint && QuantityPoint) @@ -89,6 +99,11 @@ struct equivalent_impl : std::conjunction> {}; +template + requires (QuantityKind && QuantityKind) || (QuantityPointKind && QuantityPointKind) +struct equivalent_impl : std::conjunction, + equivalent_impl> {}; + } // namespace detail template diff --git a/src/include/units/bits/quantity_of.h b/src/include/units/bits/quantity_of.h index 27841311..cd319cab 100644 --- a/src/include/units/bits/quantity_of.h +++ b/src/include/units/bits/quantity_of.h @@ -79,4 +79,41 @@ concept QuantityOf = Quantity && Dimension && equivalent concept QuantityEquivalentTo = Quantity && QuantityOf; +/** + * @brief A concept matching all quantity points with provided dimension + * + * Satisfied by all quantity points with a dimension being the instantiation derived from + * the provided dimension type. + */ +template +concept QuantityPointOf = QuantityPoint && Dimension && equivalent; + +template +concept QuantityPointEquivalentTo = QuantityPoint && QuantityPointOf; + +/** + * @brief A concept matching only quantity kinds of a specific kind. + * + * @tparam QK Quantity kind to verify. + * @tparam K Kind type to use for verification. + */ +template +concept QuantityKindOf = QuantityKind && Kind && equivalent; + +template +concept QuantityKindEquivalentTo = QuantityKind && QuantityKindOf; + +/** + * @brief A concept matching only quantity point kinds of a specific point kind. + * + * @tparam QPK Quantity point kind to verify. + * @tparam PK Point kind type to use for verification. + */ +template +concept QuantityPointKindOf = QuantityPointKind && PointKind && equivalent; + +template +concept QuantityPointKindEquivalentTo = + QuantityPointKind && QuantityPointKindOf; + } // namespace units diff --git a/src/include/units/kind.h b/src/include/units/kind.h new file mode 100644 index 00000000..7b1eef7f --- /dev/null +++ b/src/include/units/kind.h @@ -0,0 +1,64 @@ +// 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 +#include + +namespace units { + +namespace detail { + +template +struct _kind_base : downcast_base<_kind_base> { + using base_kind = K; + using dimension = D; +}; + +template +struct _point_kind_base : downcast_base<_point_kind_base> { + using base_kind = K; + using dimension = typename K::dimension; +}; + +} // namespace detail + +template + requires Kind>> +using downcast_kind = downcast>; + +template + requires PointKind>> +using downcast_point_kind = downcast>; + +template +struct kind : downcast_dispatch> {}; + +template + requires std::same_as +struct derived_kind : downcast_dispatch> {}; + +template +struct point_kind : downcast_dispatch> {}; + +} // namespace units diff --git a/src/include/units/quantity_cast.h b/src/include/units/quantity_cast.h index 582cfb38..accb2c0a 100644 --- a/src/include/units/quantity_cast.h +++ b/src/include/units/quantity_cast.h @@ -42,6 +42,12 @@ class quantity; template U, QuantityValue Rep> class quantity_point; +template U, QuantityValue Rep> +class quantity_kind; + +template U, QuantityValue Rep> +class quantity_point_kind; + namespace detail { template @@ -80,7 +86,7 @@ struct cast_traits { template requires (!common_type_with_, std::intmax_t>) && - scalable_number_, std::intmax_t> && + scalable_number_, std::intmax_t> && requires { typename std::common_type_t::value_type; } && common_type_with_::value_type, std::intmax_t> struct cast_traits { @@ -103,7 +109,7 @@ struct cast_traits { * @tparam To a target quantity type to cast to */ template Rep> - requires QuantityOf + requires QuantityOf && std::constructible_from> [[nodiscard]] constexpr auto quantity_cast(const quantity& q) { using traits = detail::cast_traits; @@ -137,7 +143,7 @@ template R * * This cast gets only the target dimension to cast to. For example: * - * auto q1 = units::quantity_cast(200_q_Gal); + * auto q1 = units::quantity_cast(200_q_Gal); * * @tparam ToD a dimension type to use for a target quantity */ @@ -179,7 +185,7 @@ template * * @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 */ @@ -203,6 +209,7 @@ template * @tparam ToRep a representation type to use for a target quantity */ template Rep> + requires std::constructible_from> [[nodiscard]] constexpr auto quantity_cast(const quantity& q) { return quantity_cast>(q); @@ -218,7 +225,7 @@ template Rep> * * auto q1 = units::quantity_point_cast(quantity_point{1_q_ms}); * auto q1 = units::quantity_point_cast>(quantity_point{1_q_ms}); - * auto q1 = units::quantity_point_cast(quantity_point{200_q_Gal}); + * auto q1 = units::quantity_point_cast(quantity_point{200_q_Gal}); * auto q1 = units::quantity_point_cast(quantity_point{1_q_ms}); * auto q1 = units::quantity_point_cast(quantity_point{1_q_ms}); * @@ -247,7 +254,7 @@ template * * @note This cast is especially useful when working with quantity points 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 */ @@ -258,6 +265,119 @@ template return quantity_point_cast>(q); } +/** + * @brief Explicit cast of a quantity kind + * + * Implicit conversions between quantity kinds of different types are allowed only for "safe" + * (i.e. non-truncating) conversion. In other cases an explicit cast has to be used. + * + * This cast gets the target (quantity) kind type to cast to or anything that works for quantity_cast. For example: + * + * auto q1 = units::quantity_kind_cast(quantity_kind{ns::width{1 * mm}); + * auto q1 = units::quantity_kind_cast(ns::width{1 * m}); + * auto q1 = units::quantity_kind_cast>(ns::width{1 * mm}); + * auto q1 = units::quantity_kind_cast(ns::rate_of_climb{200 * Gal}); + * auto q1 = units::quantity_kind_cast(ns::width{1 * mm}); + * auto q1 = units::quantity_kind_cast(ns::width{1.0 * mm}); + * + * @tparam CastSpec a target (quantity) kind type to cast to or anything that works for quantity_cast + */ +template +[[nodiscard]] constexpr QuantityKind auto quantity_kind_cast(const quantity_kind& qk) + requires (is_specialization_of && + requires { quantity_cast(qk.common()); }) || + (Kind && UnitOf) || + requires { quantity_cast(qk.common()); } +{ + if constexpr (is_specialization_of) + return CastSpec(quantity_cast(qk.common())); + else if constexpr (Kind) + return quantity_kind(qk.common()); + else { + auto q{quantity_cast(qk.common())}; + using Q = decltype(q); + return quantity_kind(static_cast(q)); + } +} + +/** + * @brief Explicit cast of a quantity kind + * + * Implicit conversions between quantity kinds of different types are allowed only for "safe" + * (i.e. non-truncating) conversion. In other cases an explicit cast has to be used. + * + * This cast gets both the target kind and unit to cast to. For example: + * + * auto q1 = units::quantity_kind_cast(w); + * + * @note This cast is especially useful when working with quantity kinds of unknown kind. + * + * @tparam ToK the kind type to use for the target quantity + * @tparam ToU the unit type to use for the target quantity + */ +template + requires equivalent && UnitOf +[[nodiscard]] constexpr QuantityKind auto quantity_kind_cast(const quantity_kind& qk) +{ + return quantity_kind_cast>(qk); +} + +/** + * @brief Explicit cast of a quantity point kind + * + * Implicit conversions between quantity point kinds of different types are allowed only for "safe" + * (i.e. non-truncating) conversion. In other cases an explicit cast has to be used. + * + * This cast gets the target (quantity) point kind type to cast to or anything that works for quantity_kind_cast. For example: + * + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1 * mm}); + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1 * mm}); + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1 * m}); + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1 * m}); + * auto q1 = units::quantity_point_kind_cast>(ns::x_coordinate{1 * mm}); + * auto q1 = units::quantity_point_kind_cast(quantity_point_kind(ns::rate_of_climb{200 * Gal})); + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1 * mm}); + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1.0 * mm}); + * + * @tparam CastSpec a target (quantity) point kind type to cast to or anything that works for quantity_kind_cast + */ +template +[[nodiscard]] constexpr QuantityPointKind auto quantity_point_kind_cast(const quantity_point_kind& qpk) + requires (is_specialization_of && + requires { quantity_kind_cast(qpk.relative()); }) || + (PointKind && UnitOf) || + requires { quantity_kind_cast(qpk.relative()); } +{ + if constexpr (is_specialization_of) + return CastSpec(quantity_kind_cast(qpk.relative())); + else if constexpr (PointKind) + return quantity_point_kind(quantity_kind_cast(qpk.relative())); + else + return quantity_point_kind(quantity_kind_cast(qpk.relative())); +} + +/** + * @brief Explicit cast of a quantity point kind + * + * Implicit conversions between quantity point kinds of different types are allowed only for "safe" + * (i.e. non-truncating) conversion. In other cases an explicit cast has to be used. + * + * This cast gets both the target point kind and unit to cast to. For example: + * + * auto q1 = units::quantity_point_kind_cast(x); + * + * @note This cast is especially useful when working with quantity point kinds of unknown point kind. + * + * @tparam ToPK the point kind type to use for the target quantity + * @tparam ToU the unit type to use for the target quantity + */ +template + requires equivalent && UnitOf +[[nodiscard]] constexpr QuantityPointKind auto quantity_point_kind_cast(const quantity_point_kind& qpk) +{ + return quantity_point_kind_cast>(qpk); +} + } // namespace units #ifdef _MSC_VER diff --git a/src/include/units/quantity_kind.h b/src/include/units/quantity_kind.h new file mode 100644 index 00000000..6dc5ed66 --- /dev/null +++ b/src/include/units/quantity_kind.h @@ -0,0 +1,351 @@ + +// 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 +#include +#include +#include +#include + +namespace units { + +namespace detail { + +template +inline constexpr auto make_quantity_kind_fn = [](auto q) { + using Q = decltype(q); + return quantity_kind(std::move(q)); +}; + +template +inline constexpr auto& make_quantity_kind = make_quantity_kind_fn; + +template +inline constexpr auto downcasted_kind_fn = [](auto q) { + using Q = decltype(q); + return make_quantity_kind_fn>(std::move(q)); +}; + +template +inline constexpr auto& downcasted_kind = downcasted_kind_fn; + +} // namespace detail + +/** + * @brief A quantity kind + * + * A quantity with more specific usage as determined by its kind. + * See https://jcgm.bipm.org/vim/en/1.2.html and NOTE 1 at https://jcgm.bipm.org/vim/en/1.1.html. + * + * @tparam K the kind of quantity + * @tparam U the measurement unit of the quantity kind + * @tparam Rep the type to be used to represent values of the quantity kind + */ +template U, QuantityValue Rep = double> +class quantity_kind { +public: + using kind_type = K; + using quantity_type = quantity; + using dimension = typename quantity_type::dimension; + using unit = typename quantity_type::unit; + using rep = typename quantity_type::rep; + +private: + quantity_type q_; + +public: + quantity_kind() = default; + quantity_kind(const quantity_kind&) = default; + quantity_kind(quantity_kind&&) = default; + + template Value> + requires is_same_v && std::is_constructible_v + constexpr explicit quantity_kind(const Value& v) : q_(v) {} + + template + requires (Quantity || QuantityLike) && std::is_constructible_v + constexpr explicit quantity_kind(const Q& q) : q_{q} {} + + template QK2> + requires std::is_convertible_v + constexpr explicit(false) quantity_kind(const QK2& qk) : q_{qk.common()} {} + + quantity_kind& operator=(const quantity_kind&) = default; + quantity_kind& operator=(quantity_kind&&) = default; + + [[nodiscard]] constexpr quantity_type common() const noexcept { return q_; } + + [[nodiscard]] static constexpr quantity_kind zero() noexcept + requires requires { quantity_type::zero(); } + { + return quantity_kind(quantity_type::zero()); + } + + [[nodiscard]] static constexpr quantity_kind one() noexcept + requires requires { quantity_type::one(); } + { + return quantity_kind(quantity_type::one()); + } + + [[nodiscard]] static constexpr quantity_kind min() noexcept + requires requires { quantity_type::min(); } + { + return quantity_kind(quantity_type::min()); + } + + [[nodiscard]] static constexpr quantity_kind max() noexcept + requires requires { quantity_type::max(); } + { + return quantity_kind(quantity_type::max()); + } + + [[nodiscard]] constexpr quantity_kind operator+() const + requires requires(quantity_type q) { +q; } + { + return *this; + } + + [[nodiscard]] constexpr QuantityKind auto operator-() const + requires requires(quantity_type q) { -q; } + { + return detail::make_quantity_kind(-q_); + } + + constexpr quantity_kind& operator++() + requires requires(quantity_type q) { ++q; } + { + ++q_; + return *this; + } + + [[nodiscard]] constexpr quantity_kind operator++(int) + requires requires(quantity_type q) { q++; } + { + return quantity_kind(q_++); + } + + constexpr quantity_kind& operator--() + requires requires(quantity_type q) { --q; } + { + --q_; + return *this; + } + + [[nodiscard]] constexpr quantity_kind operator--(int) + requires requires(quantity_type q) { q--; } + { + return quantity_kind(q_--); + } + + constexpr quantity_kind& operator+=(const quantity_kind& qk) + requires requires(quantity_type q) { q += qk.common(); } + { + q_ += qk.common(); + return *this; + } + + constexpr quantity_kind& operator-=(const quantity_kind& qk) + requires requires(quantity_type q) { q -= qk.common(); } + { + q_ -= qk.common(); + return *this; + } + + template + constexpr quantity_kind& operator*=(const Rep2& rhs) + requires requires(quantity_type q) { q *= rhs; } + { + q_ *= rhs; + return *this; + } + + template + constexpr quantity_kind& operator/=(const Rep2& rhs) + requires requires(quantity_type q) { q /= rhs; } + { + q_ /= rhs; + return *this; + } + + constexpr quantity_kind& operator%=(const rep& rhs) + requires requires(quantity_type q) { q %= rhs; } + { + q_ %= rhs; + return *this; + } + + constexpr quantity_kind& operator%=(const quantity_kind& qk) + requires requires(quantity_type q) { q %= qk.common(); } + { + q_ %= qk.common(); + return *this; + } + + // Hidden Friends + // Below friend functions are to be found via argument-dependent lookup only + [[nodiscard]] friend constexpr quantity_kind operator+(const quantity_kind& lhs, const quantity_kind& rhs) + requires requires { lhs.common() + rhs.common(); } + { + return quantity_kind(lhs.common() + rhs.common()); + } + + [[nodiscard]] friend constexpr quantity_kind operator-(const quantity_kind& lhs, const quantity_kind& rhs) + requires requires { lhs.common() - rhs.common(); } + { + return quantity_kind(lhs.common() - rhs.common()); + } + + template + [[nodiscard]] friend constexpr QuantityKind auto operator*(const quantity_kind& qk, const Value& v) + requires requires { { qk.common() * v } -> Quantity; } + { + return detail::make_quantity_kind(qk.common() * v); + } + + template + [[nodiscard]] friend constexpr QuantityKind auto operator*(const Value& v, const quantity_kind& qk) + requires requires { { v * qk.common() } -> Quantity; } + { + return detail::make_quantity_kind(v * qk.common()); + } + + template + [[nodiscard]] friend constexpr QuantityKind auto operator/(const quantity_kind& qk, const Value& v) + requires requires { { qk.common() / v } -> Quantity; } + { + return detail::make_quantity_kind(qk.common() / v); + } + + template + [[nodiscard]] friend constexpr QuantityKind auto operator/(const Value& v, const quantity_kind& qk) + requires requires { { v / qk.common() } -> Quantity; } + { + return detail::downcasted_kind(v / qk.common()); + } + + template + [[nodiscard]] friend constexpr QuantityKind auto operator%(const quantity_kind& qk, const Value& v) + requires requires { qk.common() % v; } + { + return detail::make_quantity_kind(qk.common() % v); + } + + [[nodiscard]] friend constexpr quantity_kind operator%(const quantity_kind& lhs, const quantity_kind& rhs) + requires requires { lhs.common() % rhs.common(); } + { + return quantity_kind(lhs.common() % rhs.common()); + } + + [[nodiscard]] friend constexpr auto operator<=>(const quantity_kind& lhs, const quantity_kind& rhs) + requires std::three_way_comparable + { + return lhs.common() <=> rhs.common(); + } + + [[nodiscard]] friend constexpr bool operator==(const quantity_kind&, const quantity_kind&) = default; +}; + +template +explicit(false) quantity_kind(K, V) -> quantity_kind; + +template QK2> +[[nodiscard]] constexpr QuantityKind auto operator+(const QK1& lhs, const QK2& rhs) + requires requires { lhs.common() + rhs.common(); } +{ + return detail::make_quantity_kind(lhs.common() + rhs.common()); +} + +template QK2> +[[nodiscard]] constexpr QuantityKind auto operator-(const QK1& lhs, const QK2& rhs) + requires requires { lhs.common() - rhs.common(); } +{ + return detail::make_quantity_kind(lhs.common() - rhs.common()); +} + +template +[[nodiscard]] constexpr QuantityKind auto operator*(const QK& lhs, const Q& rhs) + requires requires { lhs.common() * rhs; } +{ + return detail::downcasted_kind(lhs.common() * rhs); +} + +template +[[nodiscard]] constexpr QuantityKind auto operator*(const Q& lhs, const QK& rhs) + requires requires { lhs * rhs.common(); } +{ + return detail::downcasted_kind(lhs * rhs.common()); +} + +template +[[nodiscard]] constexpr QuantityKind auto operator/(const QK& lhs, const Q& rhs) + requires requires { lhs.common() / rhs; } +{ + return detail::downcasted_kind(lhs.common() / rhs); +} + +template +[[nodiscard]] constexpr QuantityKind auto operator/(const Q& lhs, const QK& rhs) + requires requires { lhs / rhs.common(); } +{ + return detail::downcasted_kind(lhs / rhs.common()); +} + +template +[[nodiscard]] constexpr QuantityKind auto operator%(const QK& lhs, const D& rhs) + requires requires { lhs.common() % rhs; } +{ + return detail::make_quantity_kind(lhs.common() % rhs); +} + +template QK2> +[[nodiscard]] constexpr QuantityKind auto operator%(const QK1& lhs, const QK2& rhs) + requires requires { lhs.common() % rhs.common(); } +{ + return detail::make_quantity_kind(lhs.common() % rhs.common()); +} + +template QK2> + requires std::three_way_comparable_with +[[nodiscard]] constexpr auto operator<=>(const QK1& lhs, const QK2& rhs) +{ + return lhs.common() <=> rhs.common(); +} + +template QK2> + requires std::equality_comparable_with +[[nodiscard]] constexpr bool operator==(const QK1& lhs, const QK2& rhs) +{ + return lhs.common() == rhs.common(); +} + +// type traits +namespace detail { + +template +inline constexpr bool is_quantity_kind> = true; + +} // namespace detail + +} // namespace units diff --git a/src/include/units/quantity_point.h b/src/include/units/quantity_point.h index cecdc716..a2506956 100644 --- a/src/include/units/quantity_point.h +++ b/src/include/units/quantity_point.h @@ -53,8 +53,14 @@ public: quantity_point(const quantity_point&) = default; quantity_point(quantity_point&&) = default; - template - requires std::is_convertible_v + template Value> + requires is_same_v && std::is_constructible_v + constexpr explicit quantity_point(const Value& v) : q_(v) {} + + constexpr explicit quantity_point(const quantity_type& q) : q_{q} {} + + template + requires std::is_constructible_v constexpr explicit quantity_point(const Q& q) : q_{q} {} template @@ -169,8 +175,12 @@ public: }; -template -quantity_point(quantity) -> quantity_point; +template +explicit(false) quantity_point(V) -> quantity_point; + +template +quantity_point(Q) -> quantity_point::dimension, + typename quantity_like_traits::unit, typename quantity_like_traits::rep>; namespace detail { diff --git a/src/include/units/quantity_point_kind.h b/src/include/units/quantity_point_kind.h new file mode 100644 index 00000000..83cd8f4c --- /dev/null +++ b/src/include/units/quantity_point_kind.h @@ -0,0 +1,195 @@ + +// 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 +#include +#include + +namespace units { + +/** + * @brief A quantity point kind + * + * An absolute quantity kind with respect to zero (which represents some origin). + * + * @tparam PK the point kind of quantity point + * @tparam U the measurement unit of the quantity point kind + * @tparam Rep the type to be used to represent values of the quantity point kind + */ +template U, QuantityValue Rep = double> +class quantity_point_kind { +public: + using point_kind_type = PK; + using kind_type = typename PK::base_kind; + using quantity_kind_type = quantity_kind; + using quantity_type = typename quantity_kind_type::quantity_type; + using dimension = typename quantity_type::dimension; + using unit = typename quantity_type::unit; + using rep = typename quantity_type::rep; + +private: + quantity_kind_type qk_; + +public: + quantity_point_kind() = default; + quantity_point_kind(const quantity_point_kind&) = default; + quantity_point_kind(quantity_point_kind&&) = default; + + template Value> + requires std::is_constructible_v + constexpr explicit quantity_point_kind(const Value& v) : qk_(v) {} + + constexpr explicit quantity_point_kind(const quantity_type& q) : qk_{q} {} + + template + requires std::is_constructible_v + constexpr explicit quantity_point_kind(const Q& q) : qk_{q} {} + + constexpr explicit quantity_point_kind(const quantity_point& qp) : qk_{qp.relative()} {} + + constexpr explicit quantity_point_kind(const quantity_kind_type& qk) : qk_{qk} {} + + template QPK2> + requires std::is_convertible_v + constexpr explicit(false) quantity_point_kind(const QPK2& qpk) : qk_{qpk.relative()} {} + + quantity_point_kind& operator=(const quantity_point_kind&) = default; + quantity_point_kind& operator=(quantity_point_kind&&) = default; + + [[nodiscard]] constexpr quantity_kind_type relative() const noexcept { return qk_; } + + [[nodiscard]] static constexpr quantity_point_kind min() noexcept + requires requires { quantity_kind_type::min(); } + { + return quantity_point_kind(quantity_kind_type::min()); + } + + [[nodiscard]] static constexpr quantity_point_kind max() noexcept + requires requires { quantity_kind_type::max(); } + { + return quantity_point_kind(quantity_kind_type::max()); + } + + constexpr quantity_point_kind& operator++() + requires requires(quantity_kind_type qk) { ++qk; } + { + ++qk_; + return *this; + } + + [[nodiscard]] constexpr quantity_point_kind operator++(int) + requires requires(quantity_kind_type qk) { qk++; } + { + return quantity_point_kind(qk_++); + } + + constexpr quantity_point_kind& operator--() + requires requires(quantity_kind_type qk) { --qk; } + { + --qk_; + return *this; + } + + [[nodiscard]] constexpr quantity_point_kind operator--(int) + requires requires(quantity_kind_type qk) { qk--; } + { + return quantity_point_kind(qk_--); + } + + constexpr quantity_point_kind& operator+=(const quantity_kind_type& qk) + requires requires(quantity_kind_type qk1, const quantity_kind_type qk2) { qk1 += qk2; } + { + qk_ += qk; + return *this; + } + + constexpr quantity_point_kind& operator-=(const quantity_kind_type& qk) + requires requires(quantity_kind_type qk1, const quantity_kind_type qk2) { qk1 -= qk2; } + { + qk_ -= qk; + return *this; + } + + // Hidden Friends + // Below friend functions are to be found via argument-dependent lookup only + + template + [[nodiscard]] friend constexpr QuantityPointKind auto operator+(const quantity_point_kind& lhs, const QK& rhs) + requires requires { lhs.relative() + rhs; } + { + return units::quantity_point_kind(lhs.relative() + rhs); + } + + template + [[nodiscard]] friend constexpr QuantityPointKind auto operator+(const QK& lhs, const quantity_point_kind& rhs) + requires requires { lhs + rhs.relative(); } + { + return units::quantity_point_kind(lhs + rhs.relative()); + } + + template + [[nodiscard]] friend constexpr QuantityPointKind auto operator-(const quantity_point_kind& lhs, const QK& rhs) + requires requires { lhs.relative() - rhs; } + { + return units::quantity_point_kind(lhs.relative() - rhs); + } + + [[nodiscard]] friend constexpr QuantityKind auto operator-(const quantity_point_kind& lhs, const quantity_point_kind& rhs) + requires requires { lhs.relative() - rhs.relative(); } + { + return lhs.relative() - rhs.relative(); + } + + template + requires std::three_way_comparable_with + [[nodiscard]] friend constexpr auto operator<=>(const quantity_point_kind& lhs, const QPK& rhs) + { + return lhs.relative() <=> rhs.relative(); + } + + template + requires std::equality_comparable_with + [[nodiscard]] friend constexpr bool operator==(const quantity_point_kind& lhs, const QPK& rhs) + { + return lhs.relative() == rhs.relative(); + } + +}; + +template +explicit(false) quantity_point_kind(PK, V) -> quantity_point_kind; + +template +quantity_point_kind(QK) -> + quantity_point_kind, typename QK::unit, typename QK::rep>; + +namespace detail { + +template +inline constexpr bool is_quantity_point_kind> = true; + +} // namespace detail + +} // namespace units diff --git a/test/unit_test/static/CMakeLists.txt b/test/unit_test/static/CMakeLists.txt index a98c54ca..e0258c39 100644 --- a/test/unit_test/static/CMakeLists.txt +++ b/test/unit_test/static/CMakeLists.txt @@ -34,9 +34,12 @@ add_library(unit_tests_static dimensions_concepts_test.cpp fixed_string_test.cpp fps_test.cpp + kind_test.cpp math_test.cpp - quantity_point_test.cpp quantity_test.cpp + quantity_point_test.cpp + quantity_kind_test.cpp + quantity_point_kind_test.cpp ratio_test.cpp si_test.cpp si_cgs_test.cpp diff --git a/test/unit_test/static/kind_test.cpp b/test/unit_test/static/kind_test.cpp new file mode 100644 index 00000000..9915b011 --- /dev/null +++ b/test/unit_test/static/kind_test.cpp @@ -0,0 +1,203 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "test_tools.h" +#include "units/generic/angle.h" +#include "units/kind.h" +#include "units/physical/si/base/length.h" +#include "units/physical/si/derived/area.h" +#include "units/physical/si/derived/speed.h" + +using namespace units; +using namespace physical::si; + +namespace { + +template +using downcast_result = std::conditional_t>; + + +// no library-defined base kind + + +// spherical coordinates +struct radius : kind {}; // program-defined base kind +struct colatitude : kind> {}; +struct azimuth : kind> {}; + +static_assert(Kind); +static_assert(Kind); +static_assert(Kind); +static_assert(Kind); +static_assert(Kind); + +static_assert(!PointKind); +static_assert(!PointKind); +static_assert(!PointKind); +static_assert(!PointKind); +static_assert(!PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v, downcast_kind>); + +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent>); +static_assert(!equivalent>); + +using radial_area = downcast_kind; // library-defined derived kind +using radial_point = downcast_point_kind; // library-defined base point kind + +static_assert(Kind); +static_assert(!PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v>); +static_assert(is_same_v>); +static_assert(is_same_v>); + +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!Kind); +static_assert(PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v>); +static_assert(is_same_v>); + +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(equivalent>); +static_assert(!equivalent); +static_assert(!equivalent); + + +struct width : kind {}; +using horizontal_speed = downcast_kind; + +struct abscissa : point_kind {}; // program-defined base point kind +using horizontal_velocity = downcast_point_kind>; // library-defined derived point kind + +static_assert(!Kind); +static_assert(!Kind); +static_assert(!Kind); +static_assert(PointKind); +static_assert(PointKind); +static_assert(PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v, downcast_point_kind>); + +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!Kind); +static_assert(PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v>); +static_assert(is_same_v>); + +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!equivalent); +static_assert(!equivalent); + + +struct height : kind {}; + +struct rate_of_climb : derived_kind {}; // program-defined derived kind +struct velocity_of_climb : point_kind {}; // program-defined derived point kind + +static_assert(Kind); +static_assert(Kind); +static_assert(Kind); +static_assert(!PointKind); +static_assert(!PointKind); +static_assert(!PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v, downcast_kind>); +static_assert(is_same_v, downcast_kind>); + +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!Kind); +static_assert(!Kind); +static_assert(!Kind); +static_assert(PointKind); +static_assert(PointKind); +static_assert(PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v, downcast_point_kind>); + +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); + +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(is_same_v, downcast_kind>); + +} // namespace diff --git a/test/unit_test/static/quantity_kind_test.cpp b/test/unit_test/static/quantity_kind_test.cpp new file mode 100644 index 00000000..cb10afd5 --- /dev/null +++ b/test/unit_test/static/quantity_kind_test.cpp @@ -0,0 +1,675 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "test_tools.h" +#include "units/chrono.h" +#include "units/physical/si/cgs/derived/speed.h" +#include "units/physical/si/derived/area.h" +#include "units/physical/si/derived/frequency.h" +#include "units/physical/si/derived/speed.h" +#include "units/physical/si/fps/derived/speed.h" +#include "units/quantity_point.h" +#include "units/quantity_kind.h" +#include +#include +#include + +namespace { + +using namespace units; +namespace si = physical::si; +using namespace si; +using namespace unit_constants; + +constexpr auto cgs_cm = cgs::unit_constants::cm; + +using namespace std::chrono_literals; + +struct radius_kind : kind {}; +struct width_kind : kind {}; +struct height_kind : kind {}; + +struct horizontal_area_kind : derived_kind {}; +struct rate_of_climb_kind : derived_kind {}; + +struct apple : kind {}; +struct orange : kind {}; + +struct time_kind : kind {}; + +struct cgs_width_kind : kind {}; + +template using radius = quantity_kind; +template using width = quantity_kind; +template using height = quantity_kind; + +template using horizontal_area = quantity_kind; +template using rate_of_climb = quantity_kind; + +template using apples = quantity_kind; +template using oranges = quantity_kind; + +template using cgs_width = quantity_kind; + +///////////// +// concepts +///////////// + +static_assert(QuantityKind>); +static_assert(QuantityKind>); +static_assert(!QuantityKind); +static_assert(!QuantityKind>); +static_assert(!QuantityKind>); + +static_assert(QuantityKindOf, width_kind>); +static_assert(!QuantityKindOf, height_kind>); +static_assert(!QuantityKindOf, metre>); +static_assert(!QuantityKindOf, width_kind>); +static_assert(!QuantityKindOf, metre>); +static_assert(!QuantityKindOf, width_kind>); +static_assert(!QuantityKindOf, dim_length>); +static_assert(!QuantityKindOf, metre>); + + +/////////////// +// invariants +/////////////// + +static_assert(sizeof(width) == sizeof(double)); +static_assert(sizeof(height) == sizeof(short)); + +template +concept invalid_types = requires { + requires !requires { typename quantity_kind; }; // unit of a different dimension + requires !requires { typename quantity_kind>; }; // quantity used as Rep + requires !requires { typename quantity_kind>; }; // quantity point used as Rep + requires !requires { typename quantity_kind>; }; // quantity kind used as Rep + requires !requires { typename quantity_kind; }; // reordered arguments + requires !requires { typename quantity_kind; }; // reordered arguments +}; +static_assert(invalid_types); + +static_assert(std::is_trivially_default_constructible_v>); +static_assert(std::is_trivially_copy_constructible_v>); +static_assert(std::is_trivially_move_constructible_v>); +static_assert(std::is_trivially_copy_assignable_v>); +static_assert(std::is_trivially_move_assignable_v>); +static_assert(std::is_trivially_destructible_v>); + +static_assert(std::is_nothrow_default_constructible_v>); +static_assert(std::is_nothrow_copy_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_copy_assignable_v>); +static_assert(std::is_nothrow_move_assignable_v>); +static_assert(std::is_nothrow_destructible_v>); + +static_assert(std::is_trivially_copyable_v>); +static_assert(std::is_standard_layout_v>); + +static_assert(std::default_initializable>); +static_assert(std::move_constructible>); +static_assert(std::copy_constructible>); +static_assert(std::equality_comparable>); +static_assert(std::totally_ordered>); +static_assert(std::regular>); + +static_assert(std::three_way_comparable>); + +static_assert(!std::is_aggregate_v>); + + +/////////////////// +// member aliases +/////////////////// + +static_assert(is_same_v::kind_type, width_kind>); +static_assert(is_same_v::quantity_type, length>); +static_assert(is_same_v::dimension, dim_length>); +static_assert(is_same_v::unit, metre>); +static_assert(is_same_v::rep, double>); + + +//////////////////// +// common observer +//////////////////// + +static_assert(same(radius{}.common(), length{})); +static_assert(radius{}.common() == // [VIM3] 1.2 kind of quantity + height{}.common()); // aspect common to mutually comparable quantities + // hence `.common()` +static_assert(!std::equality_comparable_with, oranges<>>); + + +//////////////////////////// +// static member functions +//////////////////////////// + +static_assert(width::zero().common() == 0 * m); +static_assert(width::one().common() == 1 * m); +static_assert(width::min().common() == 0 * m); +static_assert(width::max().common() == std::numeric_limits::max() * m); +static_assert(width::min().common().count() == std::numeric_limits::lowest()); +static_assert(width::max().common().count() == std::numeric_limits::max()); + + +//////////////////////// +// default constructor +//////////////////////// + +// default initialization +#if !defined(COMP_MSVC) +static_assert([] { + const auto read_uninitialized_quantity = [] { + width w; + ++w; + }; + return !require_constant_invocation; +}()); +#endif + +// value initialization +static_assert(width{}.common() == 0 * m); + + +///////// +// CTAD +///////// + +static_assert(same(quantity_kind(rate_of_climb(0.01 * km / h)), + rate_of_climb(0.01 * km / h))); + + +//////////////////////////// +// construction from a rep +//////////////////////////// + +static_assert(construct_from_only>(1).common() == 1); +static_assert(construct_from_only>(1.0).common() == 1); +static_assert(construct_from_only>(1ULL).common().count() == 1); +static_assert(construct_from_only>(1.0L).common().count() == 1); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0f)); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1)); + + +///////////////////////////////// +// construction from a quantity +///////////////////////////////// + +static_assert(construct_from_only>(1 * m).common() == 1 * m); +static_assert(construct_from_only>(1 * km).common() == 1 * km); +// static_assert(construct_from_only>(1 * cgs_cm).common() == 1 * cm); // TODO: Fix #210 +static_assert(construct_from_only>(1 * cgs_cm).common() == 1 * cm); +static_assert(construct_from_only>(1 * mm).common() == 1 * mm); +static_assert(construct_from_only>(1 * m).common() == 1 * m); +static_assert(construct_from_only>(1 * km).common() == 1 * km); +static_assert(construct_from_only>(1.0 * mm).common() == 1 * mm); +static_assert(construct_from_only>(1.0 * m).common() == 1 * m); +static_assert(construct_from_only>(1.0 * km).common() == 1 * km); +static_assert(construct_from_only>(1.0 * mm).common() == 1 * mm); +static_assert(construct_from_only>(1.0 * m).common() == 1 * m); +static_assert(construct_from_only>(1.0 * km).common() == 1 * km); + +static_assert(!constructible_or_convertible_from>(1 * mm)); +static_assert(!constructible_or_convertible_from>(1.0 * mm)); +static_assert(!constructible_or_convertible_from>(1.0 * m)); +static_assert(!constructible_or_convertible_from>(1.0 * km)); +static_assert(!constructible_or_convertible_from>(1 * s)); +static_assert(!constructible_or_convertible_from>(1 * m * m)); +static_assert(!constructible_or_convertible_from>(1 * m / s)); + + +static_assert(construct_from_only>(1.0f * m).common() == 1 * m); +static_assert(construct_from_only>(short{1} * m).common() == 1 * m); +static_assert(construct_from_only>(1 * m).common() == 1 * m); + + +static_assert(construct_from_only>(quantity(1)).common() == 1); +static_assert(construct_from_only>(dimensionless(1)).common() == 0.01); +static_assert(construct_from_only>(quantity(1.0)).common().count() == 100); +static_assert(construct_from_only>(dimensionless(1)).common().count() == 1); +static_assert(construct_from_only>(quantity(1.0)).common() == 1); +static_assert(construct_from_only>(quantity(1.0f)).common() == 1); +static_assert(construct_from_only>(quantity(1.0)).common() == 1); +static_assert(construct_from_only>(quantity(1)).common() == 1); +static_assert(construct_from_only>(quantity(short{1})).common() == 1); +static_assert(construct_from_only>(quantity(1)).common() == 1); +static_assert(construct_from_only>(quantity(1.0)).common().count() == 1e2); +static_assert(construct_from_only>(quantity(1.0f)).common().count() == 1e2); +static_assert(construct_from_only>(quantity(1.0)).common().count() == 1e2f); +static_assert(construct_from_only>(quantity(1)).common().count() == 1e2); +static_assert(construct_from_only>(quantity(short{1})).common().count() == 1e2); +static_assert(construct_from_only>(quantity(1)).common().count() == 1e2); + +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); + + +static_assert(construct_from_only>(42s).common() == 42 * s); + + +static_assert(!constructible_or_convertible_from>(1 * s)); +static_assert(!constructible_or_convertible_from>(1 * m * m)); +static_assert(!constructible_or_convertible_from>(1 * m / s)); + + +static_assert(construct_from_only>(1.0 * cgs_cm).common() == 1 * cm); +static_assert(construct_from_only>(1.0 * cm).common() == 1 * cm); + + +//////////////////////////////////////////// +// construction from another quantity kind +//////////////////////////////////////////// + +// clang-format off +static_assert(construct_and_convert_from>(width(1 * m)).common() == 1 * m); +static_assert(construct_and_convert_from>(width(1 * cgs_cm)).common() == 1 * cm); +static_assert(construct_and_convert_from>(width(1 * cgs_cm)).common() == 1 * cm); + +static_assert(construct_and_convert_from>(width(1 * m)).common() == 1 * m); +static_assert(!constructible_or_convertible_from>(width(1.0 * m))); + +static_assert(construct_and_convert_from>(width(1 * km)).common() == 1 * km); +static_assert(!constructible_or_convertible_from>(width(1 * m))); + +static_assert(construct_and_convert_from>(width(1 * km)).common() == 1 * km); +static_assert(construct_and_convert_from>(width(1 * m)).common() == 1 * m); + +static_assert(!constructible_or_convertible_from>(height(1 * m))); +static_assert(!constructible_or_convertible_from>(width(1 * m) / m)); +static_assert(!constructible_or_convertible_from>(oranges(1))); +// clang-format on + + +////////////////////////////////// +// construction from other types +////////////////////////////////// + +// clang-format off +static_assert(!constructible_or_convertible_from>(quantity_point(1 * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1 * km))); +static_assert(!constructible_or_convertible_from>(quantity_point(1 * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1 * km))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * km))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * m * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * s))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * s))); +static_assert(!constructible_or_convertible_from>(1s)); +static_assert(!constructible_or_convertible_from>(1.0s)); +static_assert(!constructible_or_convertible_from>(quantity_point(1))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(quantity_point(1))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1.0)))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * m))); +static_assert(!constructible_or_convertible_from>(1s)); +static_assert(!constructible_or_convertible_from>(1.0s)); +// clang-format on + + +//////////////////////// +// assignment operator +//////////////////////// + +static_assert((width(2 * m) = width(1 * m)).common() == 1 * m); +static_assert((width(2 * m) = width(1 * km)).common() == 1 * km); +static_assert(!std::is_assignable_v, width>); +static_assert(!std::is_assignable_v, width>); + + +///////////////////// +// member operators +///////////////////// + +#if !defined(COMP_MSVC) || defined(NDEBUG) +static_assert([]() { + width w(1 * m); + assert(+w.common() == 1 * m); + assert(-w.common() == -1 * m); + assert(&++w == &w && w.common() == 2 * m); + assert(&--w == &w && w.common() == 1 * m); + assert((w++).common() == 1 * m && w.common() == 2 * m); + assert((w--).common() == 2 * m && w.common() == 1 * m); + assert(&(w += w) == &w && w.common() == 2 * m); + assert(&(w -= w) == &w && w.common() == 0 * m); + w = width(3 * m); + assert(&(w *= 3) == &w && w.common() == 9 * m); + assert(&(w /= 2) == &w && w.common() == 4 * m); + assert(&(w %= 3) == &w && w.common() == 1 * m); + assert(&(w %= w) == &w && w.common() == 0 * m); + w = width(3 * m); + assert(&(w *= 3.9) == &w && w.common() == 11 * m); + assert(&(w /= 3.9) == &w && w.common() == 2 * m); + assert(&(w %= 3.9) == &w && w.common() == 2 * m); + return true; +}()); +#endif + +static_assert(same((-width(short{1} * m)).common(), int{-1} * m)); + +template +concept invalid_compound_assignments_ = requires(quantity_kind w, Qx q) { + requires !requires { w += q; }; + requires !requires { w -= q; }; + requires !requires { w *= q; }; + requires !requires { w /= q; }; + requires !requires { w %= q; }; +}; +template +concept invalid_compound_assignments = requires(quantity_kind w) { + requires !requires { w += 1; }; + requires !requires { w -= 1; }; + requires !requires { w %= w * 1.0; }; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_; +}; +static_assert(invalid_compound_assignments); +static_assert(invalid_compound_assignments_); + + +///////////////////////// +// non-member operators +///////////////////////// + +static_assert(same(width(2 * m) + width(3 * m), width(5 * m))); +static_assert(same(width(2 * m) + width(3. * m), width(5. * m))); +static_assert(same(width(2. * m) + width(3 * m), width(5. * m))); +static_assert(same(width(2 * km) + width(3e3 * m), width(5e3 * m))); +static_assert(same(width(2 * m) - width(3 * m), width(-1 * m))); +static_assert(same(width(2 * m) - width(3. * m), width(-1. * m))); +static_assert(same(width(2. * m) - width(3 * m), width(-1. * m))); +static_assert(same(width(2e3 * m) - width(3 * km), width(-1e3 * m))); + +static_assert(!std::is_invocable_v, width, double>); +static_assert(!std::is_invocable_v, width, length>); +static_assert(!std::is_invocable_v, width, quantity_point>); +static_assert(!std::is_invocable_v, width, height>); +static_assert(!std::is_invocable_v, width, double>); +static_assert(!std::is_invocable_v, width, length>); +static_assert(!std::is_invocable_v, width, quantity_point>); +static_assert(!std::is_invocable_v, width, height>); + +// clang-format off +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind, day>>); +// clang-format on + +static_assert(same(width(2 * m) * 3, width(6 * m))); +static_assert(same(width(2 * m) * 3., width(6. * m))); +static_assert(same(width(2. * m) * 3, width(6. * m))); +static_assert(same(2 * width(3 * m), width(6 * m))); +static_assert(same(2 * width(3. * m), width(6. * m))); +static_assert(same(2. * width(3 * m), width(6. * m))); + +static_assert(same(height(2 * m) * (3 * Hz), rate_of_climb(6 * m / s))); +static_assert(same(height(2 * m) * (3. * Hz), rate_of_climb(6. * m / s))); +static_assert(same(height(2. * m) * (3 * Hz), rate_of_climb(6. * m / s))); +static_assert(same((2 * Hz) * height(3 * m), rate_of_climb(6 * m / s))); +static_assert(same((2 * Hz) * height(3. * m), rate_of_climb(6. * m / s))); +static_assert(same((2. * Hz) * height(3 * m), rate_of_climb(6. * m / s))); + +static_assert(same(width(2 * m) / 3, width(0 * m))); +static_assert(same(width(2 * m) / 3., width(2 / 3. * m))); +static_assert(same(width(2. * m) / 3, width(2. / 3 * m))); + +static_assert(same((2 / width(3 * m)).common(), 2 / 3 / m)); +static_assert(same((2 / width(3. * m)).common(), 2 / 3. / m)); +static_assert(same((2. / width(3 * m)).common(), 2. / 3 / m)); + +static_assert(same(height(2 * m) / (3 * s), rate_of_climb(0 * m / s))); +static_assert(same(height(2 * m) / (3. * s), rate_of_climb(2 / 3. * m / s))); +static_assert(same(height(2. * m) / (3 * s), rate_of_climb(2. / 3 * m / s))); + +static_assert(same(width(2 * m) * dimensionless(3), width(6 * cm))); +static_assert(same(dimensionless(2) * width(3 * m), width(6 * cm))); +static_assert(same(width(2 * m) / dimensionless(3), width(2. / 3 * hm))); +static_assert(same(width(2 * m) % dimensionless(3), width(2 * cm))); + +static_assert(same(((2 * m) / height(3 * m)), quantity_kind, one, int>(0))); +static_assert(same(((2 * m) / height(3 * m)).common(), quantity(0))); +static_assert(same(((2 * m) / height(3. * m)).common(), quantity(2 / 3.))); +static_assert(same(((2. * m) / height(3 * m)).common(), quantity(2. / 3))); +static_assert(same(((2 * m) / height(3 * m) * (0 * m)), height(0 * m))); + +static_assert(same(width(2 * m) % 3, width(2 * m))); +static_assert(same(width(3 * m) % width(2 * m), width(1 * m))); + +static_assert(!std::is_invocable_v, width, width>); +static_assert(!std::is_invocable_v, width, height>); +static_assert(!std::is_invocable_v, height, quantity_point>); +static_assert(!std::is_invocable_v, quantity_point, height>); + +static_assert(!std::is_invocable_v, width, height>); +static_assert(!std::is_invocable_v, height, quantity_point>); +static_assert(!std::is_invocable_v, quantity_point, height>); + +static_assert(!std::is_invocable_v, width, length>); +static_assert(!std::is_invocable_v, width, quantity_point>); +static_assert(!std::is_invocable_v, width, double>); +static_assert(!std::is_invocable_v, width, width>); + +// clang-format off +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind, day>>); +// clang-format on + + +///////////////////////// +// comparison operators +///////////////////////// + +static_assert(width(1 * m) == width(1 * m)); +static_assert(width(1 * m) == width(1.0 * m)); +static_assert(width(1 * m) == width(1000 * mm)); +static_assert(width(1 * m) == width(1e3 * mm)); +static_assert(width(2 * m) != width(1 * m)); +static_assert(width(2 * m) != width(1.0 * cgs_cm)); +static_assert(std::equality_comparable_with, width>); +static_assert(std::equality_comparable_with, width>); +static_assert(std::equality_comparable_with, width>); +static_assert(std::equality_comparable_with, width>); +template +concept invalid_equality = requires(quantity_kind w) { + requires !requires { w == 1; }; + requires !requires { w != 1.0; }; + requires !requires { w == 1 * m; }; + requires !requires { w != 1.0 * cgs_cm; }; + requires !requires { w == 1 * km; }; + requires !requires { w != quantity(1); }; + requires !requires { w == dimensionless(1.0); }; + requires !requires { w != height(1 * m); }; + requires !requires { w == height(1.0 * km); }; + requires !requires { w != horizontal_area(1 * m * m); }; + requires !requires { w == rate_of_climb(1.0 * km / h); }; + requires !requires { w != quantity_point(1 * m); }; + requires !requires { w == quantity_point(1.0 * mm); }; + requires !requires { w != quantity_point(quantity(1)); }; + requires !requires { w == quantity_point(dimensionless(1.0)); }; +}; +static_assert(invalid_equality); + +static_assert(width(1 * m) < width(2 * m)); +static_assert(width(1 * m) <= width(2.0 * m)); +static_assert(width(1 * m) <= width(1 * km)); +static_assert(width(1 * m) >= width(1e3 * mm)); +static_assert(width(2 * m) >= width(1 * mm)); +static_assert(width(2 * m) > width(1 * cgs_cm)); +static_assert(std::three_way_comparable_with, width>); +static_assert(std::three_way_comparable_with, width>); +static_assert(std::three_way_comparable_with, width>); +static_assert(std::three_way_comparable_with, width>); +template +concept invalid_relational = requires(quantity_kind w) { + requires !requires { w < 1; }; + requires !requires { w <= 1.0; }; + requires !requires { w >= 1 * m; }; + requires !requires { w > 1.0 * cgs_cm; }; + requires !requires { w <=> 1 * km; }; + requires !requires { w < quantity(1); }; + requires !requires { w <= dimensionless(1.0); }; + requires !requires { w >= height(1 * m); }; + requires !requires { w > height(1.0 * km); }; + requires !requires { w <=> horizontal_area(1 * m * m); }; + requires !requires { w < rate_of_climb(1.0 * km / h); }; + requires !requires { w <= quantity_point(1 * m); }; + requires !requires { w >= quantity_point(1.0 * mm); }; + requires !requires { w > quantity_point(quantity(1)); }; + requires !requires { w <=> quantity_point(dimensionless(1.0)); }; +}; +static_assert(invalid_relational); + + +/////////////////////// +// quantity_kind_cast +/////////////////////// + +// clang-format off +static_assert(same(quantity_kind_cast>(width(1 * m)), width(1 * m))); +static_assert(same(quantity_kind_cast>(width(1 * m)), width(1.0 * m))); +static_assert(same(quantity_kind_cast>(width(999 * m)), width(0 * km))); +static_assert(same(quantity_kind_cast>(width(1000 * m)), width(1 * km))); +static_assert(same(quantity_kind_cast>(width(999 * m)), width(0.999 * km))); +static_assert(same(quantity_kind_cast(width(1 * m)), width(1.0 * m))); +static_assert(same(quantity_kind_cast(width(1 * m)), width(1 * m))); +static_assert(same(quantity_kind_cast(width(999 * m)), width(0 * km))); +static_assert(same(quantity_kind_cast(width(1000 * m)), width(1 * km))); +static_assert(same(quantity_kind_cast>(width(1 * m)), height(1 * m))); +static_assert(same(quantity_kind_cast>(width(1 * m)), height(1.0 * m))); +static_assert(same(quantity_kind_cast>(width(999 * m)), height(0 * km))); +static_assert(same(quantity_kind_cast>(width(1000 * m)), height(1 * km))); +static_assert(same(quantity_kind_cast>(width(999 * m)), height(0.999 * km))); +static_assert(same(quantity_kind_cast(width(1 * m)), height(1 * m))); +static_assert(same(quantity_kind_cast(width(1 * m)), height(1 * m))); +static_assert(same(quantity_kind_cast(width(999 * m)), height(0 * km))); +static_assert(same(quantity_kind_cast(width(1000 * m)), height(1 * km))); +static_assert(same(quantity_kind_cast>(width(1 * cm)), cgs_width(1 * cgs_cm))); +static_assert(same(quantity_kind_cast(width(1 * cm)), cgs_width(1 * cgs_cm))); +static_assert(same(quantity_kind_cast(width(1 * cm)), cgs_width(1 * cgs_cm))); +static_assert(same(quantity_kind_cast(width(1 * m)), cgs_width(1 * m))); +static_assert(same(quantity_kind_cast(width(1 * m)), cgs_width(1 * m))); +static_assert(same(quantity_kind_cast(width(1 * cm)), width(1 * cgs_cm))); +static_assert(same(quantity_kind_cast>(width(1 * m)), width(0 * km))); +static_assert(same(quantity_kind_cast>(width(1 * m)), width(100 * cm))); +static_assert(same(quantity_kind_cast>(width(0.01 * m)), width(1 * cm))); +static_assert(same(quantity_kind_cast>(width(1 * cgs_cm)), width(1 * cgs_cm))); +// clang-format on +template +concept invalid_cast = requires { + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; +}; +static_assert(invalid_cast); + + +///////////////////////// +// extensible interface +///////////////////////// + +namespace mylib { + +struct radius_kind : kind {}; + +struct cylinder_size {}; + +template Radius, units::QuantityKindOf Height> +cylinder_size operator+(Radius, Height); + +} // namespace mylib + +namespace yourapp { + +static_assert(is_same_v(1. * m) + height(1. * m))>); + +} // namespace yourapp + +} // namespace diff --git a/test/unit_test/static/quantity_point_kind_test.cpp b/test/unit_test/static/quantity_point_kind_test.cpp new file mode 100644 index 00000000..7c993901 --- /dev/null +++ b/test/unit_test/static/quantity_point_kind_test.cpp @@ -0,0 +1,654 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "test_tools.h" +#include "units/chrono.h" +#include "units/physical/si/cgs/derived/speed.h" +#include "units/physical/si/derived/area.h" +#include "units/physical/si/derived/frequency.h" +#include "units/physical/si/derived/speed.h" +#include "units/physical/si/fps/derived/speed.h" +#include "units/quantity_point.h" +#include "units/quantity_point_kind.h" +#include +#include +#include + +namespace { + +using namespace units; +namespace si = physical::si; +using namespace si; +using namespace unit_constants; + +constexpr auto cgs_cm = cgs::unit_constants::cm; + +using namespace std::chrono_literals; + +struct width_kind : kind {}; +struct height_kind : kind {}; +struct abscissa_kind : point_kind {}; +struct ordinate_kind : point_kind {}; + +struct distance_kind : kind {}; +struct cgs_width_kind : kind {}; +struct cgs_height_kind : kind {}; +struct rate_of_climb_kind : derived_kind {}; +struct altitude_kind : point_kind {}; + +struct apple : kind {}; +struct orange : kind {}; +struct nth_apple_kind : point_kind {}; +struct nth_orange_kind : point_kind {}; + +struct time_kind : kind {}; +struct time_point_kind : point_kind {}; + +template using width = quantity_kind; +template using height = quantity_kind; +template using abscissa = quantity_point_kind; +template using ordinate = quantity_point_kind; + +template using distance = quantity_kind; +template using cgs_width = quantity_kind; +template using cgs_height = quantity_kind; +template using rate_of_climb = quantity_kind; +template using altitude = quantity_point_kind; + +template using apples = quantity_kind; +template using oranges = quantity_kind; +template using nth_apple = quantity_point_kind; +template using nth_orange = quantity_point_kind; + +///////////// +// concepts +///////////// + +static_assert(QuantityPointKind>); +static_assert(QuantityPointKind>); +static_assert(!QuantityPointKind); +static_assert(!QuantityPointKind>); +static_assert(!QuantityPointKind>); +static_assert(!QuantityPointKind>); + +static_assert(QuantityPointKindOf, abscissa_kind>); +static_assert(!QuantityPointKindOf, ordinate_kind>); +static_assert(!QuantityPointKindOf, metre>); +static_assert(!QuantityPointKindOf, abscissa_kind>); +static_assert(!QuantityPointKindOf, metre>); +static_assert(!QuantityPointKindOf, abscissa_kind>); +static_assert(!QuantityPointKindOf, width_kind>); +static_assert(!QuantityPointKindOf, metre>); +static_assert(!QuantityPointKindOf, width_kind>); +static_assert(!QuantityPointKindOf, dim_length>); +static_assert(!QuantityPointKindOf, metre>); + + +/////////////// +// invariants +/////////////// + +static_assert(sizeof(abscissa) == sizeof(double)); +static_assert(sizeof(ordinate) == sizeof(short)); + +template +concept invalid_types = requires { + requires !requires { typename quantity_point_kind; }; // width_kind is not a point kind + requires !requires { typename quantity_point_kind; }; // unit of a different dimension + requires !requires { typename quantity_point_kind>; }; // quantity used as Rep + requires !requires { typename quantity_point_kind>; }; // quantity point used as Rep + requires !requires { typename quantity_point_kind>; }; // quantity kind used as Rep + requires !requires { typename quantity_point_kind>; }; // quantity point kind used as Rep + requires !requires { typename quantity_point_kind; }; // reordered arguments + requires !requires { typename quantity_point_kind; }; // reordered arguments +}; +static_assert(invalid_types); + +static_assert(std::is_trivially_default_constructible_v>); +static_assert(std::is_trivially_copy_constructible_v>); +static_assert(std::is_trivially_move_constructible_v>); +static_assert(std::is_trivially_copy_assignable_v>); +static_assert(std::is_trivially_move_assignable_v>); +static_assert(std::is_trivially_destructible_v>); + +static_assert(std::is_nothrow_default_constructible_v>); +static_assert(std::is_nothrow_copy_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_copy_assignable_v>); +static_assert(std::is_nothrow_move_assignable_v>); +static_assert(std::is_nothrow_destructible_v>); + +static_assert(std::is_trivially_copyable_v>); +static_assert(std::is_standard_layout_v>); + +static_assert(std::default_initializable>); +static_assert(std::move_constructible>); +static_assert(std::copy_constructible>); +static_assert(std::equality_comparable>); +static_assert(std::totally_ordered>); +static_assert(std::regular>); + +static_assert(std::three_way_comparable>); + +static_assert(!std::is_aggregate_v>); + + +/////////////////// +// member aliases +/////////////////// + +static_assert(is_same_v::point_kind_type, abscissa_kind>); +static_assert(is_same_v::kind_type, width_kind>); +static_assert(is_same_v::quantity_kind_type, width>); +static_assert(is_same_v::quantity_type, length>); +static_assert(is_same_v::dimension, dim_length>); +static_assert(is_same_v::unit, metre>); +static_assert(is_same_v::rep, double>); + + +////////////////////// +// relative observer +////////////////////// + +static_assert(same(abscissa{}.relative(), width{})); + + +//////////////////////////// +// static member functions +//////////////////////////// + +static_assert(abscissa::min().relative().common() == 0 * m); +static_assert(abscissa::max().relative().common() == std::numeric_limits::max() * m); +static_assert(abscissa::min().relative().common().count() == std::numeric_limits::lowest()); +static_assert(abscissa::max().relative().common().count() == std::numeric_limits::max()); + + +//////////////////////// +// default constructor +//////////////////////// + +// default initialization +#if !defined(COMP_MSVC) +static_assert([] { + const auto read_uninitialized_quantity = [] { + abscissa w; + ++w; + }; + return !require_constant_invocation; +}()); +#endif + +// value initialization +static_assert(abscissa{}.relative().common() == 0 * m); + + +///////// +// CTAD +///////// + +static_assert(same(quantity_point_kind(width(0 * m)), abscissa{})); +static_assert(same(quantity_point_kind(abscissa(0 * m)), abscissa{})); + + +//////////////////////////// +// construction from a rep +//////////////////////////// + +static_assert(construct_from_only>(1.0).relative().common() == 1); +static_assert(construct_from_only>(1.0f).relative().common() == 1); +static_assert(construct_from_only>(1).relative().common() == 1); +static_assert(construct_from_only>(short{1}).relative().common() == 1); +static_assert(construct_from_only>(1).relative().common() == 1); +static_assert(construct_from_only>(1).relative().common() == 1); +static_assert(construct_from_only>(one_rep{}).relative().common() == 1); +static_assert(construct_from_only>(one_rep{}).relative().common() == 1); +static_assert(construct_from_only>(one_rep{}).relative().common() == 1); +static_assert(construct_from_only>(1ULL).relative().common().count() == 1); +static_assert(construct_from_only>(1).relative().common().count() == 1); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0f)); +static_assert(!constructible_or_convertible_from>(1)); +static_assert(!constructible_or_convertible_from>(short{1})); +static_assert(!constructible_or_convertible_from>(1)); + + +///////////////////////////////// +// construction from a quantity +///////////////////////////////// + +// clang-format off +static_assert(construct_from_only>(1 * m).relative().common() == 1 * m); +static_assert(construct_from_only>(1 * m).relative().common() == 1 * m); +static_assert(construct_from_only>(1 * km).relative().common() == 1 * km); +static_assert(construct_from_only>(1ULL * m).relative().common() == 1 * m); +// static_assert(construct_from_only>(1 * cgs_cm).relative().common() == 1 * cm); // TODO: Fix #210 +static_assert(construct_from_only>(1 * m).relative().common() == 1 * m); +static_assert(construct_from_only>(1.0 * km).relative().common() == 1 * km); +static_assert(construct_from_only>(1 * cgs_cm).relative().common() == 1 * cm); +static_assert(construct_from_only>(1.0 * cgs_cm).relative().common() == 1 * cm); +static_assert(!constructible_or_convertible_from>(1 * mm)); +static_assert(!constructible_or_convertible_from>(1.0 * m)); +static_assert(!constructible_or_convertible_from>(1.0 * km)); +static_assert(!constructible_or_convertible_from>(1 * cgs_cm)); +static_assert(!constructible_or_convertible_from>(quantity(1))); +static_assert(!constructible_or_convertible_from>(1 * s)); +static_assert(!constructible_or_convertible_from>(1s)); + +static_assert(construct_from_only>(quantity(1)).relative().common() == 1); +static_assert(construct_from_only>(dimensionless(1)).relative().common() == 0.01); +static_assert(construct_from_only>(dimensionless(1)).relative().common() == 0.01); +static_assert(construct_from_only>(dimensionless(1)).relative().common().count() == 1); +static_assert(construct_from_only>(quantity(1)).relative().common().count() == 100); +static_assert(!constructible_or_convertible_from>(quantity(1.0))); +static_assert(!constructible_or_convertible_from>(dimensionless(1))); +static_assert(!constructible_or_convertible_from>(quantity(1.0))); +static_assert(!constructible_or_convertible_from>(dimensionless(1))); +static_assert(!constructible_or_convertible_from>(1 * m)); +static_assert(!constructible_or_convertible_from>(1 * s)); +static_assert(!constructible_or_convertible_from>(1s)); +// clang-format on + + +/////////////////////////////////////// +// construction from a quantity point +/////////////////////////////////////// + +static_assert(construct_from_only>(1 * m).relative().common() == 1 * m); +static_assert(construct_from_only>(quantity_point(short{1} * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(quantity_point(1 * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(quantity_point(1 * km)).relative().common() == 1 * km); +static_assert(construct_from_only>(quantity_point(1 * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(quantity_point(1 * km)).relative().common() == 1 * km); +static_assert(construct_from_only>(quantity_point(1.0 * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(quantity_point(1.0 * mm)).relative().common() == 1 * mm); +static_assert(!constructible_or_convertible_from>(quantity_point(1 * mm))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * km))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * m * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * s))); + +// clang-format off +static_assert(construct_from_only>(quantity_point(1)).relative().common() == 1); +static_assert(construct_from_only>(quantity_point(1)).relative().common() == 1); +static_assert(construct_from_only>(quantity_point(1)).relative().common() == 1); +static_assert(construct_from_only>(quantity_point(dimensionless(1))).relative().common() == 0.01); +static_assert(construct_from_only>(quantity_point(1.0)).relative().common() == 1); +static_assert(construct_from_only>(quantity_point(dimensionless(1.0))).relative().common() == 0.01); +static_assert(construct_from_only>(quantity_point(1)).relative().common().count() == 100); +static_assert(construct_from_only>(quantity_point(dimensionless(1))).relative().common().count() == 1); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * s))); +// clang-format on + + +////////////////////////////////////// +// construction from a quantity kind +////////////////////////////////////// + +// clang-format off +static_assert(construct_from_only>(width(1 * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(width(1ULL * km)).relative().common() == 1 * km); +static_assert(construct_from_only>(width(1 * cgs_cm)).relative().common() == 1 * cm); +static_assert(construct_from_only>(width(1 * cgs_cm)).relative().common() == 1 * cm); +static_assert(construct_from_only>(width(1 * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(width(1.0 * mm)).relative().common() == 1 * mm); +static_assert(construct_from_only>(width(1ULL * km)).relative().common() == 1 * km); +static_assert(!constructible_or_convertible_from>(width(1.0 * m))); +static_assert(!constructible_or_convertible_from>(width(1 * mm))); +static_assert(!constructible_or_convertible_from>(height(1 * m))); +static_assert(!constructible_or_convertible_from>(abscissa_kind{}, width(1.0 * m))); +static_assert(!constructible_or_convertible_from>(abscissa_kind{}, width(1 * mm))); +static_assert(!constructible_or_convertible_from>(abscissa_kind{}, height(1 * m))); + +static_assert(construct_from_only>(apples(1)).relative().common() == 1); +static_assert(construct_from_only>(apples(dimensionless(1))).relative().common() == 0.01); +static_assert(construct_from_only>(apples(1)).relative().common().count() == 100); +static_assert(construct_from_only>(apples(dimensionless(1))).relative().common().count() == 1); +static_assert(!constructible_or_convertible_from>(apples(1.0))); +static_assert(!constructible_or_convertible_from>(apples(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(apples(1.0))); +static_assert(!constructible_or_convertible_from>(apples(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(width(1 * m) / m)); +static_assert(!constructible_or_convertible_from>(oranges(1))); +// clang-format on + + +////////////////////////////////////////////////// +// construction from another quantity point kind +////////////////////////////////////////////////// + +// clang-format off +static_assert(construct_and_convert_from>(abscissa(1 * m)).relative().common() == 1 * m); +static_assert(construct_and_convert_from>(abscissa(1ULL * km)).relative().common() == 1 * km); +static_assert(construct_and_convert_from>(abscissa(1ULL * m)).relative().common() == 1 * m); +static_assert(construct_and_convert_from>(abscissa(1 * cgs_cm)).relative().common() == 1 * cm); +static_assert(construct_and_convert_from>(abscissa(1 * cgs_cm)).relative().common() == 1 * cm); +static_assert(!constructible_or_convertible_from>(abscissa(1.0 * m))); +static_assert(!constructible_or_convertible_from>(abscissa(1 * m))); +static_assert(!constructible_or_convertible_from>(ordinate(1 * m))); +static_assert(!constructible_or_convertible_from>(quantity_point_kind(1 * s))); + +static_assert(construct_and_convert_from>(nth_apple(1)).relative().common() == 1); +static_assert(construct_and_convert_from>(nth_apple(dimensionless(1))).relative().common() == 0.01); +static_assert(construct_and_convert_from>(nth_apple(1)).relative().common().count() == 100); +static_assert(construct_and_convert_from>(nth_apple(dimensionless(1))).relative().common().count() == 1); +static_assert(!constructible_or_convertible_from>(nth_apple(1.0))); +static_assert(!constructible_or_convertible_from>(nth_apple(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(nth_apple(1.0))); +static_assert(!constructible_or_convertible_from>(nth_apple(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(nth_orange(1))); +static_assert(!constructible_or_convertible_from>(abscissa(1 * m))); +// clang-format on + + +////////////////////// +// other conversions +////////////////////// + +static_assert(!std::is_convertible_v, int>); +static_assert(!std::is_convertible_v, dimensionless>); +static_assert(!std::is_convertible_v, length>); +static_assert(!std::is_convertible_v, width>); +static_assert(!std::is_convertible_v, height>); +static_assert(!std::is_convertible_v, quantity_point>); + + +//////////////////////// +// assignment operator +//////////////////////// + +// clang-format off +static_assert((abscissa(2 * m) = abscissa(1 * m)).relative().common() == 1 * m); +static_assert((abscissa(2 * m) = abscissa(1 * km)).relative().common() == 1 * km); +static_assert(!std::is_assignable_v, abscissa>); +static_assert(!std::is_assignable_v, abscissa>); +// clang-format on + + +///////////////////// +// member operators +///////////////////// + +#if !defined(COMP_MSVC) || defined(NDEBUG) +static_assert([]() { + const width w(1 * m); + quantity_point_kind x(w); + assert(&++x == &x && x.relative().common() == 2 * m); + assert(&--x == &x && x.relative().common() == 1 * m); + assert((x++).relative().common() == 1 * m && x.relative().common() == 2 * m); + assert((x--).relative().common() == 2 * m && x.relative().common() == 1 * m); + assert(&(x += w) == &x && x.relative().common() == 2 * m); + assert(&(x -= w) == &x && x.relative().common() == 1 * m); + return true; +}()); +#endif + +template +concept invalid_compound_assignments_ = requires(quantity_point_kind x, Qx q) { + requires !requires { x += q; }; + requires !requires { x -= q; }; +}; +template +concept invalid_compound_assignments = requires(quantity_point_kind x) { + requires !requires { x += 1; }; + requires !requires { x -= 1; }; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_; +}; +static_assert(invalid_compound_assignments); +static_assert(invalid_compound_assignments_); +#if __cpp_lib_chrono >= 201907L +static_assert(invalid_compound_assignments_); +#endif + + +///////////////////////// +// non-member operators +///////////////////////// + +// clang-format off +static_assert(same(abscissa(2 * m) + width(3 * m), abscissa(5 * m))); +static_assert(same(abscissa(2 * m) + width(3. * m), abscissa(5. * m))); +static_assert(same(abscissa(2. * m) + width(3 * m), abscissa(5. * m))); +static_assert(same(abscissa(2 * km) + width(3e3 * m), abscissa(5e3 * m))); +static_assert(same(abscissa(2e3 * m) + width(3 * km), abscissa(5e3 * m))); +static_assert(same(width(2 * m) + abscissa(3 * m), abscissa(5 * m))); +static_assert(same(width(2 * m) + abscissa(3. * m), abscissa(5. * m))); +static_assert(same(width(2. * m) + abscissa(3 * m), abscissa(5. * m))); +static_assert(same(width(2 * km) + abscissa(3e3 * m), abscissa(5e3 * m))); +static_assert(same(width(2e3 * m) + abscissa(3 * km), abscissa(5e3 * m))); +static_assert(!std::is_invocable_v, abscissa, double>); +static_assert(!std::is_invocable_v, abscissa, length>); +static_assert(!std::is_invocable_v, abscissa, quantity_point>); +static_assert(!std::is_invocable_v, abscissa, height>); +static_assert(!std::is_invocable_v, abscissa, abscissa>); +static_assert(!std::is_invocable_v, abscissa, abscissa>); +static_assert(!std::is_invocable_v, abscissa, abscissa>); +static_assert(!std::is_invocable_v, height, abscissa>); +static_assert(!std::is_invocable_v, quantity_point, abscissa>); +static_assert(!std::is_invocable_v, length, abscissa>); +static_assert(!std::is_invocable_v, double, abscissa>); + +static_assert(same(abscissa(2 * m) - width(3 * m), abscissa(-1 * m))); +static_assert(same(abscissa(2 * m) - width(3. * m), abscissa(-1. * m))); +static_assert(same(abscissa(2. * m) - width(3 * m), abscissa(-1. * m))); +static_assert(same(abscissa(2 * km) - width(3e3 * m), abscissa(-1e3 * m))); +static_assert(same(abscissa(2e3 * m) - width(3 * km), abscissa(-1e3 * m))); +static_assert(same(abscissa(2 * m) - abscissa(3 * m), width(-1 * m))); +static_assert(same(abscissa(2 * m) - abscissa(3. * m), width(-1. * m))); +static_assert(same(abscissa(2. * m) - abscissa(3 * m), width(-1. * m))); +static_assert(same(abscissa(2 * km) - abscissa(3e3 * m), width(-1e3 * m))); +static_assert(same(abscissa(2e3 * m) - abscissa(3 * km), width(-1e3 * m))); +static_assert(!std::is_invocable_v, abscissa, double>); +static_assert(!std::is_invocable_v, abscissa, length>); +static_assert(!std::is_invocable_v, abscissa, quantity_point>); +static_assert(!std::is_invocable_v, abscissa, height>); +static_assert(!std::is_invocable_v, abscissa, ordinate>); +static_assert(!std::is_invocable_v, ordinate, abscissa>); +static_assert(!std::is_invocable_v, height, abscissa>); +static_assert(!std::is_invocable_v, quantity_point, abscissa>); +static_assert(!std::is_invocable_v, length, abscissa>); +static_assert(!std::is_invocable_v, double, abscissa>); +// clang-format on + + +///////////////////////// +// comparison operators +///////////////////////// + +// clang-format off +static_assert(abscissa(1 * m) == abscissa(1 * m)); +static_assert(abscissa(1 * m) == abscissa(1.0 * m)); +static_assert(abscissa(1 * m) == abscissa(1000 * mm)); +static_assert(abscissa(1 * m) == abscissa(1e3 * mm)); +static_assert(abscissa(2 * m) != abscissa(1 * m)); +static_assert(abscissa(2 * m) != abscissa(1.0 * cgs_cm)); +static_assert(std::equality_comparable_with, abscissa>); +static_assert(std::equality_comparable_with, abscissa>); +static_assert(std::equality_comparable_with, abscissa>); +static_assert(std::equality_comparable_with, abscissa>); +// clang-format on +template +concept invalid_equality = requires(quantity_point_kind x) { + requires !requires { x == 1; }; + requires !requires { x != 1.0; }; + requires !requires { x == 1 * m; }; + requires !requires { x != 1.0 * cgs_cm; }; + requires !requires { x == 1 * km; }; + requires !requires { x != quantity(1); }; + requires !requires { x == dimensionless(1.0); }; + requires !requires { x != width(1 * m); }; + requires !requires { x == width(1.0 * km); }; + requires !requires { x != height(1 * m); }; + requires !requires { x == height(1.0 * km); }; + requires !requires { x == rate_of_climb(1.0 * km / h); }; + requires !requires { x != quantity_point(1 * m); }; + requires !requires { x == quantity_point(1.0 * mm); }; + requires !requires { x != quantity_point(quantity(1)); }; + requires !requires { x == quantity_point(dimensionless(1.0)); }; + requires !requires { x != quantity_point_kind(cgs_width(1 * m)); }; + requires !requires { x == ordinate(1 * m); }; +}; +static_assert(invalid_equality); + +// clang-format off +static_assert(abscissa(1 * m) < abscissa(2 * m)); +static_assert(abscissa(1 * m) <= abscissa(2.0 * m)); +static_assert(abscissa(1 * m) <= abscissa(1 * km)); +static_assert(abscissa(1 * m) >= abscissa(1e3 * mm)); +static_assert(abscissa(2 * m) >= abscissa(1 * mm)); +static_assert(abscissa(2 * m) > abscissa(1 * cgs_cm)); +static_assert(std::three_way_comparable_with, abscissa>); +static_assert(std::three_way_comparable_with, abscissa>); +static_assert(std::three_way_comparable_with, abscissa>); +static_assert(std::three_way_comparable_with, abscissa>); +// clang-format on +template +concept invalid_relational = requires(quantity_point_kind x) { + requires !requires { x < 1; }; + requires !requires { x <= 1.0; }; + requires !requires { x >= 1 * m; }; + requires !requires { x > 1.0 * cgs_cm; }; + requires !requires { x <=> 1 * km; }; + requires !requires { x < quantity(1); }; + requires !requires { x <= dimensionless(1.0); }; + requires !requires { x >= width(1 * m); }; + requires !requires { x > width(1.0 * km); }; + requires !requires { x <=> height(1 * m); }; + requires !requires { x < height(1.0 * km); }; + requires !requires { x <= rate_of_climb(1.0 * km / h); }; + requires !requires { x >= quantity_point(1 * m); }; + requires !requires { x > quantity_point(1.0 * mm); }; + requires !requires { x <=> quantity_point(quantity(1)); }; + requires !requires { x < quantity_point(dimensionless(1.0)); }; + requires !requires { x <= quantity_point_kind(cgs_width(1 * m)); }; + requires !requires { x >= ordinate(1 * m); }; +}; +static_assert(invalid_relational); + + +///////////////////////////// +// quantity_point_kind_cast +///////////////////////////// + +// clang-format off +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(1 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(1.0 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), abscissa(0 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1000 * m)), abscissa(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), abscissa(0.999 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(1 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(1.0 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), abscissa(0 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1000 * m)), abscissa(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), abscissa(0.999 * km))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), abscissa(1.0 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), abscissa(1 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(999 * m)), abscissa(0 * km))); +static_assert(same(quantity_point_kind_cast(abscissa(1000 * m)), abscissa(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), ordinate(1 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), ordinate(1.0 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), ordinate(0 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1000 * m)), ordinate(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), ordinate(0.999 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), ordinate(1 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), ordinate(1.0 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), ordinate(0 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1000 * m)), ordinate(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), ordinate(0.999 * km))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), ordinate(1 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), ordinate(1 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(999 * m)), ordinate(0 * km))); +static_assert(same(quantity_point_kind_cast(abscissa(1000 * m)), ordinate(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * cm)), quantity_point_kind(cgs_width(1 * cgs_cm)))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * cm)), quantity_point_kind(cgs_width(1 * cgs_cm)))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * cm)), altitude(1 * cgs_cm))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * cm)), altitude(1 * cgs_cm))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), quantity_point_kind(cgs_width(1 * m)))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), altitude(1 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), altitude(1 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * cm)), abscissa(1 * cgs_cm))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(0 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(100 * cm))); +static_assert(same(quantity_point_kind_cast>(abscissa(0.01 * m)), abscissa(1 * cm))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * cgs_cm)), abscissa(1 * cgs_cm))); +// clang-format on +template +concept invalid_cast = requires(Int i) { + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; +}; +static_assert(invalid_cast); + + +///////////////////////// +// extensible interface +///////////////////////// + +namespace mylib { + +struct width_kind : kind {}; +struct height_kind : kind {}; +struct abscissa_kind : point_kind {}; +struct ordinate_kind : point_kind {}; + +struct point {}; + +template Abscissa, units::QuantityPointKindOf Ordinate> +point operator+(Abscissa, Ordinate); + +} // namespace mylib + +namespace yourapp { + +static_assert(is_same_v(1 * m)) + + quantity_point_kind(quantity_kind(1 * m)))>); + +} // namespace yourapp + +} // namespace diff --git a/test/unit_test/static/quantity_point_test.cpp b/test/unit_test/static/quantity_point_test.cpp index 36a10bc1..5acf737d 100644 --- a/test/unit_test/static/quantity_point_test.cpp +++ b/test/unit_test/static/quantity_point_test.cpp @@ -22,18 +22,20 @@ #include "units/quantity_point.h" #include "test_tools.h" +#include "units/chrono.h" #include "units/math.h" #include "units/physical/si/derived/area.h" #include "units/physical/si/derived/speed.h" #include "units/physical/si/derived/volume.h" #include "units/physical/si/us/base/length.h" -#include #include namespace { using namespace units; -using namespace units::physical::si; +using namespace physical::si; +using namespace unit_constants; +using namespace std::chrono_literals; // class invariants @@ -55,9 +57,17 @@ static_assert(is_same_v::rep, int>); static_assert(is_same_v::rep, double>); static_assert(is_same_v::unit, metre>); static_assert(is_same_v::unit, kilometre>); +static_assert(is_same_v::dimension, dim_length>); +static_assert(is_same_v::quantity_type, quantity>); // constructors +static_assert(quantity_point(1).relative() == quantity(1)); +static_assert(!std::is_convertible_v>); + +static_assert(quantity_point(42s).relative() == 42 * s); +static_assert(!std::is_convertible_v>); + static_assert(quantity_point().relative() == 0_q_m); constexpr quantity_point km{1000_q_m}; static_assert(km.relative() == 1000_q_m); diff --git a/test/unit_test/static/quantity_test.cpp b/test/unit_test/static/quantity_test.cpp index 81512094..050187cd 100644 --- a/test/unit_test/static/quantity_test.cpp +++ b/test/unit_test/static/quantity_test.cpp @@ -220,7 +220,7 @@ static_assert(!std::constructible_from, length, length>); // truncating metre -> kilometre not allowed // converting to double always OK -static_assert(std::constructible_from, length>); +static_assert(std::constructible_from, length>); static_assert(std::convertible_to, length>); static_assert(std::constructible_from, length>); static_assert(std::convertible_to, length>); diff --git a/test/unit_test/static/test_tools.h b/test/unit_test/static/test_tools.h index e4f7975e..47a8463e 100644 --- a/test/unit_test/static/test_tools.h +++ b/test/unit_test/static/test_tools.h @@ -23,6 +23,9 @@ #pragma once #include "units/bits/equivalent.h" +#include +#include +#include template inline constexpr bool compare_impl = false; @@ -36,3 +39,87 @@ inline constexpr bool compare_impl = units::equivalent; template inline constexpr bool compare = compare_impl, std::remove_cvref_t>; + +template +constexpr bool constructible_from(Vs...) { + return std::constructible_from; +} + +template +concept convertible_from_ = requires(Us... us) { [](T) {}({us...}); }; + +template +constexpr bool convertible_from(Vs...) { + if constexpr (sizeof...(Us) + sizeof...(Vs) == 1) + return std::is_convertible_v; + else + return convertible_from_; +} + +template +constexpr bool constructible_or_convertible_from(Vs...) { + return constructible_from() || convertible_from(); +} + +template +constexpr bool constructible_and_convertible_from(Vs...) { + return constructible_from() && convertible_from(); +} + +template + requires (constructible_from()) +constexpr T construct_from(Us... us) { + return T(us...); +} + +template + requires (convertible_from()) +constexpr T convert_from(Us... us) { + if constexpr (sizeof...(Us) == 1) + return [](T t) { return t; }(us...); + else + return {us...}; +} + +template + requires (constructible_from() && convertible_from()) +constexpr T construct_and_convert_from(Us... us) { + T t{construct_from(us...)}; + assert(t == convert_from(us...)); + return t; +} + +template + requires (constructible_from() && !convertible_from()) +constexpr T construct_from_only(Us... us) { + return construct_from(us...); +} + +#if !defined(COMP_GCC) +template typename T, typename = std::void_t<>, typename... Us> +concept ctad_constructible_from_ = requires(Us... us) { T(us...); }; +#else +template typename T, typename = std::void_t<>, typename... Us> +inline constexpr bool ctad_constructible_from_ = false; + +template typename T, typename... Us> +inline constexpr bool ctad_constructible_from_, Us...> = true; +#endif + +template typename T, typename... Us, typename... Vs> +constexpr bool ctad_constructible_from(Vs...) { + return ctad_constructible_from_; +} + +#if UNITS_DOWNCAST_MODE +constexpr auto same = [](T l, T r) { return l == r; }; +#else +constexpr auto same = [](T l, U r) + requires requires { l == r; } + // requires std::equality_comparable_with // TODO: Fix #205 + { return l == r; }; +#endif + +template + requires requires { F(); } +inline constexpr bool require_constant_invocation = requires { new int[1][(F(), 1)]; }; diff --git a/test/unit_test/static/unit_test.cpp b/test/unit_test/static/unit_test.cpp index be223c56..169f21f0 100644 --- a/test/unit_test/static/unit_test.cpp +++ b/test/unit_test/static/unit_test.cpp @@ -51,6 +51,8 @@ struct metre_per_second : unit {}; struct dim_speed : derived_dimension, units::exponent> {}; struct kilometre_per_hour : deduced_unit {}; +static_assert(equivalent); +static_assert(equivalent); static_assert(compare>, metre>); static_assert(compare>, centimetre>); static_assert(compare>, yard>);