diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index a26079df..79f5b1ad 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -49,6 +49,7 @@ set(unitsSphinxDocs "${CMAKE_CURRENT_SOURCE_DIR}/design/directories.rst" "${CMAKE_CURRENT_SOURCE_DIR}/design/downcasting.rst" "${CMAKE_CURRENT_SOURCE_DIR}/design/quantity.rst" + "${CMAKE_CURRENT_SOURCE_DIR}/design/quantity_kind.rst" "${CMAKE_CURRENT_SOURCE_DIR}/examples.rst" "${CMAKE_CURRENT_SOURCE_DIR}/examples/avg_speed.rst" diff --git a/docs/design/quantity_kind.rst b/docs/design/quantity_kind.rst new file mode 100644 index 00000000..78505d1d --- /dev/null +++ b/docs/design/quantity_kind.rst @@ -0,0 +1,52 @@ +.. namespace:: units + +quantity_kind +============= + +The ``quantity_kind`` class template provides a similar interface to ``quantity``. + +Kinds +----- + +The first template parameter of ``quantity_kind`` is a ``Kind``. + +``Kind``s, by themselves: + + * Wrap a ``Dimension``, and + * opt into the downcasting facility. + +If a dimensional analysis operator on a quantity of kind *A* +can result on a quantity of kind *B*, +*A* and *B* are *related*. + +The library provides two ``Kind`` bases, ``kind`` and ``derived_kind``. + +``kind`` is an entry point, and there's only one per set of related kinds. + +``derived_kind`` is used to explicitly name other related kinds. +Unnamed, they look like ``detail::_kind_base``. + +Unnamed kinds, like unnamed units and dimensions, +allows (intermediate) results in formulas without having to name them. + +Quantity kinds +-------------- + +``quantity_kind`` wraps a ``quantity`` and layers over its dimensional analysis. +While all properties of the ``quantity`` apply transparently, +results are always ``quantity_kind``s related to the kind(s) of the argument(s). + +One of the arguments to the dimensional analysis operators can also be ``Quantity``. + +The way back to ``quantity`` is through the ``.common()`` observer. + +Intra-kind operations +--------------------- + +``quantity_kind`` takes care of operators between related quantity kinds. +Intra-kind operations can be opted into as follows:: + + struct width : kind {}; + struct height : kind {}; + + size2d operator+(QuantityKindOf auto, QuantityKindOf auto); diff --git a/docs/framework/dimensions.rst b/docs/framework/dimensions.rst index bde6378a..d81c8258 100644 --- a/docs/framework/dimensions.rst +++ b/docs/framework/dimensions.rst @@ -67,23 +67,40 @@ dimension, than we will end up with just a dimensionless quantity: Quantity Kinds ++++++++++++++ -Quantity kinds behave the same as quantities for addition and subtraction. -The same behavior is also provided for multiplication with, and division by -a :term:`scalable number`. - -Multiplication and division with a quantity (but not a quantity kind) is allowed. -The result is a quantity kind with the appropriate dimension -and related to the original quantity kind: +Quantity kinds behave the same as quantities for all operations, +except that the quantity types in the operators' declarations +are quantity kind types instead. +Additionally, for the dimensional analysis operators, +you can use a quantity argument instead of a quantity kind. .. code-block:: - :emphasize-lines: 5-6 + :emphasize-lines: 8-9 - struct height : kind {}; - struct rate_of_climb : derived_kind {}; + struct height_kind : kind {}; + struct rate_of_climb_kind : derived_kind {}; - quantity_kind h(height{}, 100 * m); - quantity_point_kind rate = h / (25 * s); - // quantity_point_kind(4 * m / s) + template using height = quantity_kind; + template using rate_of_climb = quantity_kind; + + height h{100 * m}; + rate_of_climb rate = h / (25 * s); + // quantity_kind(4 * m / s) + +.. code-block:: + :emphasize-lines: 8-12 + + struct width_kind : kind {}; + struct horizontal_area_kind : derived_kind {}; + + template using width = quantity_kind; + template using horizontal_area = quantity_kind; + + width w{5 * m}; + horizontal_area area1 = w * w; + // quantity_kind(25 * m * m) + width w2 = area1 / w; // quantity_kind(5 * m) + auto q1 = w / w; // Dimensionless quantity kinds related to width + auto q2 = w / (5 * m); // with .common() equal to quantity{1} Quantity Points +++++++++++++++ diff --git a/docs/framework/quantity_kinds.rst b/docs/framework/quantity_kinds.rst index 73528afd..36b67913 100644 --- a/docs/framework/quantity_kinds.rst +++ b/docs/framework/quantity_kinds.rst @@ -41,7 +41,7 @@ the value to the `quantity_kind` class template constructor:: Differences To Quantity ----------------------- -Unlike `quantity`, the library provides: +The library provides: - no kinds, such as ``radius`` or ``width``, therefore @@ -49,7 +49,7 @@ Unlike `quantity`, the library provides: * 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 +- a slightly larger set of operations on quantity kinds (see the :ref:`framework/dimensions:Quantity Kinds` chapter). diff --git a/example/glide_computer.cpp b/example/glide_computer.cpp index d3576ac9..be410b8b 100644 --- a/example/glide_computer.cpp +++ b/example/glide_computer.cpp @@ -100,8 +100,8 @@ 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(); } { +constexpr Dimensionless auto operator/(const QK1& lhs, const QK2& rhs) + requires (!units::QuantityKindRelatedTo) && requires { lhs.common() / rhs.common(); } { return lhs.common() / rhs.common(); } @@ -258,7 +258,7 @@ struct flight_point { constexpr altitude terrain_level_alt(const task& t, distance dist) { const height alt_diff = t.finish.alt - t.start.alt; - return t.start.alt + alt_diff * (dist / t.dist); + return t.start.alt + alt_diff * (dist / t.dist).common(); } constexpr height agl(altitude glider_alt, altitude terrain_level) @@ -283,7 +283,7 @@ void print(std::string_view phase_name, const flight_point& point, const flight_ flight_point tow(const flight_point& point, const aircraft_tow& at) { - const duration d = at.height_agl / at.performance; + const duration d = (at.height_agl / at.performance).common(); const flight_point new_point{point.dur + d, point.dist, point.alt + at.height_agl}; print("Tow", point, new_point); @@ -295,7 +295,7 @@ flight_point circle(const flight_point& point, const glider& g, const weather& w const height h_agl = agl(point.alt, terrain_level_alt(t, point.dist)); const height circle_height = std::min(w.cloud_base - h_agl, height_to_gain); const rate_of_climb circling_rate = w.thermal_strength + g.polar[0].climb; - const duration d = circle_height / circling_rate; + const duration d = (circle_height / circling_rate).common(); const flight_point new_point{point.dur + d, point.dist, point.alt + circle_height}; height_to_gain -= circle_height; diff --git a/src/include/units/quantity.h b/src/include/units/quantity.h index 4aef869f..9ea29b5a 100644 --- a/src/include/units/quantity.h +++ b/src/include/units/quantity.h @@ -246,6 +246,16 @@ public: return *this; } + template + constexpr quantity& operator%=(const dimensionless& rhs) + requires (!floating_point_) && (!floating_point_) && + requires(rep a, const Rep2 b) { { a %= b } -> std::same_as; } + { + gsl_ExpectsAudit(rhs.count() != quantity_values::zero()); + value_ %= rhs.count(); + return *this; + } + constexpr quantity& operator%=(const quantity& q) requires (!floating_point_) && requires(rep a, rep b) { { a %= b } -> std::same_as; } diff --git a/src/include/units/quantity_kind.h b/src/include/units/quantity_kind.h index e8ec9fb5..2e40d978 100644 --- a/src/include/units/quantity_kind.h +++ b/src/include/units/quantity_kind.h @@ -53,6 +53,15 @@ inline constexpr auto& downcasted_kind = downcasted_kind_fn +concept QuantityKindRelatedTo = QuantityKind && QuantityKind && + equivalent; + /** * @brief A quantity kind * @@ -180,6 +189,13 @@ public: q_ *= rhs; return *this; } + template + constexpr quantity_kind& operator*=(const quantity_kind, units::one, Rep2>& rhs) + requires requires(quantity_type q) { q *= rhs.common(); } + { + q_ *= rhs.common(); + return *this; + } template constexpr quantity_kind& operator/=(const Rep2& rhs) @@ -189,15 +205,29 @@ public: q_ /= rhs; return *this; } + template + constexpr quantity_kind& operator/=(const quantity_kind, units::one, Rep2>& rhs) + requires requires(quantity_type q) { q /= rhs.common(); } + { + q_ /= rhs.common(); + return *this; + } template constexpr quantity_kind& operator%=(const Rep2& rhs) - requires (!Quantity) && requires(quantity_type q, const Rep2 r) { q %= r; } + requires (!Quantity || Dimensionless) && requires(quantity_type q, const Rep2 r) { q %= r; } { gsl_ExpectsAudit(rhs != quantity_values::zero()); q_ %= rhs; return *this; } + template + constexpr quantity_kind& operator%=(const quantity_kind, units::one, Rep2>& rhs) + requires requires(quantity_type q) { q %= rhs.common(); } + { + q_ %= rhs.common(); + return *this; + } constexpr quantity_kind& operator%=(const quantity_kind& qk) requires requires(quantity_type q) { q %= qk.common(); } @@ -256,9 +286,6 @@ public: [[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(); } @@ -287,6 +314,13 @@ template return detail::downcasted_kind(lhs * rhs.common()); } +template QK2> +[[nodiscard]] constexpr QuantityKind auto operator*(const QK1& lhs, const QK2& rhs) + requires requires { lhs.common() * rhs.common(); } +{ + return detail::downcasted_kind(lhs.common() * rhs.common()); +} + template [[nodiscard]] constexpr QuantityKind auto operator/(const QK& lhs, const Q& rhs) requires requires { lhs.common() / rhs; } @@ -303,6 +337,13 @@ template return detail::downcasted_kind(lhs / rhs.common()); } +template QK2> +[[nodiscard]] constexpr QuantityKind auto operator/(const QK1& lhs, const QK2& rhs) + requires requires { lhs.common() / rhs.common(); } +{ + return detail::downcasted_kind(lhs.common() / rhs.common()); +} + template [[nodiscard]] constexpr QuantityKind auto operator%(const QK& lhs, const D& rhs) requires requires { lhs.common() % rhs; } diff --git a/src/include/units/quantity_point_kind.h b/src/include/units/quantity_point_kind.h index 94847c7e..0503bba1 100644 --- a/src/include/units/quantity_point_kind.h +++ b/src/include/units/quantity_point_kind.h @@ -182,9 +182,6 @@ public: }; -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>; diff --git a/test/unit_test/static/quantity_kind_test.cpp b/test/unit_test/static/quantity_kind_test.cpp index 301e687d..b10b7e8a 100644 --- a/test/unit_test/static/quantity_kind_test.cpp +++ b/test/unit_test/static/quantity_kind_test.cpp @@ -309,6 +309,7 @@ static_assert(construct_from_only>(1.0 * cm).comm 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); +// clang-format on static_assert(construct_and_convert_from>(width(1 * m)).common() == 1 * m); static_assert(!constructible_or_convertible_from>(width(1.0 * m))); @@ -322,7 +323,6 @@ static_assert(construct_and_convert_from>(width>(height(1 * m))); static_assert(!constructible_or_convertible_from>(width(1 * m) / m)); static_assert(!constructible_or_convertible_from>(oranges(1))); -// clang-format on ////////////////////////////////// @@ -380,23 +380,42 @@ static_assert([]() { assert(&(w -= w) == &w && w.common() == 0 * m); w = width(3 * m); assert(&(w *= 3) == &w && w.common() == 9 * m); + assert(&(w *= quantity(1)) == &w && w.common() == 9 * m); + assert(&(w *= (w / w)) == &w && w.common() == 9 * m); assert(&(w /= 2) == &w && w.common() == 4 * m); + assert(&(w /= quantity(1)) == &w && w.common() == 4 * m); + assert(&(w /= (w / w)) == &w && w.common() == 4 * m); assert(&(w %= 3) == &w && w.common() == 1 * m); + assert(&(w %= quantity(3)) == &w && w.common() == 1 * m); + assert(&(w %= 3 * (w / w)) == &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 *= quantity(1.0)) == &w && w.common() == 11 * m); + assert(&(w *= 1.0 * (w / w)) == &w && w.common() == 11 * m); assert(&(w /= 3.9) == &w && w.common() == 2 * m); + assert(&(w /= quantity(1.0)) == &w && w.common() == 2 * m); + assert(&(w /= 1.0 * (w / w)) == &w && w.common() == 2 * m); return true; }()); #endif static_assert((std::uint8_t(255) * m %= 256) == (width(255 * m) %= 256).common()); +static_assert((std::uint8_t(255) * m %= quantity(256)) == + (width(255 * m) %= quantity(256)).common()); +// static_assert((std::uint8_t(255) * m %= 256 * m) == +// (width(255 * m) %= +// quantity_kind, one, std::uint8_t>(256)).common()); // UB // static_assert((std::uint8_t(255) * m %= 256 * m) != -// (width(255 * m) %= width(256 * m)).common()); // UB +// (width(255 * m) %= width(256 * m)).common()); // UB static_assert((std::uint8_t(255) * m %= 257) == (width(255 * m) %= 257).common()); -// TODO: Fix +static_assert((std::uint8_t(255) * m %= quantity(257)) == + (width(255 * m) %= quantity(257)).common()); static_assert((std::uint8_t(255) * m %= 257 * m) == - (width(255 * m) %= width(257 * m)).common()); + (width(255 * m) %= + quantity_kind, one, std::uint8_t>(257)).common()); +static_assert((std::uint8_t(255) * m %= 257 * m) == + (width(255 * m) %= width(257 * m)).common()); static_assert(same((-width(short{1} * m)).common(), int{-1} * m)); @@ -409,11 +428,20 @@ concept invalid_compound_assignments_ = requires(quantity_kind w, Qx requires !requires { w %= q; }; }; template -concept invalid_compound_assignments = requires(quantity_kind w) { +concept invalid_compound_assignments = requires(quantity_kind w, height h) { requires !requires { w += 1; }; requires !requires { w -= 1; }; + requires !requires { w *= 1 * km / m; }; + requires !requires { w /= 1 * km / m; }; + requires !requires { w %= 1 * km / m; }; + requires !requires { w *= quantity_kind, scaled_unit, int>{1}; }; + requires !requires { w /= quantity_kind, scaled_unit, int>{1}; }; + requires !requires { w %= quantity_kind, scaled_unit, int>{1}; }; requires !requires { w %= 1.0; }; - requires !requires { w %= w * 1.0; }; + requires !requires { w %= quantity(1.0); }; + requires !requires { w %= 1.0 * (w / w); }; + requires !requires { w %= 1.0 * w; }; + requires !requires { w %= h / h; }; requires invalid_compound_assignments_>; requires invalid_compound_assignments_>; requires invalid_compound_assignments_>; @@ -477,6 +505,26 @@ 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(width(2 * m) * quantity(3), width(6 * m))); +static_assert(same(width(2 * m) * quantity(3.), width(6. * m))); +static_assert(same(width(2. * m) * quantity(3), width(6. * m))); +static_assert(same(quantity(2) * width(3 * m), width(6 * m))); +static_assert(same(quantity(2) * width(3. * m), width(6. * m))); +static_assert(same(quantity(2.) * width(3 * m), width(6. * m))); + +static_assert(same(width(2 * m) * quantity_kind, one, int>(3), + width(6 * m))); +static_assert(same(width(2 * m) * quantity_kind, one, double>(3.), + width(6. * m))); +static_assert(same(width(2. * m) * quantity_kind, one, int>(3), + width(6. * m))); +static_assert(same(quantity_kind, one, int>(2) * width(3 * m), + width(6 * m))); +static_assert(same(quantity_kind, one, int>(2) * width(3. * m), + width(6. * m))); +static_assert(same(quantity_kind, one, double>(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))); @@ -484,13 +532,65 @@ static_assert(same((2 * Hz) * height(3 * m), rate_of_climb(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(quantity_kind(2 * s) * (3 * Hz), + quantity_kind, one, int>(6))); +static_assert(same((3 * Hz) * quantity_kind(2 * s), + quantity_kind, one, int>(6))); + +static_assert(same(apples(2) * quantity(2), apples(4))); +static_assert(same(quantity(2) * apples(2), apples(4))); + +// clang-format off +static_assert(same(width(2 * m) * width(2 * m), horizontal_area(4 * m * m))); +static_assert(same(width(2 * m) * width(2 * m), horizontal_area(4 * m * m))); +static_assert(same(width(2 * m) * width(2 * m), horizontal_area(4 * m * m))); +// clang-format on + +static_assert(same(apples(2) * apples(2), apples(4))); +static_assert(same(apples(2) * (2 / apples(1)), apples(4))); + +static_assert(same(width(2 * m) * width(2 * m), horizontal_area(4 * m * m))); +static_assert(same(width(2 * m) * (1 / width(2 * m)), + quantity_kind, one>(1))); + 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(width(2 * m) / quantity(3), width(0 * m))); +static_assert(same(width(2 * m) / quantity(3.), width(2 / 3. * m))); +static_assert(same(width(2. * m) / quantity(3), width(2. / 3 * m))); + +static_assert(same(width(2 * m) / quantity_kind, one, int>(3), + width(0 * m))); +static_assert(same(width(2 * m) / quantity_kind, one, double>(3.), + width(2 / 3. * m))); +static_assert(same(width(2. * m) / quantity_kind, one, double>(3), + width(2. / 3 * m))); + +static_assert(same(2 / quantity_kind(3 * s), + quantity_kind, hertz, int>(2 / 3 / s))); +static_assert(same(2 / quantity_kind(3. * s), + quantity_kind, hertz, double>(2 / 3. / s))); +static_assert(same(2. / quantity_kind(3 * s), + quantity_kind, hertz, double>(2 / 3. / s))); + +static_assert(same(quantity(2) / quantity_kind(3 * s), + quantity_kind, hertz, int>(2 / 3 / s))); +static_assert(same(quantity(2) / quantity_kind(3. * s), + quantity_kind, hertz, double>(2 / 3. / s))); +static_assert(same(quantity(2.) / quantity_kind(3 * s), + quantity_kind, hertz, double>(2 / 3. / s))); + +static_assert( + same(quantity_kind, one, int>(2) / quantity_kind(3 * s), + quantity_kind, hertz, int>(2 / 3 / s))); +static_assert( + same(quantity_kind, one, int>(2) / quantity_kind(3. * s), + quantity_kind, hertz, double>(2 / 3. / s))); +static_assert( + same(quantity_kind, one, double>(2.) / quantity_kind(3 * s), + quantity_kind, hertz, double>(2 / 3. / s))); 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))); @@ -501,11 +601,31 @@ static_assert(same(dimensionless(2) * width(3 * m), wi 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(height(2 * m) / (3 * m), + quantity_kind, one, int>(0))); +static_assert(same(height(2 * m) / (3. * m), + quantity_kind, one, double>(2 / 3.))); +static_assert(same(height(2. * m) / (3 * m), + quantity_kind, one, double>(2. / 3))); + +static_assert(same((2 * m) / height(3 * m), + quantity_kind, one, int>(0))); +static_assert(same((2 * m) / height(3. * m), + quantity_kind, one, double>(2 / 3.))); +static_assert(same((2. * m) / height(3 * m), + quantity_kind, one, double>(2. / 3))); + +static_assert(same(width(8 * m) / width(2 * m), + quantity_kind, one, int>(4))); +static_assert(same(width(8 * m) / width(2 * m), + quantity_kind, one, double>(4.0))); +static_assert(same(width(8 * m) / width(2 * m), + quantity_kind, one, double>(4.0))); + +static_assert(same(apples(8) / apples(2), apples(4))); +static_assert(same(apples(8) / (2 / apples(1)), apples(4))); + +static_assert(same(horizontal_area(8 * m * m) / width(2 * m), width(4 * m))); static_assert(same(width(2 * m) % 3, width(2 * m))); static_assert(same(width(3 * m) % width(2 * m), width(1 * m))); @@ -514,7 +634,6 @@ static_assert(is_same_v< decltype((width(0 * m) % width(0 * m)).common().count()), decltype(std::uint8_t(0) % std::uint8_t(0))>); -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>); diff --git a/test/unit_test/static/quantity_point_kind_test.cpp b/test/unit_test/static/quantity_point_kind_test.cpp index b5eba3d0..13e83165 100644 --- a/test/unit_test/static/quantity_point_kind_test.cpp +++ b/test/unit_test/static/quantity_point_kind_test.cpp @@ -336,7 +336,6 @@ static_assert(!constructible_or_convertible_from>(apples 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 diff --git a/test/unit_test/static/quantity_test.cpp b/test/unit_test/static/quantity_test.cpp index 9952dfa5..6d945ec8 100644 --- a/test/unit_test/static/quantity_test.cpp +++ b/test/unit_test/static/quantity_test.cpp @@ -278,9 +278,10 @@ static_assert((1_q_m += 1_q_m).count() == 2); static_assert((2_q_m -= 1_q_m).count() == 1); static_assert((1_q_m *= 2).count() == 2); static_assert((2_q_m /= 2).count() == 1); +static_assert((7_q_m %= 2).count() == 1); static_assert((1_q_m *= quantity(2)).count() == 2); static_assert((2_q_m /= quantity(2)).count() == 1); -static_assert((7_q_m %= 2).count() == 1); +static_assert((7_q_m %= quantity(2)).count() == 1); static_assert((7_q_m %= 2_q_m).count() == 1); // different types @@ -295,8 +296,10 @@ static_assert((7.5_q_m /= quantity(3)).count() == 2.5); static_assert((3500_q_m %= 1_q_km).count() == 500); static_assert((std::uint8_t(255) * m %= 256).count() == [] { std::uint8_t ui(255); return ui %= 256; }()); +static_assert((std::uint8_t(255) * m %= quantity(256)).count() == [] { std::uint8_t ui(255); return ui %= 256; }()); // static_assert((std::uint8_t(255) * m %= 256 * m).count() != [] { std::uint8_t ui(255); return ui %= 256; }()); // UB static_assert((std::uint8_t(255) * m %= 257).count() == [] { std::uint8_t ui(255); return ui %= 257; }()); +static_assert((std::uint8_t(255) * m %= quantity(257)).count() == [] { std::uint8_t ui(255); return ui %= 257; }()); // TODO: Fix static_assert((std::uint8_t(255) * m %= 257 * m).count() != [] { std::uint8_t ui(255); return ui %= 257; }()); @@ -309,14 +312,21 @@ static_assert((22_q_m *= quantity(33.33)).count() == 733); static_assert((22_q_m /= quantity(3.33)).count() == 6); #endif -template +template concept invalid_compound_assignments = requires() { // truncating not allowed requires !requires(length l) { l += 2.5_q_m; }; requires !requires(length l) { l -= 2.5_q_m; }; - requires !requires(length l) { l += length(2); }; - requires !requires(length l) { l -= length(2); }; - requires !requires(length l) { l %= length(2); }; + requires !requires(length l) { l += length(2); }; + requires !requires(length l) { l -= length(2); }; + requires !requires(length l) { l %= length(2); }; + requires !requires(length l) { l %= dimensionless(2); }; + requires !requires(length l) { l %= dimensionless(2); }; + + // TODO: accept non-truncating argument + requires !requires(length l) { l *= 1 * km / m; }; + requires !requires(length l) { l /= 1 * km / m; }; + requires !requires(length l) { l %= 1 * km / m; }; // only quantities can be added or subtracted requires !requires(length l) { l += 2; }; @@ -333,7 +343,7 @@ concept invalid_compound_assignments = requires() { requires !requires(length l) { l %= 2_q_m; }; requires !requires(length l) { l %= 2._q_m; }; }; -static_assert(invalid_compound_assignments); +static_assert(invalid_compound_assignments); ////////////////////