mirror of
https://github.com/mpusz/mp-units.git
synced 2025-08-01 03:14:29 +02:00
feat: from now on truncation is not allowed in compound assignment
This commit is contained in:
@@ -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
|
||||
|
@@ -376,7 +376,7 @@ public:
|
||||
|
||||
// compound assignment operators
|
||||
template<detail::Forwarding<quantity> Q, auto R2, typename Rep2>
|
||||
requires detail::QuantityConvertibleTo<quantity<R2, rep>, quantity> && requires(rep a, Rep2 b) {
|
||||
requires detail::QuantityConvertibleTo<quantity<R2, Rep2>, quantity> && requires(rep a, Rep2 b) {
|
||||
{ a += b } -> std::same_as<rep&>;
|
||||
}
|
||||
friend constexpr decltype(auto) operator+=(Q&& lhs, const quantity<R2, Rep2>& rhs)
|
||||
@@ -389,7 +389,7 @@ public:
|
||||
}
|
||||
|
||||
template<detail::Forwarding<quantity> Q, auto R2, typename Rep2>
|
||||
requires detail::QuantityConvertibleTo<quantity<R2, rep>, quantity> && requires(rep a, Rep2 b) {
|
||||
requires detail::QuantityConvertibleTo<quantity<R2, Rep2>, quantity> && requires(rep a, Rep2 b) {
|
||||
{ a -= b } -> std::same_as<rep&>;
|
||||
}
|
||||
friend constexpr decltype(auto) operator-=(Q&& lhs, const quantity<R2, Rep2>& rhs)
|
||||
@@ -402,7 +402,7 @@ public:
|
||||
}
|
||||
|
||||
template<detail::Forwarding<quantity> Q, auto R2, typename Rep2>
|
||||
requires detail::QuantityConvertibleTo<quantity<R2, rep>, quantity> && (!treat_as_floating_point<rep>) &&
|
||||
requires detail::QuantityConvertibleTo<quantity<R2, Rep2>, quantity> && (!treat_as_floating_point<rep>) &&
|
||||
requires(rep a, Rep2 b) {
|
||||
{ a %= b } -> std::same_as<rep&>;
|
||||
}
|
||||
@@ -417,7 +417,7 @@ public:
|
||||
return std::forward<Q>(lhs);
|
||||
}
|
||||
|
||||
template<detail::Forwarding<quantity> Q, typename Value>
|
||||
template<detail::Forwarding<quantity> Q, detail::ValuePreservingTo<Rep> Value>
|
||||
requires(!Quantity<Value>) && requires(rep a, Value b) {
|
||||
{ a *= b } -> std::same_as<rep&>;
|
||||
}
|
||||
@@ -430,19 +430,16 @@ public:
|
||||
}
|
||||
|
||||
template<detail::Forwarding<quantity> Q1, QuantityOf<dimensionless> Q2>
|
||||
requires(Q2::unit == ::mp_units::one) && requires(rep a, Q2::rep b) {
|
||||
{ a *= b } -> std::same_as<rep&>;
|
||||
}
|
||||
requires(Q2::unit == ::mp_units::one) && detail::ValuePreservingTo<typename Q2::rep, Rep> &&
|
||||
requires(rep a, Q2::rep b) {
|
||||
{ a *= b } -> std::same_as<rep&>;
|
||||
}
|
||||
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<Q1>(lhs);
|
||||
return std::forward<Q1>(lhs) *= rhs.numerical_value_is_an_implementation_detail_;
|
||||
}
|
||||
|
||||
template<detail::Forwarding<quantity> Q, typename Value>
|
||||
template<detail::Forwarding<quantity> Q, detail::ValuePreservingTo<Rep> Value>
|
||||
requires(!Quantity<Value>) && requires(rep a, Value b) {
|
||||
{ a /= b } -> std::same_as<rep&>;
|
||||
}
|
||||
@@ -456,17 +453,13 @@ public:
|
||||
}
|
||||
|
||||
template<detail::Forwarding<quantity> Q1, QuantityOf<dimensionless> Q2>
|
||||
requires(Q2::unit == ::mp_units::one) && requires(rep a, Q2::rep b) {
|
||||
{ a /= b } -> std::same_as<rep&>;
|
||||
}
|
||||
requires(Q2::unit == ::mp_units::one) && detail::ValuePreservingTo<typename Q2::rep, Rep> &&
|
||||
requires(rep a, Q2::rep b) {
|
||||
{ a /= b } -> std::same_as<rep&>;
|
||||
}
|
||||
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<Q1>(lhs);
|
||||
return std::forward<Q1>(lhs) /= rhs.numerical_value_is_an_implementation_detail_;
|
||||
}
|
||||
|
||||
// binary operators on quantities
|
||||
|
@@ -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 $<IF:$<CXX_COMPILER_ID:MSVC>,/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 $<$<CXX_COMPILER_ID:GNU>:-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)
|
||||
|
@@ -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<template<auto, typename> typename Q>
|
||||
concept invalid_compound_assignments = requires() {
|
||||
// truncating not allowed
|
||||
requires !requires(Q<isq::length[m], int> l) { l += 2.5 * m; };
|
||||
requires !requires(Q<isq::length[m], int> l) { l -= 2.5 * m; };
|
||||
requires !requires(Q<isq::length[km], int> l) { l += 2 * isq::length[m]; };
|
||||
requires !requires(Q<isq::length[km], int> l) { l -= 2 * isq::length[m]; };
|
||||
requires !requires(Q<isq::length[km], int> l) { l %= 2 * isq::length[m]; };
|
||||
requires !requires(Q<isq::length[km], int> l) { l %= 2 * percent; };
|
||||
requires !requires(Q<isq::length[km], int> l) { l %= 2. * percent; };
|
||||
requires !requires(Q<isq::length[m], int> l) { l *= 2.5; };
|
||||
requires !requires(Q<isq::length[m], int> l) { l /= 2.5; };
|
||||
requires !requires(Q<isq::length[m], int> l) { l *= 2.5 * one; };
|
||||
requires !requires(Q<isq::length[m], int> l) { l /= 2.5 * one; };
|
||||
|
||||
// compound assignment with a non-convertible quantity not allowed
|
||||
requires !requires(Q<isq::height[m], int> l) { l += 2 * isq::length[m]; };
|
||||
|
Reference in New Issue
Block a user