// Copyright (C) 2025 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QCHECKEDINT_H #define QCHECKEDINT_H #include #include #include #include #include QT_BEGIN_NAMESPACE namespace QtPrivate { namespace QCheckedIntegers { template struct CheckIntTypeHelper { static_assert(std::is_integral_v, "Only integer types are supported"); static_assert(std::is_signed_v, "Only signed types are supported"); static_assert(std::is_same_v, "Only fully promoted types are supported"); }; // Implements wraparound semantics, and checks for overflow. // Never causes UB on any operation. template struct SafeCheckImpl : private CheckIntTypeHelper { static inline constexpr Int MinInt = (std::numeric_limits::min)(); static inline constexpr Int MaxInt = (std::numeric_limits::max)(); [[nodiscard]] static constexpr bool add(Int a, Int b, Int *result) noexcept { return !qAddOverflow(a, b, result); } [[nodiscard]] static constexpr bool sub(Int a, Int b, Int *result) noexcept { return !qSubOverflow(a, b, result); } [[nodiscard]] static constexpr bool mul(Int a, Int b, Int *result) noexcept { return !qMulOverflow(a, b, result); } [[nodiscard]] static constexpr bool div(Int a, Int b, Int *result) noexcept { if (Q_UNLIKELY(b == 0)) return false; *result = a / b; return true; } }; // Reports failed checks through Q_ASSERT. struct AssertReportPolicy { static constexpr void check(bool ok, const char *where, const char *description) { Q_ASSERT_X(ok, where, description); } }; template , typename FailureReportPolicy = AssertReportPolicy> class QCheckedInt : private CheckIntTypeHelper { Int m_i; #define Q_CHECKEDINT_POLICY_CHECK(cond, what) \ FailureReportPolicy::check(cond, Q_FUNC_INFO, what) public: template using if_is_same_int = std::enable_if_t, bool>; QCheckedInt() = default; explicit constexpr QCheckedInt(Int i) noexcept : m_i(i) {} // Conversions explicit constexpr operator Int() const noexcept { return m_i; } // Accessors constexpr Int value() const noexcept { return m_i; } template = true> constexpr void setValue(AInt i) noexcept { m_i = i; } constexpr Int &as_underlying() & noexcept { return m_i; } constexpr const Int &as_underlying() const & noexcept { return m_i; } constexpr Int &&as_underlying() && noexcept { return std::move(m_i); } constexpr const Int &&as_underlying() const && noexcept { return std::move(m_i); } // Unary ops constexpr QCheckedInt operator+() const noexcept { return *this; } constexpr QCheckedInt operator-() const { QCheckedInt result{}; const bool ok = Impl::sub(Int(0), m_i, &result.m_i); Q_CHECKEDINT_POLICY_CHECK(ok, "Overflow in unary negation"); return result; } constexpr QCheckedInt &operator++() { const bool ok = Impl::add(m_i, Int(1), &m_i); Q_CHECKEDINT_POLICY_CHECK(ok, "Overflow in operator++"); return *this; } constexpr QCheckedInt operator++(int) { QCheckedInt result = *this; ++*this; return result; } constexpr QCheckedInt &operator--() { const bool ok = Impl::sub(m_i, Int(1), &m_i); Q_CHECKEDINT_POLICY_CHECK(ok, "Overflow in operator--"); return *this; } constexpr QCheckedInt operator--(int) { QCheckedInt result = *this; --*this; return result; } // Addition friend constexpr QCheckedInt operator+(QCheckedInt lhs, QCheckedInt rhs) { QCheckedInt result{}; const bool ok = Impl::add(lhs.m_i, rhs.m_i, &result.m_i); Q_CHECKEDINT_POLICY_CHECK(ok, "Overflow in operator+"); return result; } template = true> friend constexpr QCheckedInt operator+(QCheckedInt lhs, AInt rhs) { return lhs + QCheckedInt(rhs); } template = true> friend constexpr QCheckedInt operator+(AInt lhs, QCheckedInt rhs) { return QCheckedInt(lhs) + rhs; } constexpr QCheckedInt &operator+=(QCheckedInt other) { return *this = *this + other; } template = true> constexpr QCheckedInt &operator+=(AInt other) { return *this = *this + QCheckedInt(other); } // Subtraction friend constexpr QCheckedInt operator-(QCheckedInt lhs, QCheckedInt rhs) { QCheckedInt result{}; const bool ok = Impl::sub(lhs.m_i, rhs.m_i, &result.m_i); Q_CHECKEDINT_POLICY_CHECK(ok, "Overflow in operator-"); return result; } template = true> friend constexpr QCheckedInt operator-(QCheckedInt lhs, AInt rhs) { return lhs - QCheckedInt(rhs); } template = true> friend constexpr QCheckedInt operator-(AInt lhs, QCheckedInt rhs) { return QCheckedInt(lhs) - rhs; } constexpr QCheckedInt &operator-=(QCheckedInt other) { return *this = *this - other; } template = true> constexpr QCheckedInt &operator-=(AInt other) { return *this = *this - QCheckedInt(other); } // Multiplication friend constexpr QCheckedInt operator*(QCheckedInt lhs, QCheckedInt rhs) { QCheckedInt result{}; const bool ok = Impl::mul(lhs.m_i, rhs.m_i, &result.m_i); Q_CHECKEDINT_POLICY_CHECK(ok, "Overflow in operator*"); return result; } template = true> friend constexpr QCheckedInt operator*(QCheckedInt lhs, AInt rhs) { return lhs * QCheckedInt(rhs); } template = true> friend constexpr QCheckedInt operator*(AInt lhs, QCheckedInt rhs) { return QCheckedInt(lhs) * rhs; } constexpr QCheckedInt &operator*=(QCheckedInt other) { return *this = *this * other; } template = true> constexpr QCheckedInt &operator*=(AInt other) { return *this = *this * QCheckedInt(other); } // Division friend constexpr QCheckedInt operator/(QCheckedInt lhs, QCheckedInt rhs) { QCheckedInt result{}; const bool ok = Impl::div(lhs.m_i, rhs.m_i, &result.m_i); Q_CHECKEDINT_POLICY_CHECK(ok, "Division by zero"); return result; } template = true> friend constexpr QCheckedInt operator/(QCheckedInt lhs, AInt rhs) { return lhs / QCheckedInt(rhs); } template = true> friend constexpr QCheckedInt operator/(AInt lhs, QCheckedInt rhs) { return QCheckedInt(lhs) / rhs; } constexpr QCheckedInt &operator/=(QCheckedInt other) { return *this = *this / other; } template = true> constexpr QCheckedInt &operator/=(AInt other) { return *this = *this / QCheckedInt(other); } #undef Q_CHECKEDINT_POLICY_CHECK // Comparisons friend constexpr bool comparesEqual(QCheckedInt lhs, QCheckedInt rhs) noexcept { return lhs.m_i == rhs.m_i; } template = true> friend constexpr bool comparesEqual(QCheckedInt lhs, AInt rhs) noexcept { return lhs.m_i == rhs; } friend constexpr Qt::strong_ordering compareThreeWay(QCheckedInt lhs, QCheckedInt rhs) noexcept { return Qt::compareThreeWay(lhs.m_i, rhs.m_i); } template = true> friend constexpr Qt::strong_ordering compareThreeWay(QCheckedInt lhs, AInt rhs) noexcept { return Qt::compareThreeWay(lhs.m_i, rhs); } Q_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE(QCheckedInt) Q_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE( QCheckedInt, AInt, template = true>) }; template Q_DECL_CONST_FUNCTION size_t constexpr inline qHash(QCheckedInt key, size_t seed = 0) noexcept { using QT_PREPEND_NAMESPACE(qHash); return qHash(key.value(), seed); } } // namespace QCheckedIntegers } // namespace QtPrivate QT_END_NAMESPACE #endif // QCHECKEDINT_H