diff --git a/docs/users_guide/framework_basics/quantity_arithmetics.md b/docs/users_guide/framework_basics/quantity_arithmetics.md index f386e564..4eabebe2 100644 --- a/docs/users_guide/framework_basics/quantity_arithmetics.md +++ b/docs/users_guide/framework_basics/quantity_arithmetics.md @@ -93,8 +93,7 @@ static_assert(isq::radius(1 * m) - 0.5 * m == isq::radius(0.5 * m)); left-hand-side argument type: ```cpp - static_assert((1 * m += 1.5 * m) == 2 * m); - static_assert((1 * m += 1.5 * km) == 1501 * m); + static_assert((1 * m += 1 * km) == 1001 * m); static_assert((isq::length(1 * m) += isq::height(1 * m)) == isq::length(1 * m)); static_assert((isq::height(1.5 * m) -= 1 * m) == isq::height(0.5 * m)); ``` @@ -102,10 +101,18 @@ static_assert(isq::radius(1 * m) - 0.5 * m == isq::radius(0.5 * m)); If we will break typical library's convertibility rules, the following code will not compile: ```cpp - quantity q1 = 1 * km += 1 * m; // Compile-time error - quantity q2 = isq::height(1 * m) += isq::length(1 * m) // Compile-time error + quantity q1 = 1 * m -= 0.5 * m; // Compile-time error(1) + quantity q2 = 1 * km += 1 * m; // Compile-time error(2) + quantity q3 = isq::height(1 * m) += isq::length(1 * m); // Compile-time error(3) ``` + 1. Conversion of the floating-point to integral representation type is + [considered narrowing](value_conversions.md). + 2. Conversion of quantity with integral representation type from a unit of a higher resolution + to the one with a lower resolution is [considered narrowing](value_conversions.md). + 3. Conversion from a more generic quantity type to a more specific one is + [considered unsafe](simple_and_typed_quantities.md#quantity_cast-to-force-unsafe-conversions). + ## Multiplication and division @@ -118,12 +125,12 @@ static_assert(isq::height(3 * m) * 0.5 == isq::height(1.5 * m)); !!! note - Unless we use a compound assignment operator, in which case we will always result with the type - of the left-hand-side argument and the value consistent to the behavior of the underlying - representation types: - + Unless we use a compound assignment operator, in which case we always have to result with + the type of the left-hand-side argument. This, together with the fact that this library + tries to prevent truncation of a quantity value means, that the following does not compile: + ```cpp - static_assert((isq::height(3 * m) *= 0.5) == isq::height(1 * m)); + quantity q = isq::height(3 * m) *= 0.5; // Compile-time error ``` However, suppose we multiply or divide quantities of the same or different types or we divide a raw diff --git a/src/core/include/mp-units/framework/quantity.h b/src/core/include/mp-units/framework/quantity.h index 4c5b3cfe..533c2518 100644 --- a/src/core/include/mp-units/framework/quantity.h +++ b/src/core/include/mp-units/framework/quantity.h @@ -376,7 +376,7 @@ public: // compound assignment operators template Q, auto R2, typename Rep2> - requires detail::QuantityConvertibleTo, quantity> && requires(rep a, Rep2 b) { + requires detail::QuantityConvertibleTo, quantity> && requires(rep a, Rep2 b) { { a += b } -> std::same_as; } friend constexpr decltype(auto) operator+=(Q&& lhs, const quantity& rhs) @@ -389,7 +389,7 @@ public: } template Q, auto R2, typename Rep2> - requires detail::QuantityConvertibleTo, quantity> && requires(rep a, Rep2 b) { + requires detail::QuantityConvertibleTo, quantity> && requires(rep a, Rep2 b) { { a -= b } -> std::same_as; } friend constexpr decltype(auto) operator-=(Q&& lhs, const quantity& rhs) @@ -402,7 +402,7 @@ public: } template Q, auto R2, typename Rep2> - requires detail::QuantityConvertibleTo, quantity> && (!treat_as_floating_point) && + requires detail::QuantityConvertibleTo, quantity> && (!treat_as_floating_point) && requires(rep a, Rep2 b) { { a %= b } -> std::same_as; } @@ -417,7 +417,7 @@ public: return std::forward(lhs); } - template Q, typename Value> + template Q, detail::ValuePreservingTo Value> requires(!Quantity) && requires(rep a, Value b) { { a *= b } -> std::same_as; } @@ -430,19 +430,16 @@ public: } template Q1, QuantityOf Q2> - requires(Q2::unit == ::mp_units::one) && requires(rep a, Q2::rep b) { - { a *= b } -> std::same_as; - } + requires(Q2::unit == ::mp_units::one) && detail::ValuePreservingTo && + requires(rep a, Q2::rep b) { + { a *= b } -> std::same_as; + } friend constexpr decltype(auto) operator*=(Q1&& lhs, const Q2& rhs) { - // TODO use *= when compiler bug is resolved: - // https://developercommunity.visualstudio.com/t/Discrepancy-in-Behavior-of-operator-an/10732445 - lhs.numerical_value_is_an_implementation_detail_ = - lhs.numerical_value_is_an_implementation_detail_ * rhs.numerical_value_is_an_implementation_detail_; - return std::forward(lhs); + return std::forward(lhs) *= rhs.numerical_value_is_an_implementation_detail_; } - template Q, typename Value> + template Q, detail::ValuePreservingTo Value> requires(!Quantity) && requires(rep a, Value b) { { a /= b } -> std::same_as; } @@ -456,17 +453,13 @@ public: } template Q1, QuantityOf Q2> - requires(Q2::unit == ::mp_units::one) && requires(rep a, Q2::rep b) { - { a /= b } -> std::same_as; - } + requires(Q2::unit == ::mp_units::one) && detail::ValuePreservingTo && + requires(rep a, Q2::rep b) { + { a /= b } -> std::same_as; + } friend constexpr decltype(auto) operator/=(Q1&& lhs, const Q2& rhs) { - MP_UNITS_EXPECTS_DEBUG(rhs != rhs.zero()); - // TODO use /= when compiler bug is resolved: - // https://developercommunity.visualstudio.com/t/Discrepancy-in-Behavior-of-operator-an/10732445 - lhs.numerical_value_is_an_implementation_detail_ = - lhs.numerical_value_is_an_implementation_detail_ / rhs.numerical_value_is_an_implementation_detail_; - return std::forward(lhs); + return std::forward(lhs) /= rhs.numerical_value_is_an_implementation_detail_; } // binary operators on quantities diff --git a/test/static/CMakeLists.txt b/test/static/CMakeLists.txt index f1b225cd..78c06b5e 100644 --- a/test/static/CMakeLists.txt +++ b/test/static/CMakeLists.txt @@ -20,15 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -add_library(unit_tests_static_truncating quantity_test.cpp) -if(NOT ${projectPrefix}API_FREESTANDING) - target_sources(unit_tests_static_truncating PRIVATE chrono_test.cpp) -endif() -target_link_libraries(unit_tests_static_truncating PRIVATE mp-units::mp-units) -target_compile_options( - unit_tests_static_truncating PRIVATE $,/wd4242 /wd4244,-Wno-conversion> -) - add_library( unit_tests_static angular_test.cpp @@ -50,6 +41,7 @@ add_library( # magnitude_test.cpp natural_test.cpp prime_test.cpp + quantity_test.cpp quantity_point_test.cpp quantity_spec_test.cpp ratio_test.cpp @@ -64,9 +56,8 @@ add_library( ) if(NOT ${projectPrefix}API_FREESTANDING) - target_sources(unit_tests_static PRIVATE fractional_exponent_quantity.cpp math_test.cpp) + target_sources(unit_tests_static PRIVATE chrono_test.cpp fractional_exponent_quantity.cpp math_test.cpp) endif() target_compile_options(unit_tests_static PRIVATE $<$:-Wno-subobject-linkage>) target_link_libraries(unit_tests_static PRIVATE mp-units::mp-units) -target_link_libraries(unit_tests_static PRIVATE unit_tests_static_truncating) diff --git a/test/static/quantity_test.cpp b/test/static/quantity_test.cpp index 6512aa76..8210542a 100644 --- a/test/static/quantity_test.cpp +++ b/test/static/quantity_test.cpp @@ -465,8 +465,8 @@ static_assert((2.5 * m *= 3 * one).numerical_value_in(m) == 7.5); static_assert((7.5 * m /= 3 * one).numerical_value_in(m) == 2.5); // different units -static_assert((1 * m += 1.5 * km).numerical_value_in(m) == 1501); -static_assert((1000 * m -= 0.5 * km).numerical_value_in(m) == 500); +static_assert((1 * m += 1 * km).numerical_value_in(m) == 1001); +static_assert((2000 * m -= 1 * km).numerical_value_in(m) == 1000); static_assert((3500 * m %= 1 * km).numerical_value_in(m) == 500); // convertible quantity types @@ -474,18 +474,6 @@ static_assert((isq::length(1 * m) += isq::height(1 * m)).numerical_value_in(m) = static_assert((isq::length(2 * m) -= isq::height(1 * m)).numerical_value_in(m) == 1); static_assert((isq::length(7 * m) %= isq::height(2 * m)).numerical_value_in(m) == 1); -// different representation types with truncation -// clang-format off -static_assert((3 * m += 2.5 * m).numerical_value_in(m) == []{ auto v = 3; v += 2.5; return v; }()); -static_assert((3 * m -= 1.5 * m).numerical_value_in(m) == []{ auto v = 3; v -= 1.5; return v; }()); -static_assert((2 * m *= 2.5).numerical_value_in(m) == []{ auto v = 2; v *= 2.5; return v; }()); -static_assert((10 * m /= 2.5).numerical_value_in(m) == []{ auto v = 10; v /= 2.5; return v; }()); -static_assert((2 * m *= 2.5 * one).numerical_value_in(m) == []{ auto v = 2; v *= 2.5; return v; }()); -static_assert((10 * m /= 2.5 * one).numerical_value_in(m) == []{ auto v = 10; v /= 2.5; return v; }()); -// clang-format on - -static_assert((isq::height(3 * m) *= 0.5) == isq::height(1 * m)); - // static_assert((std::uint8_t{255} * m %= 256 * m).numerical_value_in(m) == [] { // std::uint8_t ui(255); // return ui %= 256; @@ -495,24 +483,20 @@ static_assert((std::uint8_t{255}* m %= 257 * m).numerical_value_in(m) == [] { return ui %= 257; }()); -// clang-17 with modules build on ignores disabling conversion warnings -#if !(defined MP_UNITS_COMP_CLANG && MP_UNITS_COMP_CLANG < 18 && defined MP_UNITS_MODULES) -// next two lines trigger conversions warnings -// (warning disabled in CMake for this file) -static_assert((22 * m *= 33.33).numerical_value_in(m) == 733); -static_assert((22 * m /= 3.33).numerical_value_in(m) == 6); -static_assert((22 * m *= 33.33 * one).numerical_value_in(m) == 733); -static_assert((22 * m /= 3.33 * one).numerical_value_in(m) == 6); -#endif - template typename Q> concept invalid_compound_assignments = requires() { // truncating not allowed + requires !requires(Q l) { l += 2.5 * m; }; + requires !requires(Q l) { l -= 2.5 * m; }; requires !requires(Q l) { l += 2 * isq::length[m]; }; requires !requires(Q l) { l -= 2 * isq::length[m]; }; requires !requires(Q l) { l %= 2 * isq::length[m]; }; requires !requires(Q l) { l %= 2 * percent; }; requires !requires(Q l) { l %= 2. * percent; }; + requires !requires(Q l) { l *= 2.5; }; + requires !requires(Q l) { l /= 2.5; }; + requires !requires(Q l) { l *= 2.5 * one; }; + requires !requires(Q l) { l /= 2.5 * one; }; // compound assignment with a non-convertible quantity not allowed requires !requires(Q l) { l += 2 * isq::length[m]; };