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/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

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.
*/
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

View File

@ -23,6 +23,8 @@
#include <limits>
#include <type_traits>
#include <QtCore/q26numeric.h> // temporarily, for saturate_cast
#ifndef __has_extension
# define __has_extension(X) 0
#endif
@ -431,39 +433,10 @@ template <auto V2, typename T> 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<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>
static constexpr auto qt_saturate(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
}
return q26::saturate_cast<To>(x);
}
QT_END_NAMESPACE

View File

@ -9,7 +9,8 @@
#include <private/qthread_p.h>
#include <private/qcoreapplication_p.h>
#include <private/qfreelist_p.h>
#include <private/qnumeric_p.h>
#include <QtCore/q26numeric.h>
QT_BEGIN_NAMESPACE
@ -59,7 +60,7 @@ template <typename T> static T fromDuration(std::chrono::nanoseconds interval)
{
using namespace std::chrono;
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)

View File

@ -4,7 +4,6 @@
#include "qeventdispatcher_glib_p.h"
#include "qeventdispatcher_unix_p.h"
#include <private/qnumeric_p.h>
#include <private/qthread_p.h>
#include "qcoreapplication.h"
@ -12,6 +11,8 @@
#include <QtCore/qlist.h>
#include <QtCore/q26numeric.h>
#include <glib.h>
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<gint>(ceil<milliseconds>(remaining).count());
*timeout = q26::saturate_cast<gint>(ceil<milliseconds>(remaining).count());
return (*timeout == 0);
}

View File

@ -32,6 +32,7 @@
#include <stdlib.h>
#include <algorithm>
#include <QtCore/q26numeric.h>
#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<CompressSizeHint_t>(nbytes), out.data());
qToBigEndian(q26::saturate_cast<CompressSizeHint_t>(nbytes), out.data());
out.size = HeaderSize;
return xxflate(ZLibOp::Compression, std::move(out), {data, nbytes},

View File

@ -25,7 +25,7 @@
#ifndef QT_BOOTSTRAPPED
#include <QtCore/qvarlengtharray.h>
#include <QtCore/q20iterator.h>
#include <QtCore/private/qnumeric_p.h>
#include <QtCore/q26numeric.h>
#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<int>(mblen);
int nextIn = q26::saturate_cast<int>(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<int>(outlen);
const int nextOut = q26::saturate_cast<int>(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<int>(mblen);
nextIn = q26::saturate_cast<int>(mblen);
}
if (sp.isEmpty()) {
@ -1572,7 +1572,7 @@ QByteArray QLocal8Bit::convertFromUnicode_sys(QStringView in, quint32 codePage,
};
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
// 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<int>(outlen);
const int nextOut = q26::saturate_cast<int>(outlen);
len = WideCharToMultiByte(codePage, 0, ch, nextIn, out, nextOut, nullptr, nullptr);
if (len > 0) {
ch += nextIn;

View File

@ -12,10 +12,9 @@
#include <QtTest/qtestassert.h>
#include <QtTest/qtesteventloop.h>
#include <QtCore/private/qnumeric_p.h>
#include <climits>
#include <cwchar>
#include <QtCore/q26numeric.h>
#include <stdlib.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);
if (r == size_t(-1)) // encoding error, fall back to strlen()
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().

View File

@ -10,6 +10,8 @@
#include <math.h>
#include <float.h>
#include <QtCore/q26numeric.h>
namespace {
template <typename F> 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 <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)
#include "tst_qnumeric.moc"