Revision control
Copy as Markdown
// Copyright (C) 2025 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#ifndef QCHECKEDINT_H
#define QCHECKEDINT_H
#include <QtCore/qassert.h>
#include <QtCore/qcompare.h>
#include <QtCore/qhashfunctions.h>
#include <QtCore/qnumeric.h>
#include <type_traits>
QT_BEGIN_NAMESPACE
namespace QtPrivate {
namespace QCheckedIntegers {
template <typename Int>
struct CheckIntTypeHelper
{
static_assert(std::is_integral_v<Int>, "Only integer types are supported");
static_assert(std::is_signed_v<Int>, "Only signed types are supported");
static_assert(std::is_same_v<Int, decltype(+Int{})>, "Only fully promoted types are supported");
};
// Implements wraparound semantics, and checks for overflow.
// Never causes UB on any operation.
template <typename Int>
struct SafeCheckImpl : private CheckIntTypeHelper<Int>
{
static inline constexpr Int MinInt = (std::numeric_limits<Int>::min)();
static inline constexpr Int MaxInt = (std::numeric_limits<Int>::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;
if (b == -1 && a == MinInt)
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 Int,
typename Impl = SafeCheckImpl<Int>,
typename FailureReportPolicy = AssertReportPolicy>
class QCheckedInt : private CheckIntTypeHelper<Int>
{
Int m_i;
#define Q_CHECKEDINT_POLICY_CHECK(cond, what) \
FailureReportPolicy::check(cond, Q_FUNC_INFO, what)
public:
template <typename AInt>
using if_is_same_int = std::enable_if_t<std::is_same_v<Int, AInt>, 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 <typename AInt, if_is_same_int<AInt> = 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 <typename AInt, if_is_same_int<AInt> = true>
friend constexpr QCheckedInt operator+(QCheckedInt lhs, AInt rhs)
{
return lhs + QCheckedInt(rhs);
}
template <typename AInt, if_is_same_int<AInt> = true>
friend constexpr QCheckedInt operator+(AInt lhs, QCheckedInt rhs)
{
return QCheckedInt(lhs) + rhs;
}
constexpr QCheckedInt &operator+=(QCheckedInt other)
{
return *this = *this + other;
}
template <typename AInt, if_is_same_int<AInt> = 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 <typename AInt, if_is_same_int<AInt> = true>
friend constexpr QCheckedInt operator-(QCheckedInt lhs, AInt rhs)
{
return lhs - QCheckedInt(rhs);
}
template <typename AInt, if_is_same_int<AInt> = true>
friend constexpr QCheckedInt operator-(AInt lhs, QCheckedInt rhs)
{
return QCheckedInt(lhs) - rhs;
}
constexpr QCheckedInt &operator-=(QCheckedInt other)
{
return *this = *this - other;
}
template <typename AInt, if_is_same_int<AInt> = 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 <typename AInt, if_is_same_int<AInt> = true>
friend constexpr QCheckedInt operator*(QCheckedInt lhs, AInt rhs)
{
return lhs * QCheckedInt(rhs);
}
template <typename AInt, if_is_same_int<AInt> = true>
friend constexpr QCheckedInt operator*(AInt lhs, QCheckedInt rhs)
{
return QCheckedInt(lhs) * rhs;
}
constexpr QCheckedInt &operator*=(QCheckedInt other)
{
return *this = *this * other;
}
template <typename AInt, if_is_same_int<AInt> = 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 <typename AInt, if_is_same_int<AInt> = true>
friend constexpr QCheckedInt operator/(QCheckedInt lhs, AInt rhs)
{
return lhs / QCheckedInt(rhs);
}
template <typename AInt, if_is_same_int<AInt> = true>
friend constexpr QCheckedInt operator/(AInt lhs, QCheckedInt rhs)
{
return QCheckedInt(lhs) / rhs;
}
constexpr QCheckedInt &operator/=(QCheckedInt other)
{
return *this = *this / other;
}
template <typename AInt, if_is_same_int<AInt> = 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 <typename AInt, if_is_same_int<AInt> = 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 <typename AInt, if_is_same_int<AInt> = 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 <typename AInt, if_is_same_int<AInt> = true>)
private:
friend size_t constexpr qHash(QCheckedInt key, size_t seed = 0) noexcept
{
using QT_PREPEND_NAMESPACE(qHash); // ### needed?
return qHash(key.value(), seed);
}
};
} // namespace QCheckedIntegers
} // namespace QtPrivate
QT_END_NAMESPACE
#endif // QCHECKEDINT_H