diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 53ebb6ca8ec..076eabcbe78 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -100,6 +100,7 @@ qt_internal_add_module(Core global/q23functional.h global/q23utility.cpp # remove once we have a user that tests this global/q23utility.h + global/q26numeric.h global/qxpfunctional.h global/qxptype_traits.h global/qversiontagging.h diff --git a/src/corelib/global/q26numeric.h b/src/corelib/global/q26numeric.h new file mode 100644 index 00000000000..72a4d499481 --- /dev/null +++ b/src/corelib/global/q26numeric.h @@ -0,0 +1,69 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#ifndef Q26NUMERIC_H +#define Q26NUMERIC_H + +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. Types and functions defined in this +// file can reliably be replaced by their std counterparts, once available. +// You may use these definitions in your own code, but be aware that we +// will remove them once Qt depends on the C++ version that supports +// them in namespace std. There will be NO deprecation warning, the +// definitions will JUST go away. +// +// If you can't agree to these terms, don't use these definitions! +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace q26 { + +// Like std::saturate_cast +#ifdef __cpp_lib_saturation_arithmetic +using std::saturate_cast; +#else +template +constexpr auto saturate_cast(From x) +{ + static_assert(std::is_integral_v); + static_assert(std::is_integral_v); + + [[maybe_unused]] + constexpr auto Lo = (std::numeric_limits::min)(); + constexpr auto Hi = (std::numeric_limits::max)(); + + if constexpr (std::is_signed_v == std::is_signed_v) { + // same signedness, we can accept regular integer conversion rules + return x < Lo ? Lo : + x > Hi ? Hi : + /*else*/ To(x); + } else { + if constexpr (std::is_signed_v) { // ie. !is_signed_v + if (x < From{0}) + return To{0}; + } + + // from here on, x >= 0 + using FromU = std::make_unsigned_t; + using ToU = std::make_unsigned_t; + return FromU(x) > ToU(Hi) ? Hi : To(x); // assumes Hi >= 0 + } +} +#endif // __cpp_lib_saturation_arithmetic + +} // namespace q26 + +QT_END_NAMESPACE + +#endif /* Q26NUMERIC_H */ diff --git a/src/corelib/global/qnumeric.cpp b/src/corelib/global/qnumeric.cpp index a46039c5da5..16b2b7ca408 100644 --- a/src/corelib/global/qnumeric.cpp +++ b/src/corelib/global/qnumeric.cpp @@ -458,39 +458,4 @@ Q_CORE_EXPORT quint64 qFloatDistance(double a, double b) Returns true if the absolute value of \a f is within 0.00001f of 0.0. */ -namespace QtNumericTests { - -template static constexpr T max = std::numeric_limits::max(); -template static constexpr T min = std::numeric_limits::min(); - -static_assert(qt_saturate(max) == max); -static_assert(qt_saturate(max) == max); -static_assert(qt_saturate(max) == qint64(max)); - -static_assert(qt_saturate(max) == max); -static_assert(qt_saturate(max) == unsigned(max)); -static_assert(qt_saturate(max) == qint64(max)); - -static_assert(qt_saturate(max) == max); -static_assert(qt_saturate(max) == max); -static_assert(qt_saturate(max) == max); -static_assert(qt_saturate(max) == quint64(max)); - -static_assert(qt_saturate(max) == max); -static_assert(qt_saturate(max) == max); -static_assert(qt_saturate(max) == max); -static_assert(qt_saturate(max) == max); - -static_assert(qt_saturate(min) == min); -static_assert(qt_saturate(min) == qint64(min)); -static_assert(qt_saturate(min) == 0); -static_assert(qt_saturate(min) == 0); - -static_assert(qt_saturate(min) == min); -static_assert(qt_saturate(min) == min); -static_assert(qt_saturate(min) == 0); -static_assert(qt_saturate(min) == 0); - -} // namespace QtNumericTests - QT_END_NAMESPACE diff --git a/src/corelib/global/qnumeric_p.h b/src/corelib/global/qnumeric_p.h index d40e6b964b0..8414a681fc7 100644 --- a/src/corelib/global/qnumeric_p.h +++ b/src/corelib/global/qnumeric_p.h @@ -23,6 +23,8 @@ #include #include +#include // temporarily, for saturate_cast + #ifndef __has_extension # define __has_extension(X) 0 #endif @@ -431,39 +433,10 @@ template bool mul_overflow(T v1, T *r) } #endif // Q_QDOC -/* - Safely narrows \a x to \c{To}. Let \c L be - \c{std::numeric_limit::min()} and \c H be \c{std::numeric_limit::max()}. - - If \a x is less than L, returns L. If \a x is greater than H, - returns H. Otherwise, returns \c{To(x)}. -*/ template static constexpr auto qt_saturate(From x) { - static_assert(std::is_integral_v); - static_assert(std::is_integral_v); - - [[maybe_unused]] - constexpr auto Lo = (std::numeric_limits::min)(); - constexpr auto Hi = (std::numeric_limits::max)(); - - if constexpr (std::is_signed_v == std::is_signed_v) { - // same signedness, we can accept regular integer conversion rules - return x < Lo ? Lo : - x > Hi ? Hi : - /*else*/ To(x); - } else { - if constexpr (std::is_signed_v) { // ie. !is_signed_v - if (x < From{0}) - return To{0}; - } - - // from here on, x >= 0 - using FromU = std::make_unsigned_t; - using ToU = std::make_unsigned_t; - return FromU(x) > ToU(Hi) ? Hi : To(x); // assumes Hi >= 0 - } + return q26::saturate_cast(x); } QT_END_NAMESPACE diff --git a/src/corelib/kernel/qabstracteventdispatcher.cpp b/src/corelib/kernel/qabstracteventdispatcher.cpp index b6619d3c4d2..f022b3b463b 100644 --- a/src/corelib/kernel/qabstracteventdispatcher.cpp +++ b/src/corelib/kernel/qabstracteventdispatcher.cpp @@ -9,7 +9,8 @@ #include #include #include -#include + +#include QT_BEGIN_NAMESPACE @@ -59,7 +60,7 @@ template static T fromDuration(std::chrono::nanoseconds interval) { using namespace std::chrono; qint64 value = ceil(interval).count(); - return qt_saturate(value); + return q26::saturate_cast(value); } #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) diff --git a/src/corelib/kernel/qeventdispatcher_glib.cpp b/src/corelib/kernel/qeventdispatcher_glib.cpp index 1e906c4b275..520489939b7 100644 --- a/src/corelib/kernel/qeventdispatcher_glib.cpp +++ b/src/corelib/kernel/qeventdispatcher_glib.cpp @@ -4,7 +4,6 @@ #include "qeventdispatcher_glib_p.h" #include "qeventdispatcher_unix_p.h" -#include #include #include "qcoreapplication.h" @@ -12,6 +11,8 @@ #include +#include + #include using namespace std::chrono; @@ -104,7 +105,7 @@ static gboolean timerSourcePrepareHelper(GTimerSource *src, gint *timeout) } auto remaining = src->timerList.timerWait().value_or(-1ms); - *timeout = qt_saturate(ceil(remaining).count()); + *timeout = q26::saturate_cast(ceil(remaining).count()); return (*timeout == 0); } diff --git a/src/corelib/text/qbytearray.cpp b/src/corelib/text/qbytearray.cpp index bf9852dcd46..7ec73edf6c8 100644 --- a/src/corelib/text/qbytearray.cpp +++ b/src/corelib/text/qbytearray.cpp @@ -32,6 +32,7 @@ #include #include +#include #ifdef Q_OS_WIN # if !defined(QT_BOOTSTRAPPED) && (defined(QT_NO_CAST_FROM_ASCII) || defined(QT_NO_CAST_FROM_BYTEARRAY)) @@ -728,7 +729,7 @@ QByteArray qCompress(const uchar* data, qsizetype nbytes, int compressionLevel) if (out.data() == nullptr) // allocation failed return tooMuchData(ZLibOp::Compression); - qToBigEndian(qt_saturate(nbytes), out.data()); + qToBigEndian(q26::saturate_cast(nbytes), out.data()); out.size = HeaderSize; return xxflate(ZLibOp::Compression, std::move(out), {data, nbytes}, diff --git a/src/corelib/text/qstringconverter.cpp b/src/corelib/text/qstringconverter.cpp index 57e0e30d9c8..e411990a456 100644 --- a/src/corelib/text/qstringconverter.cpp +++ b/src/corelib/text/qstringconverter.cpp @@ -25,7 +25,7 @@ #ifndef QT_BOOTSTRAPPED #include #include -#include +#include #endif // !QT_BOOTSTRAPPED #endif @@ -1385,12 +1385,12 @@ QString QLocal8Bit::convertToUnicode_sys(QByteArrayView in, quint32 codePage, // Need it in this scope, since we try to decrease our window size if we // encounter an error - int nextIn = qt_saturate(mblen); + int nextIn = q26::saturate_cast(mblen); while (mblen > 0) { std::tie(out, outlen) = growOut(1); // Need space for at least one character if (!out) return {}; - const int nextOut = qt_saturate(outlen); + const int nextOut = q26::saturate_cast(outlen); int len = MultiByteToWideChar(codePage, MB_ERR_INVALID_CHARS, mb, nextIn, out, nextOut); if (len) { mb += nextIn; @@ -1450,7 +1450,7 @@ QString QLocal8Bit::convertToUnicode_sys(QByteArrayView in, quint32 codePage, break; } } - nextIn = qt_saturate(mblen); + nextIn = q26::saturate_cast(mblen); } if (sp.isEmpty()) { @@ -1572,7 +1572,7 @@ QByteArray QLocal8Bit::convertFromUnicode_sys(QStringView in, quint32 codePage, }; const auto getNextWindowSize = [&]() { - int nextIn = qt_saturate(uclen); + int nextIn = q26::saturate_cast(uclen); // The Windows API has some issues if the current window ends in the // middle of a surrogate pair, so we avoid that: if (nextIn > 1 && QChar::isHighSurrogate(ch[nextIn - 1])) @@ -1586,7 +1586,7 @@ QByteArray QLocal8Bit::convertFromUnicode_sys(QStringView in, quint32 codePage, std::tie(out, outlen) = growOut(1); // We need at least one byte if (!out) return {}; - const int nextOut = qt_saturate(outlen); + const int nextOut = q26::saturate_cast(outlen); len = WideCharToMultiByte(codePage, 0, ch, nextIn, out, nextOut, nullptr, nullptr); if (len > 0) { ch += nextIn; diff --git a/src/testlib/qtestresult.cpp b/src/testlib/qtestresult.cpp index fd4a9fd2f75..a9f42cbf5e2 100644 --- a/src/testlib/qtestresult.cpp +++ b/src/testlib/qtestresult.cpp @@ -12,10 +12,9 @@ #include #include -#include - #include #include +#include #include #include @@ -338,7 +337,7 @@ static int approx_wide_len(const char *s) auto r = std::mbsrtowcs(nullptr, &s, INT_MAX, &state); if (r == size_t(-1)) // encoding error, fall back to strlen() r = strlen(s); // `s` was not advanced since `dst == nullptr` - return qt_saturate(r); + return q26::saturate_cast(r); } // Overload to format failures for "const char *" - no need to strdup(). diff --git a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp index d21fabd74e4..d7a2ded57a6 100644 --- a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp +++ b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp @@ -10,6 +10,8 @@ #include #include +#include + namespace { template struct Fuzzy {}; /* Data taken from qglobal.h's implementation of qFuzzyCompare: @@ -750,5 +752,40 @@ void tst_QNumeric::signedOverflow() QCOMPARE(qMulOverflow(maxInt, maxInt, &r), true); } +namespace SaturateCastTest { + +template static constexpr T max = std::numeric_limits::max(); +template static constexpr T min = std::numeric_limits::min(); + +static_assert(q26::saturate_cast(max) == max); +static_assert(q26::saturate_cast(max) == max); +static_assert(q26::saturate_cast(max) == qint64(max)); + +static_assert(q26::saturate_cast(max) == max); +static_assert(q26::saturate_cast(max) == unsigned(max)); +static_assert(q26::saturate_cast(max) == qint64(max)); + +static_assert(q26::saturate_cast(max) == max); +static_assert(q26::saturate_cast(max) == max); +static_assert(q26::saturate_cast(max) == max); +static_assert(q26::saturate_cast(max) == quint64(max)); + +static_assert(q26::saturate_cast(max) == max); +static_assert(q26::saturate_cast(max) == max); +static_assert(q26::saturate_cast(max) == max); +static_assert(q26::saturate_cast(max) == max); + +static_assert(q26::saturate_cast(min) == min); +static_assert(q26::saturate_cast(min) == qint64(min)); +static_assert(q26::saturate_cast(min) == 0); +static_assert(q26::saturate_cast(min) == 0); + +static_assert(q26::saturate_cast(min) == min); +static_assert(q26::saturate_cast(min) == min); +static_assert(q26::saturate_cast(min) == 0); +static_assert(q26::saturate_cast(min) == 0); + +} // namespace SaturateCastTest + QTEST_APPLESS_MAIN(tst_QNumeric) #include "tst_qnumeric.moc"