Rename qt_saturate to q26::saturate_cast

C++26 adds std::saturate_cast, so follow the established pattern of
other similar "backported" APIs. The old name is left around while
we port other submodules.

While at it, move qt_saturate's tests to the qnumeric test.

Change-Id: I653a2e3d936081378298a9c8e51e7c1a2d438d83
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit 7447ad503330ed176cf369792ffb33b7e00a58d3)
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Giuseppe D'Angelo 2024-08-01 11:17:03 +02:00
parent f2e083156e
commit 27dc042712
10 changed files with 126 additions and 79 deletions

View File

@ -100,6 +100,7 @@ qt_internal_add_module(Core
global/q23functional.h global/q23functional.h
global/q23utility.cpp # remove once we have a user that tests this global/q23utility.cpp # remove once we have a user that tests this
global/q23utility.h global/q23utility.h
global/q26numeric.h
global/qxpfunctional.h global/qxpfunctional.h
global/qxptype_traits.h global/qxptype_traits.h
global/qversiontagging.h global/qversiontagging.h

View File

@ -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 <QtCore/qglobal.h>
//
// 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 <numeric>
#include <limits>
#include <type_traits>
QT_BEGIN_NAMESPACE
namespace q26 {
// Like std::saturate_cast
#ifdef __cpp_lib_saturation_arithmetic
using std::saturate_cast;
#else
template <typename To, typename From>
constexpr auto saturate_cast(From x)
{
static_assert(std::is_integral_v<To>);
static_assert(std::is_integral_v<From>);
[[maybe_unused]]
constexpr auto Lo = (std::numeric_limits<To>::min)();
constexpr auto Hi = (std::numeric_limits<To>::max)();
if constexpr (std::is_signed_v<From> == std::is_signed_v<To>) {
// 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<From>) { // ie. !is_signed_v<To>
if (x < From{0})
return To{0};
}
// from here on, x >= 0
using FromU = std::make_unsigned_t<From>;
using ToU = std::make_unsigned_t<To>;
return FromU(x) > ToU(Hi) ? Hi : To(x); // assumes Hi >= 0
}
}
#endif // __cpp_lib_saturation_arithmetic
} // namespace q26
QT_END_NAMESPACE
#endif /* Q26NUMERIC_H */

View File

@ -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. Returns true if the absolute value of \a f is within 0.00001f of 0.0.
*/ */
namespace QtNumericTests {
template <typename T> static constexpr T max = std::numeric_limits<T>::max();
template <typename T> static constexpr T min = std::numeric_limits<T>::min();
static_assert(qt_saturate<short>(max<unsigned>) == max<short>);
static_assert(qt_saturate<int>(max<unsigned>) == max<int>);
static_assert(qt_saturate<qint64>(max<unsigned>) == qint64(max<unsigned>));
static_assert(qt_saturate<short>(max<int>) == max<short>);
static_assert(qt_saturate<unsigned>(max<int>) == unsigned(max<int>));
static_assert(qt_saturate<qint64>(max<int>) == qint64(max<int>));
static_assert(qt_saturate<short>(max<qint64>) == max<short>);
static_assert(qt_saturate<int>(max<qint64>) == max<int>);
static_assert(qt_saturate<unsigned>(max<qint64>) == max<unsigned>);
static_assert(qt_saturate<quint64>(max<qint64>) == quint64(max<qint64>));
static_assert(qt_saturate<short>(max<quint64>) == max<short>);
static_assert(qt_saturate<int>(max<quint64>) == max<int>);
static_assert(qt_saturate<unsigned>(max<quint64>) == max<unsigned>);
static_assert(qt_saturate<qint64>(max<quint64>) == max<qint64>);
static_assert(qt_saturate<short>(min<int>) == min<short>);
static_assert(qt_saturate<qint64>(min<int>) == qint64(min<int>));
static_assert(qt_saturate<unsigned>(min<int>) == 0);
static_assert(qt_saturate<quint64>(min<int>) == 0);
static_assert(qt_saturate<short>(min<qint64>) == min<short>);
static_assert(qt_saturate<int>(min<qint64>) == min<int>);
static_assert(qt_saturate<unsigned>(min<qint64>) == 0);
static_assert(qt_saturate<quint64>(min<qint64>) == 0);
} // namespace QtNumericTests
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -23,6 +23,8 @@
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
#include <QtCore/q26numeric.h> // temporarily, for saturate_cast
#ifndef __has_extension #ifndef __has_extension
# define __has_extension(X) 0 # define __has_extension(X) 0
#endif #endif
@ -431,39 +433,10 @@ template <auto V2, typename T> bool mul_overflow(T v1, T *r)
} }
#endif // Q_QDOC #endif // Q_QDOC
/*
Safely narrows \a x to \c{To}. Let \c L be
\c{std::numeric_limit<To>::min()} and \c H be \c{std::numeric_limit<To>::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 <typename To, typename From> template <typename To, typename From>
static constexpr auto qt_saturate(From x) static constexpr auto qt_saturate(From x)
{ {
static_assert(std::is_integral_v<To>); return q26::saturate_cast<To>(x);
static_assert(std::is_integral_v<From>);
[[maybe_unused]]
constexpr auto Lo = (std::numeric_limits<To>::min)();
constexpr auto Hi = (std::numeric_limits<To>::max)();
if constexpr (std::is_signed_v<From> == std::is_signed_v<To>) {
// 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<From>) { // ie. !is_signed_v<To>
if (x < From{0})
return To{0};
}
// from here on, x >= 0
using FromU = std::make_unsigned_t<From>;
using ToU = std::make_unsigned_t<To>;
return FromU(x) > ToU(Hi) ? Hi : To(x); // assumes Hi >= 0
}
} }
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -9,7 +9,8 @@
#include <private/qthread_p.h> #include <private/qthread_p.h>
#include <private/qcoreapplication_p.h> #include <private/qcoreapplication_p.h>
#include <private/qfreelist_p.h> #include <private/qfreelist_p.h>
#include <private/qnumeric_p.h>
#include <QtCore/q26numeric.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -59,7 +60,7 @@ template <typename T> static T fromDuration(std::chrono::nanoseconds interval)
{ {
using namespace std::chrono; using namespace std::chrono;
qint64 value = ceil<milliseconds>(interval).count(); qint64 value = ceil<milliseconds>(interval).count();
return qt_saturate<T>(value); return q26::saturate_cast<T>(value);
} }
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)

View File

@ -4,7 +4,6 @@
#include "qeventdispatcher_glib_p.h" #include "qeventdispatcher_glib_p.h"
#include "qeventdispatcher_unix_p.h" #include "qeventdispatcher_unix_p.h"
#include <private/qnumeric_p.h>
#include <private/qthread_p.h> #include <private/qthread_p.h>
#include "qcoreapplication.h" #include "qcoreapplication.h"
@ -12,6 +11,8 @@
#include <QtCore/qlist.h> #include <QtCore/qlist.h>
#include <QtCore/q26numeric.h>
#include <glib.h> #include <glib.h>
using namespace std::chrono; using namespace std::chrono;
@ -104,7 +105,7 @@ static gboolean timerSourcePrepareHelper(GTimerSource *src, gint *timeout)
} }
auto remaining = src->timerList.timerWait().value_or(-1ms); auto remaining = src->timerList.timerWait().value_or(-1ms);
*timeout = qt_saturate<gint>(ceil<milliseconds>(remaining).count()); *timeout = q26::saturate_cast<gint>(ceil<milliseconds>(remaining).count());
return (*timeout == 0); return (*timeout == 0);
} }

View File

@ -32,6 +32,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <algorithm> #include <algorithm>
#include <QtCore/q26numeric.h>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
# if !defined(QT_BOOTSTRAPPED) && (defined(QT_NO_CAST_FROM_ASCII) || defined(QT_NO_CAST_FROM_BYTEARRAY)) # 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 if (out.data() == nullptr) // allocation failed
return tooMuchData(ZLibOp::Compression); return tooMuchData(ZLibOp::Compression);
qToBigEndian(qt_saturate<CompressSizeHint_t>(nbytes), out.data()); qToBigEndian(q26::saturate_cast<CompressSizeHint_t>(nbytes), out.data());
out.size = HeaderSize; out.size = HeaderSize;
return xxflate(ZLibOp::Compression, std::move(out), {data, nbytes}, return xxflate(ZLibOp::Compression, std::move(out), {data, nbytes},

View File

@ -25,7 +25,7 @@
#ifndef QT_BOOTSTRAPPED #ifndef QT_BOOTSTRAPPED
#include <QtCore/qvarlengtharray.h> #include <QtCore/qvarlengtharray.h>
#include <QtCore/q20iterator.h> #include <QtCore/q20iterator.h>
#include <QtCore/private/qnumeric_p.h> #include <QtCore/q26numeric.h>
#endif // !QT_BOOTSTRAPPED #endif // !QT_BOOTSTRAPPED
#endif #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 // Need it in this scope, since we try to decrease our window size if we
// encounter an error // encounter an error
int nextIn = qt_saturate<int>(mblen); int nextIn = q26::saturate_cast<int>(mblen);
while (mblen > 0) { while (mblen > 0) {
std::tie(out, outlen) = growOut(1); // Need space for at least one character std::tie(out, outlen) = growOut(1); // Need space for at least one character
if (!out) if (!out)
return {}; return {};
const int nextOut = qt_saturate<int>(outlen); const int nextOut = q26::saturate_cast<int>(outlen);
int len = MultiByteToWideChar(codePage, MB_ERR_INVALID_CHARS, mb, nextIn, out, nextOut); int len = MultiByteToWideChar(codePage, MB_ERR_INVALID_CHARS, mb, nextIn, out, nextOut);
if (len) { if (len) {
mb += nextIn; mb += nextIn;
@ -1450,7 +1450,7 @@ QString QLocal8Bit::convertToUnicode_sys(QByteArrayView in, quint32 codePage,
break; break;
} }
} }
nextIn = qt_saturate<int>(mblen); nextIn = q26::saturate_cast<int>(mblen);
} }
if (sp.isEmpty()) { if (sp.isEmpty()) {
@ -1572,7 +1572,7 @@ QByteArray QLocal8Bit::convertFromUnicode_sys(QStringView in, quint32 codePage,
}; };
const auto getNextWindowSize = [&]() { const auto getNextWindowSize = [&]() {
int nextIn = qt_saturate<int>(uclen); int nextIn = q26::saturate_cast<int>(uclen);
// The Windows API has some issues if the current window ends in the // The Windows API has some issues if the current window ends in the
// middle of a surrogate pair, so we avoid that: // middle of a surrogate pair, so we avoid that:
if (nextIn > 1 && QChar::isHighSurrogate(ch[nextIn - 1])) 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 std::tie(out, outlen) = growOut(1); // We need at least one byte
if (!out) if (!out)
return {}; return {};
const int nextOut = qt_saturate<int>(outlen); const int nextOut = q26::saturate_cast<int>(outlen);
len = WideCharToMultiByte(codePage, 0, ch, nextIn, out, nextOut, nullptr, nullptr); len = WideCharToMultiByte(codePage, 0, ch, nextIn, out, nextOut, nullptr, nullptr);
if (len > 0) { if (len > 0) {
ch += nextIn; ch += nextIn;

View File

@ -12,10 +12,9 @@
#include <QtTest/qtestassert.h> #include <QtTest/qtestassert.h>
#include <QtTest/qtesteventloop.h> #include <QtTest/qtesteventloop.h>
#include <QtCore/private/qnumeric_p.h>
#include <climits> #include <climits>
#include <cwchar> #include <cwchar>
#include <QtCore/q26numeric.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
@ -338,7 +337,7 @@ static int approx_wide_len(const char *s)
auto r = std::mbsrtowcs(nullptr, &s, INT_MAX, &state); auto r = std::mbsrtowcs(nullptr, &s, INT_MAX, &state);
if (r == size_t(-1)) // encoding error, fall back to strlen() if (r == size_t(-1)) // encoding error, fall back to strlen()
r = strlen(s); // `s` was not advanced since `dst == nullptr` r = strlen(s); // `s` was not advanced since `dst == nullptr`
return qt_saturate<int>(r); return q26::saturate_cast<int>(r);
} }
// Overload to format failures for "const char *" - no need to strdup(). // Overload to format failures for "const char *" - no need to strdup().

View File

@ -10,6 +10,8 @@
#include <math.h> #include <math.h>
#include <float.h> #include <float.h>
#include <QtCore/q26numeric.h>
namespace { namespace {
template <typename F> struct Fuzzy {}; template <typename F> struct Fuzzy {};
/* Data taken from qglobal.h's implementation of qFuzzyCompare: /* Data taken from qglobal.h's implementation of qFuzzyCompare:
@ -750,5 +752,40 @@ void tst_QNumeric::signedOverflow()
QCOMPARE(qMulOverflow(maxInt, maxInt, &r), true); QCOMPARE(qMulOverflow(maxInt, maxInt, &r), true);
} }
namespace SaturateCastTest {
template <typename T> static constexpr T max = std::numeric_limits<T>::max();
template <typename T> static constexpr T min = std::numeric_limits<T>::min();
static_assert(q26::saturate_cast<short>(max<unsigned>) == max<short>);
static_assert(q26::saturate_cast<int>(max<unsigned>) == max<int>);
static_assert(q26::saturate_cast<qint64>(max<unsigned>) == qint64(max<unsigned>));
static_assert(q26::saturate_cast<short>(max<int>) == max<short>);
static_assert(q26::saturate_cast<unsigned>(max<int>) == unsigned(max<int>));
static_assert(q26::saturate_cast<qint64>(max<int>) == qint64(max<int>));
static_assert(q26::saturate_cast<short>(max<qint64>) == max<short>);
static_assert(q26::saturate_cast<int>(max<qint64>) == max<int>);
static_assert(q26::saturate_cast<unsigned>(max<qint64>) == max<unsigned>);
static_assert(q26::saturate_cast<quint64>(max<qint64>) == quint64(max<qint64>));
static_assert(q26::saturate_cast<short>(max<quint64>) == max<short>);
static_assert(q26::saturate_cast<int>(max<quint64>) == max<int>);
static_assert(q26::saturate_cast<unsigned>(max<quint64>) == max<unsigned>);
static_assert(q26::saturate_cast<qint64>(max<quint64>) == max<qint64>);
static_assert(q26::saturate_cast<short>(min<int>) == min<short>);
static_assert(q26::saturate_cast<qint64>(min<int>) == qint64(min<int>));
static_assert(q26::saturate_cast<unsigned>(min<int>) == 0);
static_assert(q26::saturate_cast<quint64>(min<int>) == 0);
static_assert(q26::saturate_cast<short>(min<qint64>) == min<short>);
static_assert(q26::saturate_cast<int>(min<qint64>) == min<int>);
static_assert(q26::saturate_cast<unsigned>(min<qint64>) == 0);
static_assert(q26::saturate_cast<quint64>(min<qint64>) == 0);
} // namespace SaturateCastTest
QTEST_APPLESS_MAIN(tst_QNumeric) QTEST_APPLESS_MAIN(tst_QNumeric)
#include "tst_qnumeric.moc" #include "tst_qnumeric.moc"