// 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 #include #include #include #include #include #include #include #ifdef MP_UNITS_IMPORT_STD import std; #else #include #include #include #include #if MP_UNITS_HOSTED #include #endif #endif namespace { using namespace mp_units; using namespace mp_units::utility; using namespace mp_units::detail; using namespace mp_units::utility::detail; // safe_int's own helpers (add_overflows, is_value_preserving_v, ...) // ============================================================================ // A test error policy (mirrors constrained_test.cpp for cross-wrapper tests) // ============================================================================ struct test_policy { static constexpr void on_constraint_violation(std::string_view) noexcept {} }; // ============================================================================ // Type traits and value_type // ============================================================================ static_assert(std::is_same_v::value_type, int>); static_assert(std::is_same_v::value_type, long>); static_assert(std::is_same_v::value_type, std::uint8_t>); static_assert(std::is_same_v>, int>); static_assert(std::is_same_v>, long>); static_assert(std::is_same_v>, unsigned>); // ============================================================================ // is_value_preserving_int_v — range-based check using numeric_limits // ============================================================================ // Same type: always value-preserving static_assert(is_value_preserving_int_v); static_assert(is_value_preserving_int_v); static_assert(is_value_preserving_int_v); // Widening same signedness static_assert(is_value_preserving_int_v); static_assert(is_value_preserving_int_v); static_assert(is_value_preserving_int_v); static_assert(is_value_preserving_int_v); // Narrowing: not value-preserving static_assert(!is_value_preserving_int_v); static_assert(!is_value_preserving_int_v); static_assert(!is_value_preserving_int_v); static_assert(!is_value_preserving_int_v); // Signed → unsigned same size: not value-preserving (negative values don't fit) static_assert(!is_value_preserving_int_v); static_assert(!is_value_preserving_int_v); static_assert(!is_value_preserving_int_v); // Unsigned → signed same size: not value-preserving (large unsigned doesn't fit) static_assert(!is_value_preserving_int_v); static_assert(!is_value_preserving_int_v); // Unsigned → signed wider: value-preserving (uint8_t 0..255 fits in int) static_assert(is_value_preserving_int_v); static_assert(is_value_preserving_int_v); // Signed → unsigned wider: NOT value-preserving (negative values never fit unsigned) static_assert(!is_value_preserving_int_v); static_assert(!is_value_preserving_int_v); // Cross-wrapper: safe_int → raw U (safe_int has numeric_limits with same range as T) static_assert(is_value_preserving_int_v, int>); static_assert(!is_value_preserving_int_v, short>); static_assert(!is_value_preserving_int_v, unsigned>); // Cross-wrapper: raw T → safe_int static_assert(is_value_preserving_int_v>); static_assert(!is_value_preserving_int_v>); static_assert(is_value_preserving_int_v>); // Cross-wrapper: safe_int → safe_int static_assert(is_value_preserving_int_v, safe_int>); static_assert(!is_value_preserving_int_v, safe_int>); static_assert(!is_value_preserving_int_v, safe_int>); static_assert(!is_value_preserving_int_v, safe_int>); static_assert(is_value_preserving_int_v, safe_int>); // Cross-wrapper: constrained → raw U and vice versa static_assert(is_value_preserving_int_v, int>); static_assert(!is_value_preserving_int_v, short>); static_assert(is_value_preserving_int_v>); // Cross-wrapper: safe_int → constrained and vice versa static_assert(is_value_preserving_int_v, constrained>); static_assert(!is_value_preserving_int_v, constrained>); static_assert(is_value_preserving_int_v, safe_int>); static_assert(!is_value_preserving_int_v, safe_int>); // Types without numeric_limits: always false #if MP_UNITS_HOSTED static_assert(!is_value_preserving_int_v); static_assert(!is_value_preserving_int_v); #endif // ============================================================================ // is_value_preserving_v — generalized: delegates to is_value_preserving_int_v // when both have numeric_limits, otherwise falls back to is_convertible_v // ============================================================================ // Integral pairs: delegates to is_value_preserving_int_v static_assert(is_value_preserving_v); static_assert(!is_value_preserving_v); static_assert(!is_value_preserving_v); static_assert(is_value_preserving_v); // Wrapper pairs with numeric_limits: delegates to is_value_preserving_int_v static_assert(is_value_preserving_v, safe_int>); static_assert(!is_value_preserving_v, safe_int>); // Non-numeric_limits types: falls back to is_convertible_v #if MP_UNITS_HOSTED static_assert(!is_value_preserving_v); // not convertible static_assert(!is_value_preserving_v); // not convertible #endif // ============================================================================ // treat_as_floating_point: must be false // ============================================================================ static_assert(!treat_as_floating_point>); static_assert(!treat_as_floating_point>); static_assert(!treat_as_floating_point>); // ============================================================================ // Representation concepts // ============================================================================ // UsesIntegerScaling: value_type is std::integral and the type supports * / with integers. // The scaling engine uses the type's own operators so that safe_int's overflow checks and // widening promotions are preserved throughout unit conversion. static_assert(std::integral>>); static_assert(UsesIntegerScaling>); static_assert(UsesIntegerScaling>); static_assert(UsesIntegerScaling>); // WeaklyRegular static_assert(std::copyable>); static_assert(std::equality_comparable>); // BaseScalar static_assert(BaseScalar>); static_assert(BaseScalar>); // totally_ordered static_assert(std::totally_ordered>); static_assert(std::totally_ordered>); // RealScalar static_assert(RealScalar>); static_assert(RealScalar>); static_assert(RealScalar>); // UnitMagnitudeScalable static_assert(UnitMagnitudeScalable>); static_assert(UnitMagnitudeScalable>); // ScalarRepresentation (real-field coverage is the RealScalar checks above) static_assert(ScalarRepresentation>); static_assert(ScalarRepresentation>); static_assert(ScalarRepresentation>); // floating-point types must NOT be accepted static_assert(!requires { requires(!treat_as_floating_point) && mp_units::detail::RealScalar; }); static_assert(!requires { requires(!treat_as_floating_point) && mp_units::detail::RealScalar; }); // ============================================================================ // std::numeric_limits // ============================================================================ static_assert(std::numeric_limits>::is_specialized); static_assert(std::numeric_limits>::is_specialized); static_assert(std::numeric_limits>::min() == safe_int{std::numeric_limits::min()}); static_assert(std::numeric_limits>::max() == safe_int{std::numeric_limits::max()}); static_assert(std::numeric_limits>::lowest() == safe_int{std::numeric_limits::lowest()}); static_assert(std::numeric_limits>::min() == safe_int{0u}); // ============================================================================ // representation_values // ============================================================================ static_assert(std::constructible_from, int>); static_assert(representation_values>::zero() == safe_int{0}); static_assert(representation_values>::one() == safe_int{1}); static_assert(representation_values>::min() == safe_int{std::numeric_limits::min()}); static_assert(representation_values>::max() == safe_int{std::numeric_limits::max()}); // ============================================================================ // Construction and conversion // ============================================================================ static_assert(safe_int{42}.value() == 42); static_assert(static_cast(safe_int{-7}) == -7); static_assert(safe_int{0} == safe_int{0}); static_assert(safe_int{1} != safe_int{2}); // operator T() is explicit: safe_int does not silently decay to the raw integer type. // Use .value() or static_cast() to extract the underlying value intentionally. static_assert(!std::is_convertible_v, int>); static_assert(std::is_constructible_v>); // explicit cast still works // ============================================================================ // Converting constructors — raw integer types // ============================================================================ // Widening: implicit (short → int, is_convertible_v is true) static_assert(std::is_convertible_v>); static_assert(std::is_constructible_v, short>); static_assert(safe_int{short{42}}.value() == 42); // Narrowing: explicit (int range doesn't fit in short) static_assert(!std::is_convertible_v>); static_assert(std::is_constructible_v, int>); static_assert(safe_int{int{100}}.value() == 100); // Signed↔unsigned same size: explicit (ranges don't fully overlap) static_assert(!std::is_convertible_v>); static_assert(std::is_constructible_v, unsigned>); static_assert(safe_int{42u}.value() == 42); static_assert(!std::is_convertible_v>); static_assert(std::is_constructible_v, int>); static_assert(safe_int{42}.value() == 42u); // Widening unsigned→unsigned: implicit static_assert(std::is_convertible_v>); static_assert(safe_int{std::uint8_t{200}}.value() == 200u); // Widening signed→signed: implicit static_assert(std::is_convertible_v>); static_assert(safe_int{std::int8_t{42}}.value() == 42); // ============================================================================ // Converting constructors — safe_int → safe_int // (uses is_value_preserving_int_v for explicit/implicit decision) // ============================================================================ // Widening: implicit (safe_int → safe_int) static_assert(std::is_convertible_v, safe_int>); static_assert(std::is_constructible_v, safe_int>); static_assert(safe_int{safe_int{42}}.value() == 42); // Narrowing: explicit (safe_int → safe_int) static_assert(!std::is_convertible_v, safe_int>); static_assert(std::is_constructible_v, safe_int>); static_assert(safe_int{safe_int{100}}.value() == 100); // Signed→unsigned: explicit (negative values don't fit) static_assert(!std::is_convertible_v, safe_int>); static_assert(std::is_constructible_v, safe_int>); static_assert(safe_int{safe_int{42}}.value() == 42u); // Unsigned→signed same size: explicit (large unsigned doesn't fit) static_assert(!std::is_convertible_v, safe_int>); static_assert(std::is_constructible_v, safe_int>); static_assert(safe_int{safe_int{42u}}.value() == 42); // Widening unsigned→unsigned: implicit static_assert(std::is_convertible_v, safe_int>); static_assert(safe_int{safe_int{200}}.value() == 200u); // ============================================================================ // CTAD (class template argument deduction) // ============================================================================ static_assert(std::is_same_v>); static_assert(std::is_same_v>); static_assert(std::is_same_v>); static_assert(std::is_same_v>); static_assert(std::is_same_v>); static_assert(std::is_same_v>); static_assert(safe_int{100}.value() == 100); static_assert(safe_int{short{42}}.value() == 42); // ============================================================================ // common_type deduction // // Because operator T() is explicit, the only implicit conversion paths are the // value-preserving constructors (widening). The ternary used by std::common_type // therefore has at most one viable implicit direction, so ternary resolution is // unambiguous and the wrapper is never silently dropped. // // Helper: uses a dependent requires-expression so that "no viable conversion" // (neither direction is implicit) becomes a clean SFINAE failure rather than a // hard error — the dependent T/U defer the check to substitution time. // ============================================================================ template inline constexpr bool has_common_type = requires(T a, U b) { false ? a : b; }; // Widening between same-sign wrappers: one direction is implicit → resolves correctly. static_assert( std::is_same_v, safe_int>, safe_int::error_policy>>); static_assert(std::is_same_v, safe_int>, safe_int::error_policy>>); static_assert(std::is_same_v, safe_int>, safe_int::error_policy>>); // safe_int vs raw int: with explicit operator T(), only int→safe_int is // implicit (value-preserving ctor). The wrapper is preserved, not dropped. static_assert(std::is_same_v, int>, safe_int>); static_assert(std::is_same_v>, safe_int>); // safe_int vs raw int: int→safe_int is explicit (narrowing), and // safe_int→int requires explicit operator T(). Neither direction is implicit // → no common_type. This prevents silently mixing safe and unsafe integer types. static_assert(!has_common_type, int>); static_assert(!has_common_type>); // Widening unsigned→signed: uint8_t fits in int, so converting ctor is implicit. static_assert(std::is_same_v, safe_int>, safe_int::error_policy>>); // Signed↔unsigned same size: both constructors are explicit (ranges don't overlap) // and operator T() is explicit → no common_type. Mixed-signedness arithmetic // must be resolved explicitly by the user. static_assert(!has_common_type, safe_int>); static_assert(!has_common_type, safe_int>); // Cross-wrapper: safe_int has an implicit constructor from constrained // (value-preserving), while constrained only constructs from T directly (would // need two UDCs via safe_int's operator T() — not allowed). So only one direction // is implicit → common_type resolves to safe_int. static_assert(std::is_same_v, constrained>, safe_int>); static_assert(std::is_same_v, safe_int>, safe_int>); // Cross-wrapper with widening: constrained → safe_int is implicit (range fits). static_assert(std::is_same_v, safe_int>, safe_int>); // Cross-wrapper where safe_int→constrained is not implicit (would need // operator T() to go short→int then int→constrained: two UDCs), and the // reverse is explicit (narrowing) → no common_type. static_assert(!has_common_type, constrained>); // ============================================================================ // Comparison operators // ============================================================================ static_assert(safe_int{1} < safe_int{2}); static_assert(safe_int{2} > safe_int{1}); static_assert(safe_int{1} <= safe_int{1}); static_assert(safe_int{1} >= safe_int{1}); static_assert((safe_int{1} <=> safe_int{2}) < 0); // ============================================================================ // Unary operators // ============================================================================ static_assert(+safe_int{5} == safe_int{5}); static_assert(-safe_int{5} == safe_int{-5}); static_assert(-safe_int{-3} == safe_int{3}); // Unary +/- model integral promotion: sub-int types promote to int static_assert(std::is_same_v{}), safe_int::error_policy>>); static_assert(std::is_same_v{}), safe_int::error_policy>>); static_assert( std::is_same_v{}), safe_int::error_policy>>); static_assert( std::is_same_v{}), safe_int::error_policy>>); // int and wider types are unaffected by promotion static_assert(std::is_same_v{}), safe_int::error_policy>>); static_assert(std::is_same_v{}), safe_int::error_policy>>); static_assert(std::is_same_v{}), safe_int::error_policy>>); static_assert(std::is_same_v{}), safe_int::error_policy>>); // safe_int: -INT16_MIN promotes to int first, so int(32768) is in range — no overflow static_assert(-safe_int{std::numeric_limits::min()} == safe_int{32768}); // ============================================================================ // Arithmetic (non-overflow paths) // ============================================================================ static_assert(safe_int{3} + safe_int{4} == safe_int{7}); static_assert(safe_int{10} - safe_int{3} == safe_int{7}); static_assert(safe_int{3} * safe_int{4} == safe_int{12}); static_assert(safe_int{12} / safe_int{4} == safe_int{3}); static_assert(safe_int{10} % safe_int{3} == safe_int{1}); // unsigned static_assert(safe_int{10u} + safe_int{5u} == safe_int{15u}); static_assert(safe_int{10u} - safe_int{5u} == safe_int{5u}); static_assert(safe_int{3u} * safe_int{4u} == safe_int{12u}); static_assert(safe_int{12u} / safe_int{4u} == safe_int{3u}); // ============================================================================ // Increment / decrement // ============================================================================ static_assert([] { safe_int x{5}; ++x; return x == safe_int{6}; }()); static_assert([] { safe_int x{5}; x++; return x == safe_int{6}; }()); static_assert([] { safe_int x{5}; --x; return x == safe_int{4}; }()); static_assert([] { safe_int x{5}; x--; return x == safe_int{4}; }()); // post-increment / post-decrement return the old value static_assert([] { safe_int x{5}; auto old = x++; return old == safe_int{5} && x == safe_int{6}; }()); static_assert([] { safe_int x{5}; auto old = x--; return old == safe_int{5} && x == safe_int{4}; }()); // ============================================================================ // Compound assignment // ============================================================================ static_assert([] { safe_int x{10}; x += safe_int{3}; return x == safe_int{13}; }()); static_assert([] { safe_int x{10}; x -= safe_int{3}; return x == safe_int{7}; }()); static_assert([] { safe_int x{10}; x *= safe_int{3}; return x == safe_int{30}; }()); static_assert([] { safe_int x{10}; x /= safe_int{2}; return x == safe_int{5}; }()); static_assert([] { safe_int x{10}; x %= safe_int{3}; return x == safe_int{1}; }()); // ============================================================================ // Overflow detection helpers // ============================================================================ // add_overflows static_assert(!utility::detail::add_overflows(1, 2)); static_assert(!utility::detail::add_overflows(std::numeric_limits::max() - 1, 1)); static_assert(utility::detail::add_overflows(std::numeric_limits::max(), 1)); static_assert(utility::detail::add_overflows(std::numeric_limits::min(), -1)); static_assert(!utility::detail::add_overflows(0u, 0u)); static_assert(utility::detail::add_overflows(std::numeric_limits::max(), 1u)); // sub_overflows static_assert(!utility::detail::sub_overflows(5, 3)); static_assert(utility::detail::sub_overflows(std::numeric_limits::min(), 1)); static_assert(utility::detail::sub_overflows(std::numeric_limits::max(), -1)); static_assert(!utility::detail::sub_overflows(5u, 3u)); static_assert(utility::detail::sub_overflows(0u, 1u)); // mul_overflows static_assert(!utility::detail::mul_overflows(2, 3)); static_assert(!utility::detail::mul_overflows(std::numeric_limits::max(), 1)); static_assert(utility::detail::mul_overflows(std::numeric_limits::max(), 2)); static_assert(utility::detail::mul_overflows(std::numeric_limits::min(), -1)); static_assert(!utility::detail::mul_overflows(0u, std::numeric_limits::max())); static_assert(utility::detail::mul_overflows(std::numeric_limits::max(), 2u)); // div_overflows static_assert(!utility::detail::div_overflows(10, 2)); static_assert(utility::detail::div_overflows(10, 0)); static_assert(utility::detail::div_overflows(std::numeric_limits::min(), -1)); static_assert(!utility::detail::div_overflows(std::numeric_limits::min(), 2)); static_assert(utility::detail::div_overflows(5u, 0u)); // neg_overflows static_assert(!utility::detail::neg_overflows(5)); static_assert(!utility::detail::neg_overflows(-5)); static_assert(!utility::detail::neg_overflows(0)); static_assert(utility::detail::neg_overflows(std::numeric_limits::min())); static_assert(!utility::detail::neg_overflows(0u)); static_assert(utility::detail::neg_overflows(1u)); static_assert(utility::detail::neg_overflows(std::numeric_limits::max())); // ============================================================================ // Error policy types exist and have on_overflow // ============================================================================ static_assert(requires { safe_int_terminate_policy::on_overflow(""); }); #if MP_UNITS_HOSTED static_assert(requires { safe_int_throw_policy::on_overflow(""); }); #endif // ============================================================================ // safe_int default policy selection // ============================================================================ #if MP_UNITS_HOSTED static_assert(std::is_same_v, safe_int>); #else static_assert(std::is_same_v, safe_int>); #endif // ============================================================================ // Mixed-type arithmetic — same value_type (overflow-checked, keeps safe_int) // ============================================================================ // safe_int * T / T → safe_int static_assert(std::is_same_v{} * 2), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{} / 2), safe_int>); static_assert(safe_int{6} * 2 == safe_int{12}); static_assert(2 * safe_int{6} == safe_int{12}); static_assert(safe_int{12} / 4 == safe_int{3}); // ============================================================================ // Scalar addition, subtraction, modulo — integral (overflow-checked, keeps safe_int) // ============================================================================ // same-type: result stays safe_int static_assert(std::is_same_v{} + 2), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{} - 2), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{} % 3), safe_int>); static_assert(std::is_same_v{}), safe_int>); // cross-type: int op long long → long long static_assert(std::is_same_v{} + 1LL), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{} - 1LL), safe_int>); static_assert(std::is_same_v{}), safe_int>); // values static_assert(safe_int{3} + 4 == safe_int{7}); static_assert(4 + safe_int{3} == safe_int{7}); static_assert(safe_int{10} - 3 == safe_int{7}); static_assert(10 - safe_int{3} == safe_int{7}); static_assert(safe_int{10} % 3 == safe_int{1}); static_assert(10 % safe_int{3} == safe_int{1}); // ============================================================================ // Cross-type integral (keeps wrapper, promotes to safe_int, overflow-checked) // R = decltype(T * U), e.g. int * long long → long long // ============================================================================ static_assert(std::is_same_v{} * 1LL), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{} / 1LL), safe_int>); static_assert(safe_int{6} * 2LL == safe_int{12}); static_assert(2LL * safe_int{6} == safe_int{12}); static_assert(safe_int{12} / 4LL == safe_int{3}); // ============================================================================ // Cross-type integral — additional convertibility coverage // // Exercises the `safe_int op integral_U` overloads for combinations not covered above: // - narrower raw RHS (promotes to T's rank, stays safe_int) // - `long` (whose width differs across platforms — 32-bit on Windows, 64-bit on POSIX), // verifying that the safe_int op integral overload is chosen over implicit conversion // of the raw integer to safe_int via the converting ctor. // ============================================================================ // Narrowing raw RHS: result keeps the wider wrapper type. // short → int by C++ promotion rules, so safe_int + short → safe_int. static_assert(std::is_same_v{} + short{1}), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(safe_int{3} + short{4} == safe_int{7}); static_assert(short{4} + safe_int{3} == safe_int{7}); // safe_int + int → safe_int (raw RHS has higher rank). static_assert(std::is_same_v{} + 1), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(safe_int{3} + 4 == safe_int{7}); // safe_int + 1L: result is safe_int. On Windows where `long` and `int` have // equal width this would otherwise be tempted by the converting ctor (raw long → safe_int), // which would yield safe_int; the safe_int op integral overload wins by exact match. static_assert(std::is_same_v{} + 1L), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(safe_int{3} + 4L == safe_int{7L}); static_assert(4L + safe_int{3} == safe_int{7L}); // Unsigned widening: safe_int + uint8_t → safe_int. static_assert(std::is_same_v{} + std::uint8_t{1}), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(safe_int{3u} + std::uint8_t{4} == safe_int{7u}); // int × unsigned scalar arithmetic is intentionally ill-formed — same rationale as // safe_int + safe_int: sign-mismatch conversions produce counterintuitive // results (e.g. safe_int{-1} * 2u → UINT_MAX-1 via reinterpretation). // Comparisons remain allowed because they use std::cmp_* which is correct for mixed-sign. static_assert(!std::is_invocable_v, safe_int, unsigned>); static_assert(!std::is_invocable_v, safe_int, unsigned>); static_assert(!std::is_invocable_v, safe_int, unsigned>); static_assert(!std::is_invocable_v, safe_int, unsigned>); static_assert(!std::is_invocable_v, safe_int, unsigned>); static_assert(!std::is_invocable_v, unsigned, safe_int>); static_assert(!std::is_invocable_v, unsigned, safe_int>); static_assert(!std::is_invocable_v, unsigned, safe_int>); static_assert(!std::is_invocable_v, unsigned, safe_int>); static_assert(!std::is_invocable_v, unsigned, safe_int>); // U / safe_int — symmetric with other scalar ops static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(12 / safe_int{4} == safe_int{3}); static_assert(10LL / safe_int{3} == safe_int{3}); // ============================================================================ // 128-bit integer scalar (mp_units::detail::integral extension for __int128 / unsigned __int128) // // On GCC std::integral<__int128> = false, so these operators previously fell // through to the (now-removed) non-integral fallback and returned the raw type, // not safe_int. After the mp_units::detail::integral fix they are handled by the typed // integral operators and return safe_int with overflow checking. // ============================================================================ #if defined(__SIZEOF_INT128__) // mp_units::detail::integral must recognise both 128-bit types static_assert(mp_units::detail::integral); static_assert(mp_units::detail::integral); // mp_units::detail::is_signed_v must reflect the true signedness even on GCC strict mode static_assert(mp_units::detail::is_signed_v); static_assert(!mp_units::detail::is_signed_v); // same_sign_v must work for mixed width/128-bit pairings static_assert(utility::detail::same_sign_v); static_assert(utility::detail::same_sign_v); static_assert(utility::detail::same_sign_v); static_assert(utility::detail::same_sign_v); static_assert(!utility::detail::same_sign_v); static_assert(!utility::detail::same_sign_v); // safe_int × int128_t → safe_int (wrapper preserved) static_assert(std::is_same_v{} * int128_t{1}), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{} / int128_t{1}), safe_int>); static_assert(std::is_same_v{}), safe_int>); // safe_int × uint128_t → safe_int (wrapper preserved) static_assert(std::is_same_v{} * uint128_t{1}), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{} / uint128_t{1}), safe_int>); static_assert(std::is_same_v{}), safe_int>); // sign mismatch with 128-bit is ill-formed (same policy as other mixed-sign operations) static_assert(!std::is_invocable_v, safe_int, uint128_t>); static_assert(!std::is_invocable_v, safe_int, int128_t>); static_assert(!std::is_invocable_v, safe_int, uint128_t>); static_assert(!std::is_invocable_v, safe_int, int128_t>); // values static_assert(safe_int{6} * int128_t{2} == safe_int{12}); static_assert(int128_t{2} * safe_int{6} == safe_int{12}); static_assert(safe_int{12} / int128_t{4} == safe_int{3}); static_assert(int128_t{12} / safe_int{4} == safe_int{3}); static_assert(safe_int{6u} * uint128_t{2} == safe_int{12u}); static_assert(uint128_t{2} * safe_int{6u} == safe_int{12u}); static_assert(safe_int{12u} / uint128_t{4} == safe_int{3u}); static_assert(uint128_t{12} / safe_int{4u} == safe_int{3u}); #endif // ============================================================================ // Cross-type floating-point (drops wrapper, returns bare decltype(T * U)) // ============================================================================ // safe_int + double → double (bare, no overflow wrapper) static_assert(std::is_same_v{} + 1.0), double>); static_assert(std::is_same_v{}), double>); static_assert(std::is_same_v{} - 1.0), double>); static_assert(std::is_same_v{}), double>); // safe_int + float → float static_assert(std::is_same_v{} + 1.0f), float>); static_assert(std::is_same_v{}), float>); static_assert(std::is_same_v{} - 1.0f), float>); static_assert(std::is_same_v{}), float>); // safe_int * double → double (bare, no overflow wrapper) static_assert(std::is_same_v{} * 1.0), double>); static_assert(std::is_same_v{}), double>); static_assert(std::is_same_v{} / 1.0), double>); // safe_int * float → float static_assert(std::is_same_v{} * 1.0f), float>); static_assert(std::is_same_v{}), float>); static_assert(std::is_same_v{} / 1.0f), float>); // safe_int * double → double static_assert(std::is_same_v{} * 1.0), double>); static_assert(std::is_same_v{} / 1.0), double>); // FP / safe_int — symmetric with safe_int / FP static_assert(std::is_same_v{}), double>); static_assert(std::is_same_v{}), float>); // values static_assert(safe_int{3} + 1.5 == 4.5); static_assert(1.5 + safe_int{3} == 4.5); static_assert(safe_int{3} - 1.5 == 1.5); static_assert(5.0 - safe_int{3} == 2.0); static_assert(safe_int{3} * 2.5 == 7.5); static_assert(2.5 * safe_int{3} == 7.5); static_assert(safe_int{10} / 4.0 == 2.5); static_assert(10.0 / safe_int{4} == 2.5); static_assert(10.0f / safe_int{4} == 2.5f); // ============================================================================ // Heterogeneous wrapper×wrapper arithmetic (safe_int op safe_int, T≠U) // Result type is safe_int — same EP, promoted value_type. // ============================================================================ // int op long → long (int widens to long on 64-bit platforms) static_assert(std::is_same_v{} + safe_int{}), safe_int>); static_assert(std::is_same_v{} - safe_int{}), safe_int>); static_assert(std::is_same_v{} * safe_int{}), safe_int>); static_assert(std::is_same_v{} / safe_int{}), safe_int>); static_assert(std::is_same_v{} % safe_int{}), safe_int>); // short op int → int (short promotes to int) static_assert(std::is_same_v{} + safe_int{}), safe_int>); static_assert(std::is_same_v{} + safe_int{}), safe_int>); // Value correctness (non-overflow paths) static_assert(safe_int{3} + safe_int{4L} == safe_int{7L}); static_assert(safe_int{10} - safe_int{4L} == safe_int{6L}); static_assert(safe_int{3} * safe_int{4L} == safe_int{12L}); static_assert(safe_int{12} / safe_int{4L} == safe_int{3L}); static_assert(safe_int{10} % safe_int{3L} == safe_int{1L}); // Widening prevents overflow: INT_MAX + 1 fits in long long on every platform. // (`long long` rather than `long`: on Windows `long` is 32-bit and equal to `int`, so the // RHS `static_cast(INT_MAX) + 1L` would itself be 32-bit signed overflow — UB at // constexpr — and the assertion would not be portable.) static_assert(safe_int{std::numeric_limits::max()} + safe_int{1LL} == safe_int{static_cast(std::numeric_limits::max()) + 1LL}); // Heterogeneous comparison (operator== and operator<=>) static_assert(safe_int{3} == safe_int{3L}); static_assert(safe_int{2} != safe_int{3L}); static_assert((safe_int{2} <=> safe_int{3L}) < 0); static_assert((safe_int{3} <=> safe_int{3L}) == 0); static_assert((safe_int{4} <=> safe_int{3L}) > 0); // Scalar comparison — integral static_assert(safe_int{3} == 3); static_assert(3 == safe_int{3}); static_assert(safe_int{2} != 3); static_assert(2 != safe_int{3}); static_assert(safe_int{2} < 3); static_assert(2 < safe_int{3}); static_assert((safe_int{2} <=> 3) < 0); static_assert((2 <=> safe_int{3}) < 0); static_assert((safe_int{3} <=> 3) == 0); static_assert((safe_int{4} <=> 3) > 0); // Scalar comparison — floating-point static_assert(safe_int{3} == 3.0); static_assert(3.0 == safe_int{3}); static_assert(safe_int{2} < 2.5); static_assert(2.5 > safe_int{2}); static_assert((safe_int{2} <=> 2.5) < 0); // Mixed-signedness safe_int × safe_int comparisons use std::cmp_* — correct and allowed. static_assert(safe_int{-1} < safe_int{0u}); static_assert(!(safe_int{-1} == safe_int{0u})); static_assert(safe_int{-1} != safe_int{0u}); static_assert((safe_int{-1} <=> safe_int{0u}) < 0); static_assert(safe_int{0u} > safe_int{-1}); static_assert((safe_int{0u} <=> safe_int{-1}) > 0); static_assert(safe_int{42} == safe_int{42u}); static_assert((safe_int{42} <=> safe_int{42u}) == 0); // Different EP is also fine for cross-sign comparisons: static_assert(safe_int{-1} < safe_int{0u}); // Same-sign, different EP comparisons are also allowed — bool result needs no policy: static_assert(safe_int{1} == safe_int{1}); static_assert(safe_int{1} < safe_int{2}); static_assert((safe_int{1} <=> safe_int{2}) < 0); static_assert(safe_int{-1} < safe_int{0}); // Mixed signedness scalar comparisons static_assert(safe_int{-1} < 0u); static_assert(safe_int{0u} > -1); static_assert((safe_int{-1} <=> 0u) < 0); static_assert(safe_int{42} == 42u); // Cross-wrapper comparison — safe_int vs constrained static_assert(safe_int{3} == constrained{3}); static_assert(constrained{3} == safe_int{3}); static_assert(safe_int{2} < constrained{3}); static_assert(constrained{2} < safe_int{3}); static_assert((safe_int{2} <=> constrained{3}) < 0); static_assert((constrained{2} <=> safe_int{3}) < 0); static_assert((safe_int{3} <=> constrained{3L}) == 0); // ============================================================================ // Cross-wrapper arithmetic: constrained op safe_int and safe_int op constrained // // Rules (regardless of operand order): // - Integral result → safe_int wins (overflow is the tighter guarantee) // - Non-integral result (FP) → constrained wins (preserves constraint) // ============================================================================ using EP = safe_int::error_policy; // safe_int_throw_policy on hosted // --- operator+, operator- --- static_assert(std::is_same_v{} + safe_int{}), safe_int>); static_assert(std::is_same_v{} + constrained{}), safe_int>); static_assert(std::is_same_v{} - safe_int{}), safe_int>); static_assert(std::is_same_v{} - constrained{}), safe_int>); // Non-integral result: constrained wins static_assert( std::is_same_v{} + safe_int{}), constrained>); static_assert( std::is_same_v{} + constrained{}), constrained>); static_assert( std::is_same_v{} - safe_int{}), constrained>); static_assert( std::is_same_v{} - constrained{}), constrained>); // --- operator* --- // Integral result: safe_int wins, same type both orderings static_assert(std::is_same_v{} * safe_int{}), safe_int>); static_assert(std::is_same_v{} * constrained{}), safe_int>); // Cross-type integral: result type follows decltype of underlying op static_assert( std::is_same_v{} * safe_int{}), safe_int>); static_assert( std::is_same_v{} * constrained{}), safe_int>); // Non-integral result: constrained wins, same type both orderings static_assert( std::is_same_v{} * safe_int{}), constrained>); static_assert( std::is_same_v{} * constrained{}), constrained>); // --- operator/ --- static_assert(std::is_same_v{} / safe_int{}), safe_int>); static_assert(std::is_same_v{} / constrained{}), safe_int>); static_assert( std::is_same_v{} / safe_int{}), safe_int>); static_assert( std::is_same_v{} / constrained{}), safe_int>); static_assert( std::is_same_v{} / safe_int{}), constrained>); static_assert( std::is_same_v{} / constrained{}), constrained>); // --- operator% --- static_assert(std::is_same_v{} % safe_int{}), safe_int>); static_assert(std::is_same_v{} % constrained{}), safe_int>); // Value tests for all operators (both orderings) static_assert(constrained{3} + safe_int{4} == safe_int{7}); static_assert(safe_int{3} + constrained{4} == safe_int{7}); static_assert(constrained{10} - safe_int{3} == safe_int{7}); static_assert(safe_int{10} - constrained{3} == safe_int{7}); static_assert(constrained{3} * safe_int{4} == safe_int{12}); static_assert(safe_int{3} * constrained{4} == safe_int{12}); static_assert(constrained{12} / safe_int{4} == safe_int{3}); static_assert(safe_int{12} / constrained{4} == safe_int{3}); static_assert(constrained{10} % safe_int{3} == safe_int{1}); static_assert(safe_int{10} % constrained{3} == safe_int{1}); // Non-integral cross-wrapper value tests (constrained wins) static_assert(constrained{3.0} + safe_int{4} == constrained{7.0}); static_assert(safe_int{3} + constrained{4.0} == constrained{7.0}); static_assert(constrained{10.0} - safe_int{3} == constrained{7.0}); static_assert(safe_int{10} - constrained{3.0} == constrained{7.0}); static_assert(constrained{3.0} * safe_int{4} == constrained{12.0}); static_assert(safe_int{3} * constrained{4.0} == constrained{12.0}); static_assert(constrained{12.0} / safe_int{4} == constrained{3.0}); static_assert(safe_int{12} / constrained{4.0} == constrained{3.0}); // ============================================================================ // Integer promotion rules // safe_int uses decltype of the underlying operation, so homogeneous short×short // must promote to safe_int just as raw short+short promotes to int. // ============================================================================ // Homogeneous short×short: both operands promote to int before the op. static_assert(std::is_same_v{} + safe_int{}), safe_int>); static_assert(std::is_same_v{} * safe_int{}), safe_int>); // Scalar path inherits promotion: safe_int * int-literal. static_assert(std::is_same_v{} * 2), safe_int>); // Same-signedness widening (safe_int + safe_int) → safe_int. // The narrower type converts implicitly to safe_int (value-preserving), and // the homogeneous operator runs. static_assert(std::is_same_v{} + safe_int{}), safe_int>); static_assert(std::is_same_v{} + safe_int{}), safe_int>); // Mixed-signedness safe_int×safe_int arithmetic is intentionally ill-formed. // (has_common_type is false for int/unsigned pairs — explicit conversion required.) static_assert(!std::is_invocable_v, safe_int, safe_int>); static_assert(!std::is_invocable_v, safe_int, safe_int>); static_assert(!std::is_invocable_v, safe_int, safe_int>); static_assert(!std::is_invocable_v, safe_int, safe_int>); static_assert(!std::is_invocable_v, safe_int, safe_int>); static_assert(!std::is_invocable_v, safe_int, safe_int>); static_assert(!std::is_invocable_v, safe_int, safe_int>); static_assert(!std::is_invocable_v, safe_int, safe_int>); // Scalar path with matching sub-int type must also promote. // std::common_type_t = short, but decltype(short{} op short{}) = int. // Each operator below must return safe_int, not safe_int. static_assert(std::is_same_v{} + short{}), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{} - short{}), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{} * short{}), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{} / short{}), safe_int>); static_assert(std::is_same_v{}), safe_int>); static_assert(std::is_same_v{} % short{}), safe_int>); static_assert(std::is_same_v{}), safe_int>); // ============================================================================ // Using safe_int as a quantity representation type // ============================================================================ using namespace mp_units::si::unit_symbols; // Basic quantity construction and operations static_assert([] { quantity> dist = safe_int{100} * m; return dist.numerical_value_in(m) == 100; }()); // Unit conversion with safe_int static_assert([] { quantity> distance = safe_int{2} * m; quantity> distance_mm = distance; return distance_mm.numerical_value_in(mm) == 2000; }()); // Arithmetic operations on quantities with safe_int static_assert([] { quantity> d1 = safe_int{100} * m; quantity> d2 = safe_int{50} * m; auto sum = d1 + d2; return sum.numerical_value_in(m) == 150; }()); // Multiplication of quantities static_assert([] { quantity> length = safe_int{3} * m; quantity> width = safe_int{4} * m; auto area = length * width; return area.numerical_value_in(m2) == 12; }()); // Division of quantities static_assert([] { quantity> distance = safe_int{100} * m; quantity> time = safe_int{10} * s; auto speed = distance / time; return speed.numerical_value_in(m / s) == 10; }()); // Scalar multiplication static_assert([] { quantity> dist = safe_int{5} * m; auto doubled = dist * 2; return doubled.numerical_value_in(m) == 10; }()); // Comparison of quantities with safe_int static_assert([] { quantity> d1 = safe_int{100} * m; quantity> d2 = safe_int{50} * m; return d1 > d2; }()); // Same safe_int rep type requirement for quantity arithmetic static_assert([] { quantity> d1 = safe_int{100} * m; quantity> d2 = safe_int{50} * m; auto sum = d1 + d2; return sum.numerical_value_in(m) == 150; }()); // verify safe_int propagates through quantity operations static_assert([] { quantity> dist = safe_int{10} * m; auto squared = dist * dist; // Result should still use safe_int using result_rep = decltype(squared)::rep; return std::is_same_v>; }()); // Convenience aliases work with quantities static_assert([] { quantity dist = safe_i32{100} * m; return dist.numerical_value_in(m) == 100; }()); // ============================================================================ // Quantity-level common_type and implicit conversion with safe_int reps // // The framework's quantity common_type is: // quantity> // It uses common_type_t directly (not the ternary trick on quantities). // This means bidirectional quantity-level implicit constructors (implicitly_scalable // is true for same-unit non-FP) do NOT cause ambiguous ternaries — the raw // safe_int level (where only widening is implicit) resolves common_type correctly. // ============================================================================ using namespace mp_units::si::unit_symbols; using qty_m_int = quantity; using qty_m_si = quantity>; // Widening: safe_int → safe_int is implicit at both rep and quantity level. static_assert(std::is_convertible_v>, quantity>>); // Narrowing: safe_int → safe_int is explicit at both the rep and quantity // level: the quantity constructor gates on `std::convertible_to`, which is // false when the rep's own constructor is explicit. static_assert(!std::is_convertible_v>, quantity>>); static_assert(std::is_constructible_v>, quantity>>); // Common type: quantity> wins because common_type_t, safe_int> // = safe_int (widening at the raw safe_int level is one-directional: short→int implicit only). static_assert(std::is_same_v>, quantity>>, quantity>>); static_assert(std::is_same_v>, quantity>>, quantity>>); // With explicit operator T(), common_type, int> = safe_int, so // mixing quantity> with quantity is now well-formed and // the result preserves the safety wrapper. static_assert(std::is_same_v>, quantity>, quantity>>); // unit-conversion implicit: m→mm scales by ×1000 (integral), so implicitly_scalable=true. static_assert(std::is_convertible_v>, quantity], safe_int>>); // unit-conversion explicit: mm→m scales by ÷1000 (non-integral), so implicitly_scalable=false. static_assert(!std::is_convertible_v], safe_int>, quantity>>); } // namespace