diff options
Diffstat (limited to 'src/corelib/tools')
69 files changed, 4751 insertions, 1221 deletions
diff --git a/src/corelib/tools/qarraydata.cpp b/src/corelib/tools/qarraydata.cpp index b1634d61b5..6aebd4306a 100644 --- a/src/corelib/tools/qarraydata.cpp +++ b/src/corelib/tools/qarraydata.cpp @@ -54,8 +54,8 @@ qsizetype qCalculateBlockSize(qsizetype elementCount, qsizetype elementSize, qsi Q_ASSERT(elementSize); size_t bytes; - if (Q_UNLIKELY(mul_overflow(size_t(elementSize), size_t(elementCount), &bytes)) || - Q_UNLIKELY(add_overflow(bytes, size_t(headerSize), &bytes))) + if (Q_UNLIKELY(qMulOverflow(size_t(elementSize), size_t(elementCount), &bytes)) || + Q_UNLIKELY(qAddOverflow(bytes, size_t(headerSize), &bytes))) return -1; if (Q_UNLIKELY(qsizetype(bytes) < 0)) return -1; @@ -107,33 +107,30 @@ qCalculateGrowingBlockSize(qsizetype elementCount, qsizetype elementSize, qsizet return result; } -/*! - \internal +/* + Calculate the byte size for a block of \a capacity objects of size \a + objectSize, with a header of size \a headerSize. If the \a option is + QArrayData::Grow, the capacity itself adjusted up, preallocating room for + more elements to be added later; otherwise, it is an exact calculation. - Returns \a allocSize plus extra reserved bytes necessary to store '\0'. - */ -static inline qsizetype reserveExtraBytes(qsizetype allocSize) + Returns a structure containing the size in bytes and elements available. +*/ +static inline CalculateGrowingBlockSizeResult +calculateBlockSize(qsizetype capacity, qsizetype objectSize, qsizetype headerSize, QArrayData::AllocationOption option) { - // We deal with QByteArray and QString only - constexpr qsizetype extra = qMax(sizeof(QByteArray::value_type), sizeof(QString::value_type)); - if (Q_UNLIKELY(allocSize < 0)) - return -1; - if (Q_UNLIKELY(add_overflow(allocSize, extra, &allocSize))) - return -1; - return allocSize; -} + // Adjust the header size up to account for the trailing null for QString + // and QByteArray. This is not checked for overflow because headers sizes + // should not be anywhere near the overflow limit. + constexpr qsizetype FooterSize = qMax(sizeof(QString::value_type), sizeof(QByteArray::value_type)); + if (objectSize <= FooterSize) + headerSize += FooterSize; -static inline qsizetype calculateBlockSize(qsizetype &capacity, qsizetype objectSize, qsizetype headerSize, QArrayData::AllocationOption option) -{ - // Calculate the byte size // allocSize = objectSize * capacity + headerSize, but checked for overflow // plus padded to grow in size if (option == QArrayData::Grow) { - auto r = qCalculateGrowingBlockSize(capacity, objectSize, headerSize); - capacity = r.elementCount; - return r.size; + return qCalculateGrowingBlockSize(capacity, objectSize, headerSize); } else { - return qCalculateBlockSize(capacity, objectSize, headerSize); + return { qCalculateBlockSize(capacity, objectSize, headerSize), capacity }; } } @@ -148,27 +145,20 @@ static QArrayData *allocateData(qsizetype allocSize) return header; } - namespace { -// QArrayData with strictest alignment requirements supported by malloc() -struct alignas(std::max_align_t) AlignedQArrayData : QArrayData -{ +struct AllocationResult { + void *data; + QArrayData *header; }; } +using QtPrivate::AlignedQArrayData; - -void *QArrayData::allocate(QArrayData **dptr, qsizetype objectSize, qsizetype alignment, - qsizetype capacity, QArrayData::AllocationOption option) noexcept +static inline AllocationResult +allocateHelper(qsizetype objectSize, qsizetype alignment, qsizetype capacity, + QArrayData::AllocationOption option) noexcept { - Q_ASSERT(dptr); - // Alignment is a power of two - Q_ASSERT(alignment >= qsizetype(alignof(QArrayData)) - && !(alignment & (alignment - 1))); - - if (capacity == 0) { - *dptr = nullptr; - return nullptr; - } + if (capacity == 0) + return {}; qsizetype headerSize = sizeof(AlignedQArrayData); const qsizetype headerAlignment = alignof(AlignedQArrayData); @@ -177,16 +167,16 @@ void *QArrayData::allocate(QArrayData **dptr, qsizetype objectSize, qsizetype al // Allocate extra (alignment - Q_ALIGNOF(AlignedQArrayData)) padding // bytes so we can properly align the data array. This assumes malloc is // able to provide appropriate alignment for the header -- as it should! + // Effectively, we allocate one QTypedArrayData<T>::AlignmentDummy. headerSize += alignment - headerAlignment; } Q_ASSERT(headerSize > 0); - qsizetype allocSize = calculateBlockSize(capacity, objectSize, headerSize, option); - allocSize = reserveExtraBytes(allocSize); - if (Q_UNLIKELY(allocSize < 0)) { // handle overflow. cannot allocate reliably - *dptr = nullptr; - return nullptr; - } + auto blockSize = calculateBlockSize(capacity, objectSize, headerSize, option); + capacity = blockSize.elementCount; + qsizetype allocSize = blockSize.size; + if (Q_UNLIKELY(allocSize < 0)) // handle overflow. cannot allocate reliably + return {}; QArrayData *header = allocateData(allocSize); void *data = nullptr; @@ -196,20 +186,54 @@ void *QArrayData::allocate(QArrayData **dptr, qsizetype objectSize, qsizetype al header->alloc = qsizetype(capacity); } - *dptr = header; - return data; + return { data, header }; } -QPair<QArrayData *, void *> +// Generic size and alignment allocation function +void *QArrayData::allocate(QArrayData **dptr, qsizetype objectSize, qsizetype alignment, + qsizetype capacity, AllocationOption option) noexcept +{ + Q_ASSERT(dptr); + // Alignment is a power of two + Q_ASSERT(alignment >= qsizetype(alignof(QArrayData)) + && !(alignment & (alignment - 1))); + + auto r = allocateHelper(objectSize, alignment, capacity, option); + *dptr = r.header; + return r.data; +} + +// Fixed size and alignment allocation functions +void *QArrayData::allocate1(QArrayData **dptr, qsizetype capacity, AllocationOption option) noexcept +{ + Q_ASSERT(dptr); + + auto r = allocateHelper(1, alignof(AlignedQArrayData), capacity, option); + *dptr = r.header; + return r.data; +} + +void *QArrayData::allocate2(QArrayData **dptr, qsizetype capacity, AllocationOption option) noexcept +{ + Q_ASSERT(dptr); + + auto r = allocateHelper(2, alignof(AlignedQArrayData), capacity, option); + *dptr = r.header; + return r.data; +} + +std::pair<QArrayData *, void *> QArrayData::reallocateUnaligned(QArrayData *data, void *dataPointer, qsizetype objectSize, qsizetype capacity, AllocationOption option) noexcept { Q_ASSERT(!data || !data->isShared()); const qsizetype headerSize = sizeof(AlignedQArrayData); - qsizetype allocSize = calculateBlockSize(capacity, objectSize, headerSize, option); + auto r = calculateBlockSize(capacity, objectSize, headerSize, option); + qsizetype allocSize = r.size; + capacity = r.elementCount; if (Q_UNLIKELY(allocSize < 0)) - return qMakePair<QArrayData *, void *>(nullptr, nullptr); + return {}; const qptrdiff offset = dataPointer ? reinterpret_cast<char *>(dataPointer) - reinterpret_cast<char *>(data) @@ -217,10 +241,6 @@ QArrayData::reallocateUnaligned(QArrayData *data, void *dataPointer, Q_ASSERT(offset > 0); Q_ASSERT(offset <= allocSize); // equals when all free space is at the beginning - allocSize = reserveExtraBytes(allocSize); - if (Q_UNLIKELY(allocSize < 0)) // handle overflow. cannot reallocate reliably - return qMakePair(data, dataPointer); - QArrayData *header = static_cast<QArrayData *>(::realloc(data, size_t(allocSize))); if (header) { header->alloc = capacity; @@ -228,7 +248,7 @@ QArrayData::reallocateUnaligned(QArrayData *data, void *dataPointer, } else { dataPointer = nullptr; } - return qMakePair(static_cast<QArrayData *>(header), dataPointer); + return {header, dataPointer}; } void QArrayData::deallocate(QArrayData *data, qsizetype objectSize, diff --git a/src/corelib/tools/qarraydata.h b/src/corelib/tools/qarraydata.h index 766596fe18..da83fc1a21 100644 --- a/src/corelib/tools/qarraydata.h +++ b/src/corelib/tools/qarraydata.h @@ -7,10 +7,18 @@ #include <QtCore/qpair.h> #include <QtCore/qatomic.h> +#include <QtCore/qflags.h> +#include <QtCore/qcontainerfwd.h> #include <string.h> QT_BEGIN_NAMESPACE +#if __has_cpp_attribute(gnu::malloc) +# define Q_DECL_MALLOCLIKE [[nodiscard, gnu::malloc]] +#else +# define Q_DECL_MALLOCLIKE [[nodiscard]] +#endif + template <class T> struct QTypedArrayData; struct QArrayData @@ -66,7 +74,7 @@ struct QArrayData // Returns true if a detach is necessary before modifying the data // This method is intentionally not const: if you want to know whether // detaching is necessary, you should be in a non-const function already - bool needsDetach() const noexcept + bool needsDetach() noexcept { return ref_.loadRelaxed() > 1; } @@ -78,13 +86,17 @@ struct QArrayData return newSize; } - [[nodiscard]] -#if defined(Q_CC_GNU) - __attribute__((__malloc__)) -#endif + Q_DECL_MALLOCLIKE static Q_CORE_EXPORT void *allocate(QArrayData **pdata, qsizetype objectSize, qsizetype alignment, qsizetype capacity, AllocationOption option = QArrayData::KeepSize) noexcept; - [[nodiscard]] static Q_CORE_EXPORT QPair<QArrayData *, void *> reallocateUnaligned(QArrayData *data, void *dataPointer, + Q_DECL_MALLOCLIKE + static Q_CORE_EXPORT void *allocate1(QArrayData **pdata, qsizetype capacity, + AllocationOption option = QArrayData::KeepSize) noexcept; + Q_DECL_MALLOCLIKE + static Q_CORE_EXPORT void *allocate2(QArrayData **pdata, qsizetype capacity, + AllocationOption option = QArrayData::KeepSize) noexcept; + + [[nodiscard]] static Q_CORE_EXPORT std::pair<QArrayData *, void *> reallocateUnaligned(QArrayData *data, void *dataPointer, qsizetype objectSize, qsizetype newCapacity, AllocationOption option) noexcept; static Q_CORE_EXPORT void deallocate(QArrayData *data, qsizetype objectSize, qsizetype alignment) noexcept; @@ -92,30 +104,56 @@ struct QArrayData Q_DECLARE_OPERATORS_FOR_FLAGS(QArrayData::ArrayOptions) +namespace QtPrivate { +// QArrayData with strictest alignment requirements supported by malloc() +#if defined(Q_PROCESSOR_X86_32) && defined(Q_CC_GNU) +// GCC's definition is incorrect since GCC 8 (commit r240248 in SVN; commit +// 63012d9a57edc950c5f30242d1e19318b5708060 in Git). This is applied to all +// GCC-like compilers in case they decide to follow GCC's lead in being wrong. +constexpr size_t MaxPrimitiveAlignment = 2 * sizeof(void *); +#else +constexpr size_t MaxPrimitiveAlignment = alignof(std::max_align_t); +#endif + +struct alignas(MaxPrimitiveAlignment) AlignedQArrayData : QArrayData +{ +}; +} + template <class T> struct QTypedArrayData : QArrayData { - struct AlignmentDummy { QArrayData header; T data; }; + struct AlignmentDummy { QtPrivate::AlignedQArrayData header; T data; }; - [[nodiscard]] static QPair<QTypedArrayData *, T *> allocate(qsizetype capacity, AllocationOption option = QArrayData::KeepSize) + [[nodiscard]] static std::pair<QTypedArrayData *, T *> allocate(qsizetype capacity, AllocationOption option = QArrayData::KeepSize) { static_assert(sizeof(QTypedArrayData) == sizeof(QArrayData)); QArrayData *d; - void *result = QArrayData::allocate(&d, sizeof(T), alignof(AlignmentDummy), capacity, option); + void *result; + if constexpr (sizeof(T) == 1) { + // necessarily, alignof(T) == 1 + result = allocate1(&d, capacity, option); + } else if constexpr (sizeof(T) == 2) { + // alignof(T) may be 1, but that makes no difference + result = allocate2(&d, capacity, option); + } else { + result = QArrayData::allocate(&d, sizeof(T), alignof(AlignmentDummy), capacity, option); + } #if __has_builtin(__builtin_assume_aligned) + // and yet we do offer results that have stricter alignment result = __builtin_assume_aligned(result, Q_ALIGNOF(AlignmentDummy)); #endif - return qMakePair(static_cast<QTypedArrayData *>(d), static_cast<T *>(result)); + return {static_cast<QTypedArrayData *>(d), static_cast<T *>(result)}; } - static QPair<QTypedArrayData *, T *> + static std::pair<QTypedArrayData *, T *> reallocateUnaligned(QTypedArrayData *data, T *dataPointer, qsizetype capacity, AllocationOption option) { static_assert(sizeof(QTypedArrayData) == sizeof(QArrayData)); - QPair<QArrayData *, void *> pair = + std::pair<QArrayData *, void *> pair = QArrayData::reallocateUnaligned(data, dataPointer, sizeof(T), capacity, option); - return qMakePair(static_cast<QTypedArrayData *>(pair.first), static_cast<T *>(pair.second)); + return {static_cast<QTypedArrayData *>(pair.first), static_cast<T *>(pair.second)}; } static void deallocate(QArrayData *data) noexcept @@ -132,6 +170,12 @@ struct QTypedArrayData (quintptr(data) + sizeof(QArrayData) + alignment - 1) & ~(alignment - 1)); return static_cast<T *>(start); } + + constexpr static qsizetype max_size() noexcept + { + // -1 to deal with the pointer one-past-the-end + return (QtPrivate::MaxAllocSize - sizeof(QtPrivate::AlignedQArrayData) - 1) / sizeof(T); + } }; namespace QtPrivate { @@ -172,6 +216,8 @@ struct Q_CORE_EXPORT QContainerImplHelper }; } +#undef Q_DECL_MALLOCLIKE + QT_END_NAMESPACE #endif // include guard diff --git a/src/corelib/tools/qarraydataops.h b/src/corelib/tools/qarraydataops.h index bd8ead0a80..c3e9821e81 100644 --- a/src/corelib/tools/qarraydataops.h +++ b/src/corelib/tools/qarraydataops.h @@ -7,6 +7,7 @@ #include <QtCore/qarraydata.h> #include <QtCore/qcontainertools_impl.h> +#include <QtCore/qnamespace.h> #include <memory> #include <new> @@ -236,7 +237,7 @@ public: if (it == end) return result; - QPodArrayOps<T> other{ Data::allocate(this->size), this->size }; + QPodArrayOps<T> other(this->size); Q_CHECK_PTR(other.data()); auto dest = other.begin(); // std::uninitialized_copy will fallback to ::memcpy/memmove() @@ -960,6 +961,24 @@ public: // b might be updated so use [b, n) this->copyAppend(b, b + n); } + + void appendUninitialized(qsizetype newSize) + { + Q_ASSERT(this->isMutable()); + Q_ASSERT(!this->isShared()); + Q_ASSERT(newSize > this->size); + Q_ASSERT(newSize - this->size <= this->freeSpaceAtEnd()); + + T *const b = this->begin(); + do { + auto ptr = b + this->size; + + if constexpr (std::is_constructible_v<T, Qt::Initialization>) + new (ptr) T(Qt::Uninitialized); + else + new (ptr) T; // not T() -- default-construct + } while (++this->size != newSize); + } }; } // namespace QtPrivate diff --git a/src/corelib/tools/qarraydatapointer.h b/src/corelib/tools/qarraydatapointer.h index 466cdecfc7..6657d40cf9 100644 --- a/src/corelib/tools/qarraydatapointer.h +++ b/src/corelib/tools/qarraydatapointer.h @@ -27,27 +27,39 @@ public: typedef typename std::conditional<pass_parameter_by_value, T, const T &>::type parameter_type; + Q_NODISCARD_CTOR constexpr QArrayDataPointer() noexcept : d(nullptr), ptr(nullptr), size(0) { } + Q_NODISCARD_CTOR QArrayDataPointer(const QArrayDataPointer &other) noexcept : d(other.d), ptr(other.ptr), size(other.size) { ref(); } + Q_NODISCARD_CTOR constexpr QArrayDataPointer(Data *header, T *adata, qsizetype n = 0) noexcept : d(header), ptr(adata), size(n) { } - explicit QArrayDataPointer(QPair<QTypedArrayData<T> *, T *> adata, qsizetype n = 0) noexcept + Q_NODISCARD_CTOR + explicit QArrayDataPointer(std::pair<QTypedArrayData<T> *, T *> adata, qsizetype n = 0) noexcept : d(adata.first), ptr(adata.second), size(n) { } + Q_NODISCARD_CTOR explicit + QArrayDataPointer(qsizetype alloc, qsizetype n = 0, + QArrayData::AllocationOption option = QArrayData::KeepSize) + : QArrayDataPointer(Data::allocate(alloc, option), n) + { + } + + Q_NODISCARD_CTOR static QArrayDataPointer fromRawData(const T *rawData, qsizetype length) noexcept { Q_ASSERT(rawData || !length); @@ -61,12 +73,12 @@ public: return *this; } + Q_NODISCARD_CTOR QArrayDataPointer(QArrayDataPointer &&other) noexcept - : d(other.d), ptr(other.ptr), size(other.size) + : d(std::exchange(other.d, nullptr)), + ptr(std::exchange(other.ptr, nullptr)), + size(std::exchange(other.size, 0)) { - other.d = nullptr; - other.ptr = nullptr; - other.size = 0; } QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QArrayDataPointer) @@ -315,15 +327,16 @@ public: constexpr bool IsFwdIt = std::is_convertible_v< typename std::iterator_traits<InputIterator>::iterator_category, std::forward_iterator_tag>; + constexpr bool IsIdentity = std::is_same_v<Projection, q20::identity>; if constexpr (IsFwdIt) { const qsizetype n = std::distance(first, last); if (needsDetach() || n > constAllocatedCapacity()) { - QArrayDataPointer allocated(Data::allocate(detachCapacity(n))); + QArrayDataPointer allocated(detachCapacity(n)); swap(allocated); } } else if (needsDetach()) { - QArrayDataPointer allocated(Data::allocate(allocatedCapacity())); + QArrayDataPointer allocated(allocatedCapacity()); swap(allocated); // We don't want to copy data that we know we'll overwrite } @@ -332,7 +345,7 @@ public: const auto capacityBegin = begin() - offset; const auto prependBufferEnd = begin(); - if constexpr (!std::is_nothrow_constructible_v<T, decltype(proj(*first))>) { + if constexpr (!std::is_nothrow_constructible_v<T, decltype(std::invoke(proj, *first))>) { // If construction can throw, and we have freeSpaceAtBegin(), // it's easiest to just clear the container and start fresh. // The alternative would be to keep track of two active, disjoint ranges. @@ -364,7 +377,8 @@ public: size = dst - begin(); return; } - q20::construct_at(dst, proj(*first)); // construct element in prepend buffer + // construct element in prepend buffer + q20::construct_at(dst, std::invoke(proj, *first)); ++dst; ++first; } @@ -376,23 +390,48 @@ public: break; } if (dst == dend) { // ran out of existing elements to overwrite - if constexpr (IsFwdIt) { + if constexpr (IsFwdIt && IsIdentity) { dst = std::uninitialized_copy(first, last, dst); break; + } else if constexpr (IsFwdIt && !IsIdentity + && std::is_nothrow_constructible_v<T, decltype(std::invoke(proj, *first))>) { + for (; first != last; ++dst, ++first) // uninitialized_copy with projection + q20::construct_at(dst, std::invoke(proj, *first)); + break; } else { do { - (*this)->emplace(size, proj(*first)); + (*this)->emplace(size, std::invoke(proj, *first)); } while (++first != last); return; // size() is already correct (and dst invalidated)! } } - *dst = proj(*first); // overwrite existing element + *dst = std::invoke(proj, *first); // overwrite existing element ++dst; ++first; } size = dst - begin(); } + QArrayDataPointer sliced(qsizetype pos, qsizetype n) const & + { + QArrayDataPointer result(n); + std::uninitialized_copy_n(begin() + pos, n, result.begin()); + result.size = n; + return result; + } + + QArrayDataPointer sliced(qsizetype pos, qsizetype n) && + { + if (needsDetach()) + return sliced(pos, n); + T *newBeginning = begin() + pos; + std::destroy(begin(), newBeginning); + std::destroy(newBeginning + n, end()); + setBegin(newBeginning); + size = n; + return std::move(*this); + } + // forwards from QArrayData qsizetype allocatedCapacity() noexcept { return d ? d->allocatedCapacity() : 0; } qsizetype constAllocatedCapacity() const noexcept { return d ? d->constAllocatedCapacity() : 0; } diff --git a/src/corelib/tools/qatomicscopedvaluerollback_p.h b/src/corelib/tools/qatomicscopedvaluerollback.h index 147156d585..8f653acba5 100644 --- a/src/corelib/tools/qatomicscopedvaluerollback_p.h +++ b/src/corelib/tools/qatomicscopedvaluerollback.h @@ -1,29 +1,21 @@ // Copyright (C) 2022 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 QATOMICSCOPEDVALUEROLLBACK_P_H -#define QATOMICSCOPEDVALUEROLLBACK_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header -// file may change from version to version without notice, or even be removed. -// -// We mean it. -// - -#include <QtCore/qglobal.h> +#ifndef QATOMICSCOPEDVALUEROLLBACK_H +#define QATOMICSCOPEDVALUEROLLBACK_H + +#include <QtCore/qassert.h> #include <QtCore/qatomic.h> +#include <QtCore/qcompilerdetection.h> +#include <QtCore/qtclasshelpermacros.h> +#include <QtCore/qtconfigmacros.h> #include <atomic> QT_BEGIN_NAMESPACE template <typename T> -class [[nodiscard]] QAtomicScopedValueRollback +class QAtomicScopedValueRollback { std::atomic<T> &m_atomic; T m_value; @@ -31,7 +23,7 @@ class [[nodiscard]] QAtomicScopedValueRollback Q_DISABLE_COPY_MOVE(QAtomicScopedValueRollback) - constexpr std::memory_order store_part(std::memory_order mo) noexcept + static constexpr std::memory_order store_part(std::memory_order mo) noexcept { switch (mo) { case std::memory_order_relaxed: @@ -41,7 +33,25 @@ class [[nodiscard]] QAtomicScopedValueRollback case std::memory_order_acq_rel: return std::memory_order_release; case std::memory_order_seq_cst: return std::memory_order_seq_cst; } - // GCC 8.x does not tread __builtin_unreachable() as constexpr + // GCC 8.x does not treat __builtin_unreachable() as constexpr +#if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) + // NOLINTNEXTLINE(qt-use-unreachable-return): Triggers on Clang, breaking GCC 8 + Q_UNREACHABLE(); +#endif + return std::memory_order_seq_cst; + } + + static constexpr std::memory_order load_part(std::memory_order mo) noexcept + { + switch (mo) { + case std::memory_order_relaxed: + case std::memory_order_release: return std::memory_order_relaxed; + case std::memory_order_consume: return std::memory_order_consume; + case std::memory_order_acquire: + case std::memory_order_acq_rel: return std::memory_order_acquire; + case std::memory_order_seq_cst: return std::memory_order_seq_cst; + } + // GCC 8.x does not treat __builtin_unreachable() as constexpr #if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) // NOLINTNEXTLINE(qt-use-unreachable-return): Triggers on Clang, breaking GCC 8 Q_UNREACHABLE(); @@ -52,11 +62,13 @@ public: // // std::atomic: // + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(std::atomic<T> &var, std::memory_order mo = std::memory_order_seq_cst) - : m_atomic(var), m_value(var.load(mo)), m_mo(mo) {} + : m_atomic(var), m_value(var.load(load_part(mo))), m_mo(mo) {} + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(std::atomic<T> &var, T value, std::memory_order mo = std::memory_order_seq_cst) @@ -65,11 +77,13 @@ public: // // Q(Basic)AtomicInteger: // + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(QBasicAtomicInteger<T> &var, std::memory_order mo = std::memory_order_seq_cst) : QAtomicScopedValueRollback(var._q_value, mo) {} + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(QBasicAtomicInteger<T> &var, T value, std::memory_order mo = std::memory_order_seq_cst) @@ -78,30 +92,36 @@ public: // // Q(Basic)AtomicPointer: // + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(QBasicAtomicPointer<std::remove_pointer_t<T>> &var, std::memory_order mo = std::memory_order_seq_cst) : QAtomicScopedValueRollback(var._q_value, mo) {} + Q_NODISCARD_CTOR explicit constexpr QAtomicScopedValueRollback(QBasicAtomicPointer<std::remove_pointer_t<T>> &var, T value, std::memory_order mo = std::memory_order_seq_cst) : QAtomicScopedValueRollback(var._q_value, value, mo) {} -#if __cpp_constexpr >= 201907L - constexpr -#endif ~QAtomicScopedValueRollback() { m_atomic.store(m_value, store_part(m_mo)); } - constexpr void commit() + void commit() { - m_value = m_atomic.load(m_mo); + m_value = m_atomic.load(load_part(m_mo)); } }; +template <typename T> +QAtomicScopedValueRollback(QBasicAtomicPointer<T> &) + -> QAtomicScopedValueRollback<T*>; +template <typename T> +QAtomicScopedValueRollback(QBasicAtomicPointer<T> &, std::memory_order) + -> QAtomicScopedValueRollback<T*>; + QT_END_NAMESPACE -#endif // QATOMICASCOPEDVALUEROLLBACK_P_H +#endif // QATOMICASCOPEDVALUEROLLBACK_H diff --git a/src/corelib/tools/qatomicscopedvaluerollback.qdoc b/src/corelib/tools/qatomicscopedvaluerollback.qdoc new file mode 100644 index 0000000000..8c8161cb35 --- /dev/null +++ b/src/corelib/tools/qatomicscopedvaluerollback.qdoc @@ -0,0 +1,123 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +QT_BEGIN_NAMESPACE + +/*! + \class QAtomicScopedValueRollback + \inmodule QtCore + \brief Provides a QScopedValueRollback for atomic variables. + \ingroup misc + \ingroup tools + \since 6.7 + + The QAtomicScopedValueRollback class resets an atomic variable to its + prior value on destruction. It can be used to revert state when an + exception is thrown without the need to write try-catch blocks. + + It can also be used to manage variables that are temporarily set, such as + reentrancy guards. By using this class, the variable will be reset whether the + function is exited normally, exited early by a return statement, or exited by + an exception. + + The class works on std::atomic and the Qt atomic classes: QBasicAtomicInteger, + \l QAtomicInteger, \l QAtomicInt, QBasicAtomicPointer and \l QAtomicPointer. + + \target Memory Order + The memory accesses to the atomic variable \a var are specified using the value + of \c mo. The memory order follows this mapping: + \br + \list + \li When writing to the atomic variable: + \list + \li An acquire ordering performs a relaxed operation instead. + \li A hybrid acquire-release ordering performs a release operation instead. + \endlist + \li When reading from the atomic variable: + \list + \li A release ordering performs a relaxed operation instead. + \li A consume ordering performs a consume operation. + \li A hybrid acquire-release ordering performs an acquire operation instead. + \endlist + \endlist + \br + Otherwise, the default memory order is sequential consistent ordering. + + \note You should never name the template arguments explicitly, but exclusively + use Class Template Argument Deduction (CTAD) and let the compiler pick the + template argument. + + \note There is a chance that other threads modify the variable too, which means + you may lose updates performed by other threads between the call to the + QAtomicScopedValueRollback constructor and commit() or between commit() and the + destructor. + + \sa QScopedValueRollback +*/ + +/*! + \fn template <typename T> QAtomicScopedValueRollback<T>::QAtomicScopedValueRollback(std::atomic<T> &var, std::memory_order mo = std::memory_order_seq_cst) + \fn template <typename T> QAtomicScopedValueRollback<T>::QAtomicScopedValueRollback(QBasicAtomicInteger<T> &var, std::memory_order mo = std::memory_order_seq_cst) + \fn template <typename T> QAtomicScopedValueRollback<T>::QAtomicScopedValueRollback(QBasicAtomicPointer<std::remove_pointer_t<T>> &var, std::memory_order mo = std::memory_order_seq_cst) + + Records the value of \a var in order to restore it on destruction. + + This is equivalent to: + \code + T old_value = var.load(mo); + // And in the destructor: var.store(old_value, mo); + \endcode + The \c{mo} adjustment for the load is described in the \l {Memory Order} section. +*/ + +/*! + \fn template <typename T> QAtomicScopedValueRollback<T>::QAtomicScopedValueRollback(std::atomic<T> &var, T value, std::memory_order mo = std::memory_order_seq_cst) + \fn template <typename T> QAtomicScopedValueRollback<T>::QAtomicScopedValueRollback(QBasicAtomicInteger<T> &var, T value, std::memory_order mo = std::memory_order_seq_cst) + \fn template <typename T> QAtomicScopedValueRollback<T>::QAtomicScopedValueRollback(QBasicAtomicPointer<std::remove_pointer_t<T>> &var, T value, std::memory_order mo = std::memory_order_seq_cst) + + Assigns \a value to \a var and stores the prior value of \a var internally for + reverting on destruction. + + This is equivalent to: + \code + T old_value = var.exchange(new_value, mo); + // And in the destructor: var.store(old_value, mo); + \endcode +*/ + +/*! + \fn template <typename T> QAtomicScopedValueRollback<T>::~QAtomicScopedValueRollback() + + Restores the stored value that was current at construction time, or + at the last call to commit(), to the managed variable. + + This is equivalent to: + \code + // In the constructor: T old_value = var.load(mo); + // or: T old_value = exchange(new_value, mo); + var.store(old_value, mo); + \endcode + Where \c{mo} is the same as the one initially passed to the constructor. + See \l{Memory Order} for the meaning of \c{mo}. +*/ + +/*! + \fn template <typename T> void QAtomicScopedValueRollback<T>::commit() + + Updates the stored value to the managed variable's current value, loaded + with the same memory order as on construction. + + This updated value will be restored on destruction, instead of the original + prior value. + + This is equivalent to: + \code + // Given constructor: T old_value = var.load(mo); + old_value = var.load(mo); // referesh it + // And, in the destructor: var.store(old_value, mo); + \endcode + Where \c{mo} is the same as the one initially passed to the constructor. + See \l{Memory Order} for the meaning of \c{mo}. +*/ + +QT_END_NAMESPACE diff --git a/src/corelib/tools/qbitarray.cpp b/src/corelib/tools/qbitarray.cpp index 5c48b3b608..e4276d383d 100644 --- a/src/corelib/tools/qbitarray.cpp +++ b/src/corelib/tools/qbitarray.cpp @@ -7,6 +7,9 @@ #include <qdatastream.h> #include <qdebug.h> #include <qendian.h> + +#include <limits> + #include <string.h> QT_BEGIN_NAMESPACE @@ -20,6 +23,8 @@ QT_BEGIN_NAMESPACE \ingroup shared \reentrant + \compares equality + A QBitArray is an array that gives access to individual bits and provides operators (\l{operator&()}{AND}, \l{operator|()}{OR}, \l{operator^()}{XOR}, and \l{operator~()}{NOT}) that work on @@ -74,6 +79,7 @@ QT_BEGIN_NAMESPACE \sa QByteArray, QList */ +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) /*! \fn QBitArray::QBitArray(QBitArray &&other) @@ -82,6 +88,7 @@ QT_BEGIN_NAMESPACE \since 5.2 */ +#endif /*! \fn QBitArray::QBitArray() @@ -104,22 +111,39 @@ QT_BEGIN_NAMESPACE * inline qsizetype size() const { return (d.size() << 3) - *d.constData(); } */ +static constexpr qsizetype storage_size(qsizetype size) +{ + // avoid overflow when adding 7, by doing the arithmetic in unsigned space: + return qsizetype((size_t(size) + 7) / 8); +} + +static constexpr qsizetype allocation_size(qsizetype size) +{ + return size <= 0 ? 0 : storage_size(size) + 1; +} + +static void adjust_head_and_tail(char *data, qsizetype storageSize, qsizetype logicalSize) +{ + quint8 *c = reinterpret_cast<quint8 *>(data); + // store the difference between storage and logical size in d[0]: + *c = quint8(size_t(storageSize) * 8 - logicalSize); + // reset unallocated bits to 0: + if (logicalSize & 7) + *(c + 1 + logicalSize / 8) &= (1 << (logicalSize & 7)) - 1; +} + /*! Constructs a bit array containing \a size bits. The bits are initialized with \a value, which defaults to false (0). */ QBitArray::QBitArray(qsizetype size, bool value) - : d(size <= 0 ? 0 : 1 + (size + 7) / 8, Qt::Uninitialized) + : d(allocation_size(size), value ? 0xFF : 0x00) { Q_ASSERT_X(size >= 0, "QBitArray::QBitArray", "Size must be greater than or equal to 0."); if (size <= 0) return; - uchar *c = reinterpret_cast<uchar *>(d.data()); - memset(c + 1, value ? 0xff : 0, d.size() - 1); - *c = d.size() * 8 - size; - if (value && size && size & 7) - *(c + 1 + size / 8) &= (1 << (size & 7)) - 1; + adjust_head_and_tail(d.data(), d.size(), size); } /*! \fn qsizetype QBitArray::size() const @@ -183,17 +207,12 @@ qsizetype QBitArray::count(bool on) const */ void QBitArray::resize(qsizetype size) { - if (!size) { + Q_ASSERT_X(size >= 0, "QBitArray::resize", "Size must be greater than or equal to 0."); + if (size <= 0) { d.resize(0); } else { - qsizetype s = d.size(); - d.resize(1 + (size + 7) / 8); - uchar *c = reinterpret_cast<uchar *>(d.data()); - if (size > (s << 3)) - memset(c + s, 0, d.size() - s); - else if (size & 7) - *(c + 1 + size / 8) &= (1 << (size & 7)) - 1; - *c = d.size() * 8 - size; + d.resize(allocation_size(size), 0x00); + adjust_head_and_tail(d.data(), d.size(), size); } } @@ -289,20 +308,15 @@ void QBitArray::fill(bool value, qsizetype begin, qsizetype end) */ QBitArray QBitArray::fromBits(const char *data, qsizetype size) { + Q_ASSERT_X(size >= 0, "QBitArray::fromBits", "Size must be greater than or equal to 0."); QBitArray result; - if (size == 0) + if (size <= 0) return result; - qsizetype nbytes = (size + 7) / 8; - - result.d = QByteArray(nbytes + 1, Qt::Uninitialized); - char *bits = result.d.data(); - memcpy(bits + 1, data, nbytes); - - // clear any unused bits from the last byte - if (size & 7) - bits[nbytes] &= 0xffU >> (8 - (size & 7)); - *bits = result.d.size() * 8 - size; + auto &d = result.d; + d.resize(allocation_size(size)); + memcpy(d.data() + 1, data, d.size() - 1); + adjust_head_and_tail(d.data(), d.size(), size); return result; } @@ -453,6 +467,7 @@ quint32 QBitArray::toUInt32(QSysInfo::Endian endianness, bool *ok) const noexcep \overload */ +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) /*! \fn QBitArray::QBitArray(const QBitArray &other) noexcept Constructs a copy of \a other. @@ -477,6 +492,7 @@ quint32 QBitArray::toUInt32(QSysInfo::Endian endianness, bool *ok) const noexcep Moves \a other to this bit array and returns a reference to this bit array. */ +#endif // Qt 6 /*! \fn void QBitArray::swap(QBitArray &other) \since 4.8 @@ -485,23 +501,145 @@ quint32 QBitArray::toUInt32(QSysInfo::Endian endianness, bool *ok) const noexcep fast and never fails. */ -/*! \fn bool QBitArray::operator==(const QBitArray &other) const +/*! \fn bool QBitArray::operator==(const QBitArray &lhs, const QBitArray &rhs) - Returns \c true if \a other is equal to this bit array; otherwise + Returns \c true if \a lhs is equal to \a rhs bit array; otherwise returns \c false. \sa operator!=() */ -/*! \fn bool QBitArray::operator!=(const QBitArray &other) const +/*! \fn bool QBitArray::operator!=(const QBitArray &lhs, const QBitArray &rhs) - Returns \c true if \a other is not equal to this bit array; + Returns \c true if \a lhs is not equal to \a rhs bit array; otherwise returns \c false. \sa operator==() */ +// Returns a new QBitArray that has the same size as the bigger of \a a1 and +// \a a2, but whose contents are uninitialized. +static QBitArray sizedForOverwrite(const QBitArray &a1, const QBitArray &a2) +{ + QBitArray result; + const QByteArrayData &d1 = a1.data_ptr(); + const QByteArrayData &d2 = a2.data_ptr(); + qsizetype n1 = d1.size; + qsizetype n2 = d2.size; + qsizetype n = qMax(n1, n2); + + QByteArrayData bytes(n, n); + + // initialize the count of bits in the last byte (see construction note) + if (n1 > n2) + *bytes.ptr = *d1.ptr; + else if (n2 > n1) + *bytes.ptr = *d2.ptr; + else if (n1) // n1 == n2 + *bytes.ptr = qMin(*d1.ptr, *d2.ptr); + + result.data_ptr() = std::move(bytes); + return result; +} + +template <typename BitwiseOp> static Q_NEVER_INLINE +QBitArray &performBitwiseOperationHelper(QBitArray &out, const QBitArray &a1, + const QBitArray &a2, BitwiseOp op) +{ + const QByteArrayData &d1 = a1.data_ptr(); + const QByteArrayData &d2 = a2.data_ptr(); + + // Sizes in bytes (including the initial bit difference counter) + qsizetype n1 = d1.size; + qsizetype n2 = d2.size; + Q_ASSERT(out.data_ptr().size == qMax(n1, n2)); + Q_ASSERT(out.data_ptr().size == 0 || !out.data_ptr().needsDetach()); + + // Bypass QByteArray's emptiness verification; we won't dereference + // these pointers if their size is zero. + auto dst = reinterpret_cast<uchar *>(out.data_ptr().data()); + auto p1 = reinterpret_cast<const uchar *>(d1.data()); + auto p2 = reinterpret_cast<const uchar *>(d2.data()); + + // Main: perform the operation in the range where both arrays have data + if (n1 < n2) { + std::swap(n1, n2); + std::swap(p1, p2); + } + for (qsizetype i = 1; i < n2; ++i) + dst[i] = op(p1[i], p2[i]); + + // Tail: operate as if both arrays had the same data by padding zeroes to + // the end of the shorter of the two (for std::bit_or and std::bit_xor, this is + // a memmove; for std::bit_and, it's memset to 0). + for (qsizetype i = qMax(n2, qsizetype(1)); i < n1; ++i) + dst[i] = op(p1[i], uchar(0)); + + return out; +} + +template <typename BitwiseOp> static Q_NEVER_INLINE +QBitArray &performBitwiseOperationInCopy(QBitArray &self, const QBitArray &other, BitwiseOp op) +{ + QBitArray tmp(std::move(self)); + self = sizedForOverwrite(tmp, other); + return performBitwiseOperationHelper(self, tmp, other, op); +} + +template <typename BitwiseOp> static Q_NEVER_INLINE +QBitArray &performBitwiseOperationInPlace(QBitArray &self, const QBitArray &other, BitwiseOp op) +{ + if (self.size() < other.size()) + self.resize(other.size()); + return performBitwiseOperationHelper(self, self, other, op); +} + +template <typename BitwiseOp> static +QBitArray &performBitwiseOperation(QBitArray &self, const QBitArray &other, BitwiseOp op) +{ + if (self.data_ptr().needsDetach()) + return performBitwiseOperationInCopy(self, other, op); + return performBitwiseOperationInPlace(self, other, op); +} + +// SCARY helper +enum { InCopy, InPlace }; +static auto prepareForBitwiseOperation(QBitArray &self, QBitArray &other) +{ + QByteArrayData &d1 = self.data_ptr(); + QByteArrayData &d2 = other.data_ptr(); + bool detached1 = !d1.needsDetach(); + bool detached2 = !d2.needsDetach(); + if (!detached1 && !detached2) + return InCopy; + + // at least one of the two is detached, we'll reuse its buffer + bool swap = false; + if (detached1 && detached2) { + // both are detached, so choose the larger of the two + swap = d1.allocatedCapacity() < d2.allocatedCapacity(); + } else if (detached2) { + // we can re-use other's buffer but not self's, so swap the two + swap = true; + } + if (swap) + self.swap(other); + return InPlace; +} + +template <typename BitwiseOp> static +QBitArray &performBitwiseOperation(QBitArray &self, QBitArray &other, BitwiseOp op) +{ + auto choice = prepareForBitwiseOperation(self, other); + if (choice == InCopy) + return performBitwiseOperationInCopy(self, other, std::move(op)); + return performBitwiseOperationInPlace(self, other, std::move(op)); +} + /*! + \fn QBitArray &QBitArray::operator&=(const QBitArray &other) + \fn QBitArray &QBitArray::operator&=(QBitArray &&other) + Performs the AND operation between all bits in this bit array and \a other. Assigns the result to this bit array, and returns a reference to it. @@ -516,21 +654,20 @@ quint32 QBitArray::toUInt32(QSysInfo::Endian endianness, bool *ok) const noexcep \sa operator&(), operator|=(), operator^=(), operator~() */ +QBitArray &QBitArray::operator&=(QBitArray &&other) +{ + return performBitwiseOperation(*this, other, std::bit_and<uchar>()); +} + QBitArray &QBitArray::operator&=(const QBitArray &other) { - resize(qMax(size(), other.size())); - uchar *a1 = reinterpret_cast<uchar *>(d.data()) + 1; - const uchar *a2 = reinterpret_cast<const uchar *>(other.d.constData()) + 1; - qsizetype n = other.d.size() - 1; - qsizetype p = d.size() - 1 - n; - while (n-- > 0) - *a1++ &= *a2++; - while (p-- > 0) - *a1++ = 0; - return *this; + return performBitwiseOperation(*this, other, std::bit_and<uchar>()); } /*! + \fn QBitArray &QBitArray::operator|=(const QBitArray &other) + \fn QBitArray &QBitArray::operator|=(QBitArray &&other) + Performs the OR operation between all bits in this bit array and \a other. Assigns the result to this bit array, and returns a reference to it. @@ -545,18 +682,20 @@ QBitArray &QBitArray::operator&=(const QBitArray &other) \sa operator|(), operator&=(), operator^=(), operator~() */ +QBitArray &QBitArray::operator|=(QBitArray &&other) +{ + return performBitwiseOperation(*this, other, std::bit_or<uchar>()); +} + QBitArray &QBitArray::operator|=(const QBitArray &other) { - resize(qMax(size(), other.size())); - uchar *a1 = reinterpret_cast<uchar *>(d.data()) + 1; - const uchar *a2 = reinterpret_cast<const uchar *>(other.d.constData()) + 1; - qsizetype n = other.d.size() - 1; - while (n-- > 0) - *a1++ |= *a2++; - return *this; + return performBitwiseOperation(*this, other, std::bit_or<uchar>()); } /*! + \fn QBitArray &QBitArray::operator^=(const QBitArray &other) + \fn QBitArray &QBitArray::operator^=(QBitArray &&other) + Performs the XOR operation between all bits in this bit array and \a other. Assigns the result to this bit array, and returns a reference to it. @@ -571,20 +710,20 @@ QBitArray &QBitArray::operator|=(const QBitArray &other) \sa operator^(), operator&=(), operator|=(), operator~() */ +QBitArray &QBitArray::operator^=(QBitArray &&other) +{ + return performBitwiseOperation(*this, other, std::bit_xor<uchar>()); +} + QBitArray &QBitArray::operator^=(const QBitArray &other) { - resize(qMax(size(), other.size())); - uchar *a1 = reinterpret_cast<uchar *>(d.data()) + 1; - const uchar *a2 = reinterpret_cast<const uchar *>(other.d.constData()) + 1; - qsizetype n = other.d.size() - 1; - while (n-- > 0) - *a1++ ^= *a2++; - return *this; + return performBitwiseOperation(*this, other, std::bit_xor<uchar>()); } /*! - Returns a bit array that contains the inverted bits of this bit - array. + \fn QBitArray QBitArray::operator~(QBitArray a) + Returns a bit array that contains the inverted bits of the bit + array \a a. Example: \snippet code/src_corelib_tools_qbitarray.cpp 11 @@ -592,24 +731,42 @@ QBitArray &QBitArray::operator^=(const QBitArray &other) \sa operator&(), operator|(), operator^() */ -QBitArray QBitArray::operator~() const +Q_NEVER_INLINE QBitArray QBitArray::inverted_inplace() && { - qsizetype sz = size(); - QBitArray a(sz); - const uchar *a1 = reinterpret_cast<const uchar *>(d.constData()) + 1; - uchar *a2 = reinterpret_cast<uchar *>(a.d.data()) + 1; - qsizetype n = d.size() - 1; - - while (n-- > 0) - *a2++ = ~*a1++; - - if (sz && sz % 8) - *(a2 - 1) &= (1 << (sz % 8)) - 1; - return a; + qsizetype n = d.size(); + uchar *dst = reinterpret_cast<uchar *>(data_ptr().data()); + const uchar *src = dst; + QBitArray result([&] { + if (d.isDetached() || n == 0) + return std::move(d.data_ptr()); // invert in-place + + QByteArrayData tmp(n, n); + dst = reinterpret_cast<uchar *>(tmp.data()); + return tmp; + }()); + + uchar bitdiff = 8; + if (n) + bitdiff = dst[0] = src[0]; // copy the count of bits in the last byte + + for (qsizetype i = 1; i < n; ++i) + dst[i] = ~src[i]; + + if (int tailCount = 16 - bitdiff; tailCount != 8) { + // zero the bits beyond our size in the last byte + Q_ASSERT(n > 1); + uchar tailMask = (1U << tailCount) - 1; + dst[n - 1] &= tailMask; + } + + return result; } /*! - \relates QBitArray + \fn QBitArray QBitArray::operator&(const QBitArray &a1, const QBitArray &a2) + \fn QBitArray QBitArray::operator&(QBitArray &&a1, const QBitArray &a2) + \fn QBitArray QBitArray::operator&(const QBitArray &a1, QBitArray &&a2) + \fn QBitArray QBitArray::operator&(QBitArray &&a1, QBitArray &&a2) Returns a bit array that is the AND of the bit arrays \a a1 and \a a2. @@ -626,13 +783,16 @@ QBitArray QBitArray::operator~() const QBitArray operator&(const QBitArray &a1, const QBitArray &a2) { - QBitArray tmp = a1; - tmp &= a2; + QBitArray tmp = sizedForOverwrite(a1, a2); + performBitwiseOperationHelper(tmp, a1, a2, std::bit_and<uchar>()); return tmp; } /*! - \relates QBitArray + \fn QBitArray QBitArray::operator|(const QBitArray &a1, const QBitArray &a2) + \fn QBitArray QBitArray::operator|(QBitArray &&a1, const QBitArray &a2) + \fn QBitArray QBitArray::operator|(const QBitArray &a1, QBitArray &&a2) + \fn QBitArray QBitArray::operator|(QBitArray &&a1, QBitArray &&a2) Returns a bit array that is the OR of the bit arrays \a a1 and \a a2. @@ -649,13 +809,16 @@ QBitArray operator&(const QBitArray &a1, const QBitArray &a2) QBitArray operator|(const QBitArray &a1, const QBitArray &a2) { - QBitArray tmp = a1; - tmp |= a2; + QBitArray tmp = sizedForOverwrite(a1, a2); + performBitwiseOperationHelper(tmp, a1, a2, std::bit_or<uchar>()); return tmp; } /*! - \relates QBitArray + \fn QBitArray QBitArray::operator^(const QBitArray &a1, const QBitArray &a2) + \fn QBitArray QBitArray::operator^(QBitArray &&a1, const QBitArray &a2) + \fn QBitArray QBitArray::operator^(const QBitArray &a1, QBitArray &&a2) + \fn QBitArray QBitArray::operator^(QBitArray &&a1, QBitArray &&a2) Returns a bit array that is the XOR of the bit arrays \a a1 and \a a2. @@ -672,8 +835,8 @@ QBitArray operator|(const QBitArray &a1, const QBitArray &a2) QBitArray operator^(const QBitArray &a1, const QBitArray &a2) { - QBitArray tmp = a1; - tmp ^= a2; + QBitArray tmp = sizedForOverwrite(a1, a2); + performBitwiseOperationHelper(tmp, a1, a2, std::bit_xor<uchar>()); return tmp; } @@ -733,19 +896,19 @@ QBitArray operator^(const QBitArray &a1, const QBitArray &a2) QDataStream &operator<<(QDataStream &out, const QBitArray &ba) { + const qsizetype len = ba.size(); if (out.version() < QDataStream::Qt_6_0) { - quint32 len = ba.size(); - out << len; - if (len > 0) - out.writeRawData(ba.d.constData() + 1, ba.d.size() - 1); - return out; + if (Q_UNLIKELY(len > qsizetype{(std::numeric_limits<qint32>::max)()})) { + out.setStatus(QDataStream::Status::SizeLimitExceeded); + return out; + } + out << quint32(len); } else { - quint64 len = ba.size(); - out << len; - if (len > 0) - out.writeRawData(ba.d.constData() + 1, ba.d.size() - 1); - return out; + out << quint64(len); } + if (len > 0) + out.writeRawData(ba.d.data() + 1, ba.d.size() - 1); + return out; } /*! @@ -763,10 +926,18 @@ QDataStream &operator>>(QDataStream &in, QBitArray &ba) if (in.version() < QDataStream::Qt_6_0) { quint32 tmp; in >> tmp; + if (Q_UNLIKELY(tmp > quint32((std::numeric_limits<qint32>::max)()))) { + in.setStatus(QDataStream::ReadCorruptData); + return in; + } len = tmp; } else { quint64 tmp; in >> tmp; + if (Q_UNLIKELY(tmp > quint64((std::numeric_limits<qsizetype>::max)()))) { + in.setStatus(QDataStream::Status::SizeLimitExceeded); + return in; + } len = tmp; } if (len == 0) { @@ -775,7 +946,7 @@ QDataStream &operator>>(QDataStream &in, QBitArray &ba) } const qsizetype Step = 8 * 1024 * 1024; - qsizetype totalBytes = (len + 7) / 8; + const qsizetype totalBytes = storage_size(len); qsizetype allocated = 0; while (allocated < totalBytes) { @@ -789,14 +960,13 @@ QDataStream &operator>>(QDataStream &in, QBitArray &ba) allocated += blockSize; } - qsizetype paddingMask = ~((0x1 << (len & 0x7)) - 1); - if (paddingMask != ~0x0 && (ba.d.constData()[ba.d.size() - 1] & paddingMask)) { + const auto fromStream = ba.d.back(); + adjust_head_and_tail(ba.d.data(), ba.d.size(), len); + if (ba.d.back() != fromStream) { ba.clear(); in.setStatus(QDataStream::ReadCorruptData); return in; } - - *ba.d.data() = ba.d.size() * 8 - len; return in; } #endif // QT_NO_DATASTREAM diff --git a/src/corelib/tools/qbitarray.h b/src/corelib/tools/qbitarray.h index e724aea598..b9c36b5320 100644 --- a/src/corelib/tools/qbitarray.h +++ b/src/corelib/tools/qbitarray.h @@ -11,25 +11,70 @@ QT_BEGIN_NAMESPACE class QBitRef; class Q_CORE_EXPORT QBitArray { + Q_CORE_EXPORT friend QBitArray operator&(const QBitArray &a1, const QBitArray &a2); + friend QBitArray operator&(QBitArray &&a1, const QBitArray &a2) + { return a1 &= a2; } + friend QBitArray operator&(const QBitArray &a1, QBitArray &&a2) + { return a2 &= a1; } + friend QBitArray operator&(QBitArray &&a1, QBitArray &&a2) + { return a1 &= a2; } + + Q_CORE_EXPORT friend QBitArray operator|(const QBitArray &a1, const QBitArray &a2); + friend QBitArray operator|(QBitArray &&a1, const QBitArray &a2) + { return a1 |= a2; } + friend QBitArray operator|(const QBitArray &a1, QBitArray &&a2) + { return a2 |= a1; } + friend QBitArray operator|(QBitArray &&a1, QBitArray &&a2) + { return a1 |= a2; } + + Q_CORE_EXPORT friend QBitArray operator^(const QBitArray &a1, const QBitArray &a2); + friend QBitArray operator^(QBitArray &&a1, const QBitArray &a2) + { return a1 ^= a2; } + friend QBitArray operator^(const QBitArray &a1, QBitArray &&a2) + { return a2 ^= a1; } + friend QBitArray operator^(QBitArray &&a1, QBitArray &&a2) + { return a1 ^= a2; } + #ifndef QT_NO_DATASTREAM friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QBitArray &); friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QBitArray &); #endif friend Q_CORE_EXPORT size_t qHash(const QBitArray &key, size_t seed) noexcept; + friend QBitArray operator~(QBitArray a) + { return std::move(a).inverted_inplace(); } QByteArray d; + QBitArray(QByteArrayData &&dd) : d(std::move(dd)) {} + + template <typename BitArray> static auto bitLocation(BitArray &ba, qsizetype i) + { + Q_ASSERT(size_t(i) < size_t(ba.size())); + struct R { + decltype(ba.d[1]) byte; + uchar bitMask; + }; + qsizetype byteIdx = i >> 3; + qsizetype bitIdx = i & 7; + return R{ ba.d[1 + byteIdx], uchar(1U << bitIdx) }; + } + + QBitArray inverted_inplace() &&; + public: inline QBitArray() noexcept {} explicit QBitArray(qsizetype size, bool val = false); + // Rule Of Zero applies +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) QBitArray(const QBitArray &other) noexcept : d(other.d) {} inline QBitArray &operator=(const QBitArray &other) noexcept { d = other.d; return *this; } inline QBitArray(QBitArray &&other) noexcept : d(std::move(other.d)) {} QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QBitArray) +#endif // Qt 6 void swap(QBitArray &other) noexcept { d.swap(other.d); } - inline qsizetype size() const { return (d.size() << 3) - *d.constData(); } - inline qsizetype count() const { return (d.size() << 3) - *d.constData(); } + qsizetype size() const { return qsizetype((size_t(d.size()) << 3) - *d.constData()); } + qsizetype count() const { return size(); } qsizetype count(bool on) const; inline bool isEmpty() const { return d.isEmpty(); } @@ -41,25 +86,43 @@ public: inline bool isDetached() const { return d.isDetached(); } inline void clear() { d.clear(); } - bool testBit(qsizetype i) const; - void setBit(qsizetype i); - void setBit(qsizetype i, bool val); - void clearBit(qsizetype i); - bool toggleBit(qsizetype i); - - bool at(qsizetype i) const; - QBitRef operator[](qsizetype i); - bool operator[](qsizetype i) const; - + bool testBit(qsizetype i) const + { auto r = bitLocation(*this, i); return r.byte & r.bitMask; } + void setBit(qsizetype i) + { auto r = bitLocation(*this, i); r.byte |= r.bitMask; } + void setBit(qsizetype i, bool val) + { if (val) setBit(i); else clearBit(i); } + void clearBit(qsizetype i) + { auto r = bitLocation(*this, i); r.byte &= ~r.bitMask; } + bool toggleBit(qsizetype i) + { + auto r = bitLocation(*this, i); + bool cl = r.byte & r.bitMask; + r.byte ^= r.bitMask; + return cl; + } + + bool at(qsizetype i) const { return testBit(i); } + inline QBitRef operator[](qsizetype i); + bool operator[](qsizetype i) const { return testBit(i); } + + QBitArray &operator&=(QBitArray &&); + QBitArray &operator|=(QBitArray &&); + QBitArray &operator^=(QBitArray &&); QBitArray &operator&=(const QBitArray &); QBitArray &operator|=(const QBitArray &); QBitArray &operator^=(const QBitArray &); +#if QT_CORE_REMOVED_SINCE(6, 7) QBitArray operator~() const; +#endif - inline bool operator==(const QBitArray &other) const { return d == other.d; } - inline bool operator!=(const QBitArray &other) const { return d != other.d; } +#if QT_CORE_REMOVED_SINCE(6, 8) + inline bool operator==(const QBitArray &other) const { return comparesEqual(d, other.d); } + inline bool operator!=(const QBitArray &other) const { return !operator==(other); } +#endif - inline bool fill(bool val, qsizetype size = -1); + bool fill(bool aval, qsizetype asize = -1) + { *this = QBitArray((asize < 0 ? this->size() : asize), aval); return true; } void fill(bool val, qsizetype first, qsizetype last); inline void truncate(qsizetype pos) { if (pos < size()) resize(pos); } @@ -72,39 +135,17 @@ public: public: typedef QByteArray::DataPointer DataPtr; inline DataPtr &data_ptr() { return d.data_ptr(); } -}; + inline const DataPtr &data_ptr() const { return d.data_ptr(); } -inline bool QBitArray::fill(bool aval, qsizetype asize) -{ *this = QBitArray((asize < 0 ? this->size() : asize), aval); return true; } - -Q_CORE_EXPORT QBitArray operator&(const QBitArray &, const QBitArray &); -Q_CORE_EXPORT QBitArray operator|(const QBitArray &, const QBitArray &); -Q_CORE_EXPORT QBitArray operator^(const QBitArray &, const QBitArray &); - -inline bool QBitArray::testBit(qsizetype i) const -{ Q_ASSERT(size_t(i) < size_t(size())); - return (*(reinterpret_cast<const uchar*>(d.constData())+1+(i>>3)) & (1 << (i & 7))) != 0; } - -inline void QBitArray::setBit(qsizetype i) -{ Q_ASSERT(size_t(i) < size_t(size())); - *(reinterpret_cast<uchar*>(d.data())+1+(i>>3)) |= uchar(1 << (i & 7)); } - -inline void QBitArray::clearBit(qsizetype i) -{ Q_ASSERT(size_t(i) < size_t(size())); - *(reinterpret_cast<uchar*>(d.data())+1+(i>>3)) &= ~uchar(1 << (i & 7)); } - -inline void QBitArray::setBit(qsizetype i, bool val) -{ if (val) setBit(i); else clearBit(i); } - -inline bool QBitArray::toggleBit(qsizetype i) -{ Q_ASSERT(size_t(i) < size_t(size())); - uchar b = uchar(1<<(i&7)); uchar* p = reinterpret_cast<uchar*>(d.data())+1+(i>>3); - uchar c = uchar(*p&b); *p^=b; return c!=0; } - -inline bool QBitArray::operator[](qsizetype i) const { return testBit(i); } -inline bool QBitArray::at(qsizetype i) const { return testBit(i); } +private: + friend bool comparesEqual(const QBitArray &lhs, const QBitArray &rhs) noexcept + { + return lhs.d == rhs.d; + } + Q_DECLARE_EQUALITY_COMPARABLE(QBitArray) +}; -class Q_CORE_EXPORT QBitRef +class QT6_ONLY(Q_CORE_EXPORT) QBitRef { private: QBitArray &a; @@ -119,7 +160,7 @@ public: QBitRef &operator=(bool val) { a.setBit(i, val); return *this; } }; -inline QBitRef QBitArray::operator[](qsizetype i) +QBitRef QBitArray::operator[](qsizetype i) { Q_ASSERT(i >= 0); return QBitRef(*this, i); } #ifndef QT_NO_DATASTREAM diff --git a/src/corelib/tools/qcommandlineoption.cpp b/src/corelib/tools/qcommandlineoption.cpp index f1816753e6..6b990cecf1 100644 --- a/src/corelib/tools/qcommandlineoption.cpp +++ b/src/corelib/tools/qcommandlineoption.cpp @@ -116,8 +116,7 @@ QCommandLineOption::QCommandLineOption(const QStringList &names) The default value for the option is set to \a defaultValue. In Qt versions before 5.4, this constructor was \c explicit. In Qt 5.4 - and later, it no longer is and can be used for C++11-style uniform - initialization: + and later, it no longer is and can be used for uniform initialization: \snippet code/src_corelib_tools_qcommandlineoption.cpp cxx11-init @@ -152,8 +151,7 @@ QCommandLineOption::QCommandLineOption(const QString &name, const QString &descr The default value for the option is set to \a defaultValue. In Qt versions before 5.4, this constructor was \c explicit. In Qt 5.4 - and later, it no longer is and can be used for C++11-style uniform - initialization: + and later, it no longer is and can be used for uniform initialization: \snippet code/src_corelib_tools_qcommandlineoption.cpp cxx11-init-list diff --git a/src/corelib/tools/qcommandlineparser.cpp b/src/corelib/tools/qcommandlineparser.cpp index 0bab9dafe4..2880eedf77 100644 --- a/src/corelib/tools/qcommandlineparser.cpp +++ b/src/corelib/tools/qcommandlineparser.cpp @@ -125,24 +125,24 @@ QStringList QCommandLineParserPrivate::aliases(const QString &optionName) const The parser handles short names, long names, more than one name for the same option, and option values. - Options on the command line are recognized as starting with a single or - double \c{-} character(s). + Options on the command line are recognized as starting with one or two + \c{-} characters, followed by the option name. The option \c{-} (single dash alone) is a special case, often meaning standard - input, and not treated as an option. The parser will treat everything after the + input, and is not treated as an option. The parser will treat everything after the option \c{--} (double dash) as positional arguments. Short options are single letters. The option \c{v} would be specified by passing \c{-v} on the command line. In the default parsing mode, short options can be written in a compact form, for instance \c{-abc} is equivalent to \c{-a -b -c}. - The parsing mode for can be set to ParseAsLongOptions, in which case \c{-abc} + The parsing mode can be changed to ParseAsLongOptions, in which case \c{-abc} will be parsed as the long option \c{abc}. Long options are more than one letter long and cannot be compacted together. The long option \c{verbose} would be passed as \c{--verbose} or \c{-verbose}. - Passing values to options can be done using the assignment operator: \c{-v=value} - \c{--verbose=value}, or a space: \c{-v value} \c{--verbose value}, i.e. the next - argument is used as value (even if it starts with a \c{-}). + Passing values to options can be done by using the assignment operator (\c{-v=value}, + \c{--verbose=value}), or with a space (\c{-v value}, \c{--verbose value}). This + works even if the the value starts with a \c{-}. The parser does not support optional values - if an option is set to require a value, one must be present. If such an option is placed last @@ -157,13 +157,13 @@ QStringList QCommandLineParserPrivate::aliases(const QString &optionName) const Example: \snippet code/src_corelib_tools_qcommandlineparser_main.cpp 0 - If your compiler supports the C++11 standard, the three addOption() calls in - the above example can be simplified: + The three addOption() calls in the above example can be made more compact + by using addOptions(): \snippet code/src_corelib_tools_qcommandlineparser_main.cpp cxx11 Known limitation: the parsing of Qt options inside QCoreApplication and subclasses happens before QCommandLineParser exists, so it can't take it into account. This - means any option value that looks like a builtin Qt option, will be treated by + means any option value that looks like a builtin Qt option will be treated by QCoreApplication as a builtin Qt option. Example: \c{--profile -reverse} will lead to QGuiApplication seeing the -reverse option set, and removing it from QCoreApplication::arguments() before QCommandLineParser defines the \c{profile} @@ -398,7 +398,7 @@ QCommandLineOption QCommandLineParser::addVersionOption() These options are handled automatically by QCommandLineParser. - Remember to use \c setApplicationDescription() to set the application + Remember to use setApplicationDescription() to set the application description, which will be displayed when this option is used. Example: @@ -508,7 +508,7 @@ QString QCommandLineParser::errorText() const if (!d->errorText.isEmpty()) return d->errorText; if (d->unknownOptionNames.size() == 1) - return tr("Unknown option '%1'.").arg(d->unknownOptionNames.first()); + return tr("Unknown option '%1'.").arg(d->unknownOptionNames.constFirst()); if (d->unknownOptionNames.size() > 1) return tr("Unknown options: %1.").arg(d->unknownOptionNames.join(QStringLiteral(", "))); return QString(); @@ -521,7 +521,8 @@ enum MessageType { UsageMessage, ErrorMessage }; // or we are run with redirected handles (for example, by QProcess). static inline bool displayMessageBox() { - if (GetConsoleWindow()) + if (GetConsoleWindow() + || qEnvironmentVariableIsSet("QT_COMMAND_LINE_PARSER_NO_GUI_MESSAGE_BOXES")) return false; STARTUPINFO startupInfo; startupInfo.cb = sizeof(STARTUPINFO); @@ -673,7 +674,6 @@ bool QCommandLineParserPrivate::parse(const QStringList &args) needsParsing = false; bool error = false; - const QString doubleDashString(QStringLiteral("--")); const QLatin1Char dashChar('-'); const QLatin1Char assignChar('='); @@ -697,7 +697,7 @@ bool QCommandLineParserPrivate::parse(const QStringList &args) if (forcePositional) { positionalArgumentList.append(argument); - } else if (argument.startsWith(doubleDashString)) { + } else if (argument.startsWith("--"_L1)) { if (argument.size() > 2) { QString optionName = argument.mid(2).section(assignChar, 0, 0); if (registerFoundOption(optionName)) { @@ -786,7 +786,7 @@ bool QCommandLineParserPrivate::parse(const QStringList &args) Returns \c true if the option \a name was set, false otherwise. The name provided can be any long or short name of any option that was - added with \c addOption(). All the options names are treated as being + added with addOption(). All the options names are treated as being equivalent. If the name is not recognized or that option was not present, false is returned. @@ -812,7 +812,7 @@ bool QCommandLineParser::isSet(const QString &name) const an empty string if not found. The name provided can be any long or short name of any option that was - added with \c addOption(). All the option names are treated as being + added with addOption(). All the option names are treated as being equivalent. If the name is not recognized or that option was not present, an empty string is returned. @@ -820,7 +820,7 @@ bool QCommandLineParser::isSet(const QString &name) const that option is returned. If the option wasn't specified on the command line, the default value is returned. - An empty string is returned if the option does not take a value. + If the option does not take a value, a warning is printed, and an empty string is returned. \sa values(), QCommandLineOption::setDefaultValue(), QCommandLineOption::setDefaultValues() */ @@ -841,7 +841,7 @@ QString QCommandLineParser::value(const QString &optionName) const optionName, or an empty list if not found. The name provided can be any long or short name of any option that was - added with \c addOption(). All the options names are treated as being + added with addOption(). All the options names are treated as being equivalent. If the name is not recognized or that option was not present, an empty list is returned. @@ -861,8 +861,14 @@ QStringList QCommandLineParser::values(const QString &optionName) const if (it != d->nameHash.cend()) { const qsizetype optionOffset = *it; QStringList values = d->optionValuesHash.value(optionOffset); - if (values.isEmpty()) - values = d->commandLineOptionList.at(optionOffset).defaultValues(); + if (values.isEmpty()) { + const auto &option = d->commandLineOptionList.at(optionOffset); + if (option.valueName().isEmpty()) { + qWarning("QCommandLineParser: option not expecting values: \"%ls\"", + qUtf16Printable(optionName)); + } + values = option.defaultValues(); + } return values; } @@ -949,8 +955,8 @@ QStringList QCommandLineParser::positionalArguments() const Names may appear more than once in this list if they were encountered more than once by the parser. - Any entry in the list can be used with \c value() or with - \c values() to get any relevant option values. + Any entry in the list can be used with value() or with + values() to get any relevant option values. */ QStringList QCommandLineParser::optionNames() const @@ -1100,7 +1106,8 @@ QString QCommandLineParserPrivate::helpText(bool includeQtOptions) const QString text; QString usage; // executable name - usage += qApp ? QCoreApplication::arguments().constFirst() : QStringLiteral("<executable_name>"); + usage += qApp ? QStringView(QCoreApplication::arguments().constFirst()) + : QStringView(u"<executable_name>"); QList<QCommandLineOption> options = commandLineOptionList; if (includeQtOptions && qApp) qApp->d_func()->addQtOptions(&options); diff --git a/src/corelib/tools/qcontainerfwd.h b/src/corelib/tools/qcontainerfwd.h index 013ba9eab0..d5590553fa 100644 --- a/src/corelib/tools/qcontainerfwd.h +++ b/src/corelib/tools/qcontainerfwd.h @@ -12,7 +12,9 @@ #endif // std headers can unfortunately not be forward declared +#include <cstddef> // std::size_t #include <utility> +#include <limits> QT_BEGIN_NAMESPACE @@ -21,12 +23,16 @@ template <typename Key, typename T> class QHash; template <typename Key, typename T> class QMap; template <typename Key, typename T> class QMultiHash; template <typename Key, typename T> class QMultiMap; +#ifndef QT_NO_QPAIR template <typename T1, typename T2> using QPair = std::pair<T1, T2>; +#endif template <typename T> class QQueue; template <typename T> class QSet; +template <typename T, std::size_t E = std::size_t(-1) /* = std::dynamic_extent*/> class QSpan; template <typename T> class QStack; -template <typename T, qsizetype Prealloc = 256> class QVarLengthArray; +constexpr qsizetype QVarLengthArrayDefaultPrealloc = 256; +template <typename T, qsizetype Prealloc = QVarLengthArrayDefaultPrealloc> class QVarLengthArray; template <typename T> class QList; class QString; #ifndef Q_QDOC @@ -45,7 +51,13 @@ class QVariant; using QVariantList = QList<QVariant>; using QVariantMap = QMap<QString, QVariant>; using QVariantHash = QHash<QString, QVariant>; -using QVariantPair = QPair<QVariant, QVariant>; +using QVariantPair = std::pair<QVariant, QVariant>; + +namespace QtPrivate +{ +[[maybe_unused]] +constexpr qsizetype MaxAllocSize = (std::numeric_limits<qsizetype>::max)(); +} QT_END_NAMESPACE diff --git a/src/corelib/tools/qcontainertools_impl.h b/src/corelib/tools/qcontainertools_impl.h index 40ed0a84e9..998bc292d4 100644 --- a/src/corelib/tools/qcontainertools_impl.h +++ b/src/corelib/tools/qcontainertools_impl.h @@ -72,6 +72,8 @@ template <typename T, typename N> void q_uninitialized_relocate_n(T* first, N n, T* out) { if constexpr (QTypeInfo<T>::isRelocatable) { + static_assert(std::is_copy_constructible_v<T> || std::is_move_constructible_v<T>, + "Refusing to relocate this non-copy/non-move-constructible type."); if (n != N(0)) { // even if N == 0, out == nullptr or first == nullptr are UB for memcpy() std::memcpy(static_cast<void *>(out), static_cast<const void *>(first), @@ -252,6 +254,13 @@ void q_relocate_overlap_n(T *first, N n, T *d_first) } } +template <typename T> +struct ArrowProxy +{ + T t; + T *operator->() noexcept { return &t; } +}; + template <typename Iterator> using IfIsInputIterator = typename std::enable_if< std::is_convertible<typename std::iterator_traits<Iterator>::iterator_category, std::input_iterator_tag>::value, @@ -300,7 +309,11 @@ using IfAssociativeIteratorHasKeyAndValue = template <typename Iterator> using IfAssociativeIteratorHasFirstAndSecond = - std::enable_if_t<qxp::is_detected_v<FirstAndSecondTest, Iterator>, bool>; + std::enable_if_t< + std::conjunction_v< + std::negation<qxp::is_detected<KeyAndValueTest, Iterator>>, + qxp::is_detected<FirstAndSecondTest, Iterator> + >, bool>; template <typename Iterator> using MoveBackwardsTest = decltype( @@ -367,8 +380,7 @@ template <typename Container, typename T> auto sequential_erase_with_copy(Container &c, const T &t) { using CopyProxy = std::conditional_t<std::is_copy_constructible_v<T>, T, const T &>; - const T &tCopy = CopyProxy(t); - return sequential_erase(c, tCopy); + return sequential_erase(c, CopyProxy(t)); } template <typename Container, typename T> diff --git a/src/corelib/tools/qcontiguouscache.h b/src/corelib/tools/qcontiguouscache.h index c01dbb9390..e3f98c30de 100644 --- a/src/corelib/tools/qcontiguouscache.h +++ b/src/corelib/tools/qcontiguouscache.h @@ -8,6 +8,7 @@ #include <QtCore/qassert.h> #include <QtCore/qtclasshelpermacros.h> #include <QtCore/qtcoreexports.h> +#include <QtCore/qttypetraits.h> #include <QtCore/qtypeinfo.h> #include <climits> diff --git a/src/corelib/tools/qcryptographichash.cpp b/src/corelib/tools/qcryptographichash.cpp index 2c4a2b276b..d0ed17eba2 100644 --- a/src/corelib/tools/qcryptographichash.cpp +++ b/src/corelib/tools/qcryptographichash.cpp @@ -25,12 +25,13 @@ #include "../../3rdparty/rfc6234/sha.h" #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 -#if !QT_CONFIG(opensslv30) || !QT_CONFIG(openssl_linked) +#if !QT_CONFIG(openssl_hash) // qdoc and qmake only need SHA-1 #include "../../3rdparty/md5/md5.h" #include "../../3rdparty/md5/md5.cpp" #include "../../3rdparty/md4/md4.h" #include "../../3rdparty/md4/md4.cpp" +#endif // !QT_CONFIG(openssl_hash) typedef unsigned char BitSequence; typedef unsigned long long DataLength; @@ -71,6 +72,7 @@ Q_CONSTINIT static SHA3Final * const sha3Final = Final; #endif +#if !QT_CONFIG(openssl_hash) /* These 2 functions replace macros of the same name in sha224-256.c and sha384-512.c. Originally, these macros relied on a global static 'addTemp' @@ -114,7 +116,7 @@ static inline int SHA384_512AddLength(SHA512Context *context, unsigned int lengt #endif #endif // QT_CRYPTOGRAPHICHASH_ONLY_SHA1 -#if !defined(QT_BOOTSTRAPPED) && QT_CONFIG(opensslv30) && QT_CONFIG(openssl_linked) +#if !defined(QT_BOOTSTRAPPED) && QT_CONFIG(openssl_hash) #define USING_OPENSSL30 #include <openssl/evp.h> #include <openssl/provider.h> @@ -266,10 +268,6 @@ static constexpr const char * methodToName(QCryptographicHash::Algorithm method) CASE(RealSha3_256, "SHA3-256"); CASE(RealSha3_384, "SHA3-384"); CASE(RealSha3_512, "SHA3-512"); - CASE(Keccak_224, "SHA3-224"); - CASE(Keccak_256, "SHA3-256"); - CASE(Keccak_384, "SHA3-384"); - CASE(Keccak_512, "SHA3-512"); CASE(Blake2b_512, "BLAKE2B512"); CASE(Blake2s_256, "BLAKE2S256"); #undef CASE @@ -283,7 +281,9 @@ static constexpr const char * methodToName(QCryptographicHash::Algorithm method) */ static constexpr bool useNonOpenSSLFallback(QCryptographicHash::Algorithm method) noexcept { - if (method == QCryptographicHash::Blake2b_160 || method == QCryptographicHash::Blake2b_256 || + if (method == QCryptographicHash::Keccak_224 || method == QCryptographicHash::Keccak_256 || + method == QCryptographicHash::Keccak_384 || method == QCryptographicHash::Keccak_512 || + method == QCryptographicHash::Blake2b_160 || method == QCryptographicHash::Blake2b_256 || method == QCryptographicHash::Blake2b_384 || method == QCryptographicHash::Blake2s_128 || method == QCryptographicHash::Blake2s_160 || method == QCryptographicHash::Blake2s_224) return true; @@ -326,11 +326,20 @@ public: EVP_MD_free(md); } }; + struct OSSL_PROVIDER_deleter { + void operator()(OSSL_PROVIDER *provider) const noexcept { + OSSL_PROVIDER_unload(provider); + } + }; + using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, EVP_MD_CTX_deleter>; using EVP_MD_ptr = std::unique_ptr<EVP_MD, EVP_MD_deleter>; + using OSSL_PROVIDER_ptr = std::unique_ptr<OSSL_PROVIDER, OSSL_PROVIDER_deleter>; struct EVP { EVP_MD_ptr algorithm; EVP_MD_CTX_ptr context; + OSSL_PROVIDER_ptr defaultProvider; + OSSL_PROVIDER_ptr legacyProvider; bool initializationFailed; explicit EVP(QCryptographicHash::Algorithm method); @@ -361,11 +370,11 @@ public: SHA256Context sha256Context; SHA384Context sha384Context; SHA512Context sha512Context; +#endif SHA3Context sha3Context; enum class Sha3Variant { Sha3, Keccak }; void sha3Finish(HashResult &result, int bitCount, Sha3Variant sha3Variant); -#endif blake2b_state blake2bContext; blake2s_state blake2sContext; #endif @@ -378,7 +387,6 @@ public: }; #ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 -#ifndef USING_OPENSSL30 void QCryptographicHashPrivate::State::sha3Finish(HashResult &result, int bitCount, Sha3Variant sha3Variant) { @@ -418,7 +426,6 @@ void QCryptographicHashPrivate::State::sha3Finish(HashResult &result, int bitCou sha3Final(©, result.data()); } -#endif // !QT_CONFIG(opensslv30) #endif /*! @@ -548,9 +555,15 @@ QCryptographicHash::Algorithm QCryptographicHash::algorithm() const noexcept QCryptographicHashPrivate::State::State(QCryptographicHash::Algorithm method) { - if (method == QCryptographicHash::Blake2b_160 || - method == QCryptographicHash::Blake2b_256 || - method == QCryptographicHash::Blake2b_384) { + if (method == QCryptographicHash::Keccak_224 || + method == QCryptographicHash::Keccak_256 || + method == QCryptographicHash::Keccak_384 || + method == QCryptographicHash::Keccak_512) { + new (&sha3Context) SHA3Context; + reset(method); + } else if (method == QCryptographicHash::Blake2b_160 || + method == QCryptographicHash::Blake2b_256 || + method == QCryptographicHash::Blake2b_384) { new (&blake2bContext) blake2b_state; reset(method); } else if (method == QCryptographicHash::Blake2s_128 || @@ -565,7 +578,11 @@ QCryptographicHashPrivate::State::State(QCryptographicHash::Algorithm method) void QCryptographicHashPrivate::State::destroy(QCryptographicHash::Algorithm method) { - if (method != QCryptographicHash::Blake2b_160 && + if (method != QCryptographicHash::Keccak_224 && + method != QCryptographicHash::Keccak_256 && + method != QCryptographicHash::Keccak_384 && + method != QCryptographicHash::Keccak_512 && + method != QCryptographicHash::Blake2b_160 && method != QCryptographicHash::Blake2b_256 && method != QCryptographicHash::Blake2b_384 && method != QCryptographicHash::Blake2s_128 && @@ -583,12 +600,16 @@ QCryptographicHashPrivate::EVP::EVP(QCryptographicHash::Algorithm method) * We need to load the legacy provider in order to have the MD4 * algorithm available. */ - if (!OSSL_PROVIDER_load(nullptr, "legacy")) - return; - if (!OSSL_PROVIDER_load(nullptr, "default")) + legacyProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_load(nullptr, "legacy")); + + if (!legacyProvider) return; } + defaultProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_load(nullptr, "default")); + if (!defaultProvider) + return; + context = EVP_MD_CTX_ptr(EVP_MD_CTX_new()); if (!context) { @@ -685,9 +706,14 @@ void QCryptographicHashPrivate::reset() noexcept void QCryptographicHashPrivate::State::reset(QCryptographicHash::Algorithm method) noexcept { - if (method == QCryptographicHash::Blake2b_160 || - method == QCryptographicHash::Blake2b_256 || - method == QCryptographicHash::Blake2b_384) { + if (method == QCryptographicHash::Keccak_224 || + method == QCryptographicHash::Keccak_256 || + method == QCryptographicHash::Keccak_384 || + method == QCryptographicHash::Keccak_512) { + sha3Init(&sha3Context, hashLengthInternal(method) * 8); + } else if (method == QCryptographicHash::Blake2b_160 || + method == QCryptographicHash::Blake2b_256 || + method == QCryptographicHash::Blake2b_384) { blake2b_init(&blake2bContext, hashLengthInternal(method)); } else if (method == QCryptographicHash::Blake2s_128 || method == QCryptographicHash::Blake2s_160 || @@ -813,9 +839,14 @@ void QCryptographicHashPrivate::State::addData(QCryptographicHash::Algorithm met auto length = bytes.size(); // all functions take size_t length, so we don't need to loop around them: { - if (method == QCryptographicHash::Blake2b_160 || - method == QCryptographicHash::Blake2b_256 || - method == QCryptographicHash::Blake2b_384) { + if (method == QCryptographicHash::Keccak_224 || + method == QCryptographicHash::Keccak_256 || + method == QCryptographicHash::Keccak_384 || + method == QCryptographicHash::Keccak_512) { + sha3Update(&sha3Context, reinterpret_cast<const BitSequence *>(data), uint64_t(length) * 8); + } else if (method == QCryptographicHash::Blake2b_160 || + method == QCryptographicHash::Blake2b_256 || + method == QCryptographicHash::Blake2b_384) { blake2b_update(&blake2bContext, reinterpret_cast<const uint8_t *>(data), length); } else if (method == QCryptographicHash::Blake2s_128 || method == QCryptographicHash::Blake2s_160 || @@ -987,9 +1018,14 @@ void QCryptographicHashPrivate::finalizeUnchecked() noexcept void QCryptographicHashPrivate::State::finalizeUnchecked(QCryptographicHash::Algorithm method, HashResult &result) noexcept { - if (method == QCryptographicHash::Blake2b_160 || - method == QCryptographicHash::Blake2b_256 || - method == QCryptographicHash::Blake2b_384) { + if (method == QCryptographicHash::Keccak_224 || + method == QCryptographicHash::Keccak_256 || + method == QCryptographicHash::Keccak_384 || + method == QCryptographicHash::Keccak_512) { + sha3Finish(result, 8 * hashLengthInternal(method), Sha3Variant::Keccak); + } else if (method == QCryptographicHash::Blake2b_160 || + method == QCryptographicHash::Blake2b_256 || + method == QCryptographicHash::Blake2b_384) { const auto length = hashLengthInternal(method); blake2b_state copy = blake2bContext; result.resizeForOverwrite(length); @@ -1117,13 +1153,49 @@ void QCryptographicHashPrivate::State::finalizeUnchecked(QCryptographicHash::Alg \note In Qt versions prior to 6.3, this function took QByteArray, not QByteArrayView. + + \sa hashInto() */ QByteArray QCryptographicHash::hash(QByteArrayView data, Algorithm method) { + QByteArray ba(hashLengthInternal(method), Qt::Uninitialized); + [[maybe_unused]] const auto r = hashInto(ba, data, method); + Q_ASSERT(r.size() == ba.size()); + return ba; +} + +/*! + \since 6.8 + \fn QCryptographicHash::hashInto(QSpan<char> buffer, QSpan<const QByteArrayView> data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan<uchar> buffer, QSpan<const QByteArrayView> data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan<std::byte> buffer, QSpan<const QByteArrayView> data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan<char> buffer, QByteArrayView data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan<uchar> buffer, QByteArrayView data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan<std::byte> buffer, QByteArrayView data, Algorithm method); + + Returns the hash of \a data using \a method, using \a buffer to store the result. + + If \a data is a span, adds all the byte array views to the hash, in the order given. + + The return value will be a sub-span of \a buffer, unless \a buffer is of + insufficient size, in which case a null QByteArrayView is returned. + + \sa hash() +*/ +QByteArrayView QCryptographicHash::hashInto(QSpan<std::byte> buffer, + QSpan<const QByteArrayView> data, + Algorithm method) noexcept +{ QCryptographicHashPrivate hash(method); - hash.addData(data); + for (QByteArrayView part : data) + hash.addData(part); hash.finalizeUnchecked(); // no mutex needed: no-one but us has access to 'hash' - return hash.resultView().toByteArray(); + auto result = hash.resultView(); + if (buffer.size() < result.size()) + return {}; // buffer too small + // ### optimize: have the method directly write into `buffer` + memcpy(buffer.data(), result.data(), result.size()); + return buffer.first(result.size()); } /*! @@ -1163,8 +1235,8 @@ bool QCryptographicHashPrivate::supportsAlgorithm(QCryptographicHash::Algorithm if (useNonOpenSSLFallback(method)) return true; - OSSL_PROVIDER_load(nullptr, "legacy"); - OSSL_PROVIDER_load(nullptr, "default"); + auto legacyProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_load(nullptr, "legacy")); + auto defaultProvider = OSSL_PROVIDER_ptr(OSSL_PROVIDER_load(nullptr, "default")); const char *restriction = "-fips"; EVP_MD_ptr algorithm = EVP_MD_ptr(EVP_MD_fetch(nullptr, methodToName(method), restriction)); @@ -1246,7 +1318,7 @@ static constexpr int qt_hash_block_size(QCryptographicHash::Algorithm method) return BLAKE2S_BLOCKBYTES; #endif // QT_CRYPTOGRAPHICHASH_ONLY_SHA1 case QCryptographicHash::NumAlgorithms: -#if !defined(Q_GCC_ONLY) || Q_CC_GCC >= 900 +#if !defined(Q_CC_GNU_ONLY) || Q_CC_GNU >= 900 // GCC 8 has trouble with Q_UNREACHABLE() in constexpr functions Q_UNREACHABLE(); #endif @@ -1289,9 +1361,9 @@ using HashBlock = QSmallByteArray<maxHashBlockSize()>; static HashBlock xored(const HashBlock &block, quint8 val) noexcept { // some hints for the optimizer: - Q_ASSUME(block.size() >= minHashBlockSize()); - Q_ASSUME(block.size() <= maxHashBlockSize()); - Q_ASSUME(block.size() % gcdHashBlockSize() == 0); + Q_ASSERT(block.size() >= minHashBlockSize()); + Q_ASSERT(block.size() <= maxHashBlockSize()); + Q_ASSERT(block.size() % gcdHashBlockSize() == 0); HashBlock result; result.resizeForOverwrite(block.size()); for (qsizetype i = 0; i < block.size(); ++i) @@ -1302,7 +1374,7 @@ static HashBlock xored(const HashBlock &block, quint8 val) noexcept class QMessageAuthenticationCodePrivate { public: - QMessageAuthenticationCodePrivate(QCryptographicHash::Algorithm m) + explicit QMessageAuthenticationCodePrivate(QCryptographicHash::Algorithm m) noexcept : messageHash(m) { } @@ -1381,21 +1453,32 @@ void QMessageAuthenticationCodePrivate::initMessageHash() noexcept \ingroup tools \reentrant - QMessageAuthenticationCode supports all cryptographic hashes which are supported by - QCryptographicHash. + Use the QMessageAuthenticationCode class to generate hash-based message + authentication codes (HMACs). The class supports all cryptographic + hash algorithms from \l QCryptographicHash (see also + \l{QCryptographicHash::Algorithm}). + + To generate a message authentication code, pass a suitable hash + algorithm and secret key to the constructor. Then process the message + data by calling \l addData() one or more times. After the full + message has been processed, get the final authentication code + via the \l result() function: - To generate message authentication code, pass hash algorithm QCryptographicHash::Algorithm - to constructor, then set key and message by setKey() and addData() functions. Result - can be acquired by result() function. \snippet qmessageauthenticationcode/main.cpp 0 \dots \snippet qmessageauthenticationcode/main.cpp 1 - Alternatively, this effect can be achieved by providing message, - key and method to hash() method. + For simple cases like above, you can also use the static + \l hash() function: + \snippet qmessageauthenticationcode/main.cpp 2 - \sa QCryptographicHash + + \note The cryptographic strength of the HMAC depends upon the + size of the secret key, and the security of the + underlying hash function. + + \sa QCryptographicHash, QCryptographicHash::Algorithm */ /*! @@ -1460,7 +1543,7 @@ QMessageAuthenticationCode::~QMessageAuthenticationCode() */ /*! - Resets message data. Calling this method doesn't affect the key. + Resets message data. Calling this function doesn't affect the key. */ void QMessageAuthenticationCode::reset() noexcept { @@ -1469,9 +1552,9 @@ void QMessageAuthenticationCode::reset() noexcept } /*! - Sets secret \a key. Calling this method automatically resets the object state. + Sets secret \a key. Calling this function automatically resets the object state. - For optimal performance, call this method only to \e change the active key, + For optimal performance, call this function only to \e change the active key, not to set an \e initial key, as in \code @@ -1588,15 +1671,52 @@ void QMessageAuthenticationCodePrivate::finalizeUnchecked() noexcept the key \a key and the method \a method. \include qcryptographichash.cpp {qba-to-qbav-6.6} + + \sa hashInto() */ QByteArray QMessageAuthenticationCode::hash(QByteArrayView message, QByteArrayView key, QCryptographicHash::Algorithm method) { + QByteArray ba(hashLengthInternal(method), Qt::Uninitialized); + [[maybe_unused]] const auto r = hashInto(ba, message, key, method); + Q_ASSERT(r.size() == ba.size()); + return ba; +} + +/*! + \since 6.8 + \fn QMessageAuthenticationCode::hashInto(QSpan<char> buffer, QSpan<const QByteArrayView> messageParts, QByteArrayView key, QCryptographicHash::Algorithm method); + \fn QMessageAuthenticationCode::hashInto(QSpan<uchar> buffer, QSpan<const QByteArrayView> messageParts, QByteArrayView key, QCryptographicHash::Algorithm method); + \fn QMessageAuthenticationCode::hashInto(QSpan<std::byte> buffer, QSpan<const QByteArrayView> messageParts, QByteArrayView key, QCryptographicHash::Algorithm method); + \fn QMessageAuthenticationCode::hashInto(QSpan<char> buffer, QByteArrayView message, QByteArrayView key, QCryptographicHash::Algorithm method); + \fn QMessageAuthenticationCode::hashInto(QSpan<uchar> buffer, QByteArrayView message, QByteArrayView key, QCryptographicHash::Algorithm method); + \fn QMessageAuthenticationCode::hashInto(QSpan<std::byte> buffer, QByteArrayView message, QByteArrayView key, QCryptographicHash::Algorithm method); + + Returns the authentication code for the message (\a message or, for the + QSpan overloads, the concatenation of \a messageParts) using the key \a key + and the method \a method. + + The return value will be a sub-span of \a buffer, unless \a buffer is of + insufficient size, in which case a null QByteArrayView is returned. + + \sa hash() +*/ +QByteArrayView QMessageAuthenticationCode::hashInto(QSpan<std::byte> buffer, + QSpan<const QByteArrayView> messageParts, + QByteArrayView key, + QCryptographicHash::Algorithm method) noexcept +{ QMessageAuthenticationCodePrivate mac(method); mac.setKey(key); - mac.messageHash.addData(message); + for (QByteArrayView part : messageParts) + mac.messageHash.addData(part); mac.finalizeUnchecked(); - return mac.messageHash.resultView().toByteArray(); + auto result = mac.messageHash.resultView(); + if (buffer.size() < result.size()) + return {}; // buffer too small + // ### optimize: have the method directly write into `buffer` + memcpy(buffer.data(), result.data(), result.size()); + return buffer.first(result.size()); } QT_END_NAMESPACE diff --git a/src/corelib/tools/qcryptographichash.h b/src/corelib/tools/qcryptographichash.h index 294453adce..8c4f70457f 100644 --- a/src/corelib/tools/qcryptographichash.h +++ b/src/corelib/tools/qcryptographichash.h @@ -8,6 +8,7 @@ #include <QtCore/qbytearray.h> #include <QtCore/qobjectdefs.h> +#include <QtCore/qspan.h> QT_BEGIN_NAMESPACE @@ -91,6 +92,19 @@ public: static QByteArray hash(const QByteArray &data, Algorithm method); #endif static QByteArray hash(QByteArrayView data, Algorithm method); + + static QByteArrayView hashInto(QSpan<char> buffer, QByteArrayView data, Algorithm method) noexcept + { return hashInto(as_writable_bytes(buffer), {&data, 1}, method); } + static QByteArrayView hashInto(QSpan<uchar> buffer, QByteArrayView data, Algorithm method) noexcept + { return hashInto(as_writable_bytes(buffer), {&data, 1}, method); } + static QByteArrayView hashInto(QSpan<std::byte> buffer, QByteArrayView data, Algorithm method) noexcept + { return hashInto(buffer, {&data, 1}, method); } + static QByteArrayView hashInto(QSpan<char> buffer, QSpan<const QByteArrayView> data, Algorithm method) noexcept + { return hashInto(as_writable_bytes(buffer), data, method); } + static QByteArrayView hashInto(QSpan<uchar> buffer, QSpan<const QByteArrayView> data, Algorithm method) noexcept + { return hashInto(as_writable_bytes(buffer), data, method); } + static QByteArrayView hashInto(QSpan<std::byte> buffer, QSpan<const QByteArrayView> data, Algorithm method) noexcept; + static int hashLength(Algorithm method); static bool supportsAlgorithm(Algorithm method); private: diff --git a/src/corelib/tools/qduplicatetracker_p.h b/src/corelib/tools/qduplicatetracker_p.h index 950220184f..23465ecffe 100644 --- a/src/corelib/tools/qduplicatetracker_p.h +++ b/src/corelib/tools/qduplicatetracker_p.h @@ -16,7 +16,7 @@ #include <private/qglobal_p.h> -#if __has_include(<memory_resource>) +#ifdef __cpp_lib_memory_resource # include <unordered_set> # include <memory_resource> # include <qhash.h> // for the hashing helpers diff --git a/src/corelib/tools/qeasingcurve.cpp b/src/corelib/tools/qeasingcurve.cpp index d8b3367de3..52602a0256 100644 --- a/src/corelib/tools/qeasingcurve.cpp +++ b/src/corelib/tools/qeasingcurve.cpp @@ -1153,32 +1153,37 @@ QEasingCurve::~QEasingCurve() */ /*! - Compare this easing curve with \a other and returns \c true if they are - equal. It will also compare the properties of a curve. + \fn bool QEasingCurve::operator==(const QEasingCurve &lhs, const QEasingCurve &rhs) + + Compares easing curve \a lhs with \a rhs and returns \c true if they are + equal; otherwise returns \c false. + It will also compare the properties of the curves. */ -bool QEasingCurve::operator==(const QEasingCurve &other) const +bool comparesEqual(const QEasingCurve &lhs, const QEasingCurve &rhs) noexcept { - bool res = d_ptr->func == other.d_ptr->func - && d_ptr->type == other.d_ptr->type; + bool res = lhs.d_ptr->func == rhs.d_ptr->func + && lhs.d_ptr->type == rhs.d_ptr->type; if (res) { - if (d_ptr->config && other.d_ptr->config) { + if (lhs.d_ptr->config && rhs.d_ptr->config) { // catch the config content - res = d_ptr->config->operator==(*(other.d_ptr->config)); + res = lhs.d_ptr->config->operator==(*(rhs.d_ptr->config)); - } else if (d_ptr->config || other.d_ptr->config) { + } else if (lhs.d_ptr->config || rhs.d_ptr->config) { // one one has a config object, which could contain default values - res = qFuzzyCompare(amplitude(), other.amplitude()) - && qFuzzyCompare(period(), other.period()) - && qFuzzyCompare(overshoot(), other.overshoot()); + res = qFuzzyCompare(lhs.amplitude(), rhs.amplitude()) + && qFuzzyCompare(lhs.period(), rhs.period()) + && qFuzzyCompare(lhs.overshoot(), rhs.overshoot()); } } return res; } /*! - \fn bool QEasingCurve::operator!=(const QEasingCurve &other) const - Compare this easing curve with \a other and returns \c true if they are not equal. - It will also compare the properties of a curve. + \fn bool QEasingCurve::operator!=(const QEasingCurve &lhs, const QEasingCurve &rhs) + + Compares easing curve \a lhs with \a rhs and returns \c true if they are + not equal; otherwise returns \c false. + It will also compare the properties of the curves. \sa operator==() */ diff --git a/src/corelib/tools/qeasingcurve.h b/src/corelib/tools/qeasingcurve.h index 5b112d7d7d..61e9aa247d 100644 --- a/src/corelib/tools/qeasingcurve.h +++ b/src/corelib/tools/qeasingcurve.h @@ -8,6 +8,7 @@ QT_REQUIRE_CONFIG(easingcurve); +#include <QtCore/qcompare.h> #include <QtCore/qlist.h> #include <QtCore/qobjectdefs.h> @@ -47,9 +48,11 @@ public: void swap(QEasingCurve &other) noexcept { qt_ptr_swap(d_ptr, other.d_ptr); } +#if QT_CORE_REMOVED_SINCE(6, 8) bool operator==(const QEasingCurve &other) const; inline bool operator!=(const QEasingCurve &other) const { return !(this->operator==(other)); } +#endif qreal amplitude() const; void setAmplitude(qreal amplitude); @@ -81,6 +84,11 @@ private: friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QEasingCurve &); friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QEasingCurve &); #endif + friend Q_CORE_EXPORT bool + comparesEqual(const QEasingCurve &lhs, const QEasingCurve &rhs) noexcept; +#if !QT_CORE_REMOVED_SINCE(6, 8) + Q_DECLARE_EQUALITY_COMPARABLE(QEasingCurve) +#endif }; Q_DECLARE_SHARED(QEasingCurve) diff --git a/src/corelib/tools/qflatmap_p.h b/src/corelib/tools/qflatmap_p.h index 7fce7c43af..d2c0d45b79 100644 --- a/src/corelib/tools/qflatmap_p.h +++ b/src/corelib/tools/qflatmap_p.h @@ -1099,7 +1099,9 @@ private: containers c; }; -template<class Key, class T, qsizetype N = 256, class Compare = std::less<Key>> +template <class Key, class T, + qsizetype N = QVarLengthArrayDefaultPrealloc, + class Compare = std::less<Key>> using QVarLengthFlatMap = QFlatMap<Key, T, Compare, QVarLengthArray<Key, N>, QVarLengthArray<T, N>>; QT_END_NAMESPACE diff --git a/src/corelib/tools/qfreelist.cpp b/src/corelib/tools/qfreelist.cpp index 45bd3ba8ae..db15fac5d6 100644 --- a/src/corelib/tools/qfreelist.cpp +++ b/src/corelib/tools/qfreelist.cpp @@ -6,6 +6,7 @@ QT_BEGIN_NAMESPACE // default sizes and offsets (no need to define these when customizing) +namespace QFreeListDefaultConstantsPrivate { enum { Offset0 = 0x00000000, Offset1 = 0x00008000, @@ -17,12 +18,13 @@ enum { Size2 = Offset3 - Offset2, Size3 = QFreeListDefaultConstants::MaxIndex - Offset3 }; +} Q_CONSTINIT const int QFreeListDefaultConstants::Sizes[QFreeListDefaultConstants::BlockCount] = { - Size0, - Size1, - Size2, - Size3 + QFreeListDefaultConstantsPrivate::Size0, + QFreeListDefaultConstantsPrivate::Size1, + QFreeListDefaultConstantsPrivate::Size2, + QFreeListDefaultConstantsPrivate::Size3 }; QT_END_NAMESPACE diff --git a/src/corelib/tools/qfunctionaltools_impl.cpp b/src/corelib/tools/qfunctionaltools_impl.cpp new file mode 100644 index 0000000000..28148c39a2 --- /dev/null +++ b/src/corelib/tools/qfunctionaltools_impl.cpp @@ -0,0 +1,47 @@ +// Copyright (C) 2023 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 + +#include <QtCore/qfunctionaltools_impl.h> + +// Remove this file once we have tests that implicitly test all aspects of +// CompactStorage + +QT_BEGIN_NAMESPACE + +namespace QtPrivate { + +#define FOR_EACH_CVREF(op) \ + op(&) \ + op(const &) \ + op(&&) \ + op(const &&) \ + /* end */ + +namespace _testing { + struct empty {}; + struct final final {}; + static_assert(std::is_same_v<CompactStorage<empty>, + detail::StorageEmptyBaseClassOptimization<empty>>); + static_assert(std::is_same_v<CompactStorage<final>, + detail::StorageByValue<final>>); + static_assert(std::is_same_v<CompactStorage<int>, + detail::StorageByValue<int>>); +#define CHECK1(Obj, cvref) \ + static_assert(std::is_same_v<decltype(std::declval<CompactStorage< Obj > cvref>().object()), \ + Obj cvref>); +#define CHECK(cvref) \ + CHECK1(empty, cvref) \ + CHECK1(final, cvref) \ + CHECK1(int, cvref) \ + /* end */ + + FOR_EACH_CVREF(CHECK) +#undef CHECK +#undef CHECK1 +} // namespace _testing + +} // namespace QtPrivate + +#undef FOR_EACH_CVREF + +QT_END_NAMESPACE diff --git a/src/corelib/tools/qfunctionaltools_impl.h b/src/corelib/tools/qfunctionaltools_impl.h index 5878b90bd6..0942d5fe7d 100644 --- a/src/corelib/tools/qfunctionaltools_impl.h +++ b/src/corelib/tools/qfunctionaltools_impl.h @@ -42,6 +42,7 @@ struct StorageByValue template <typename Object, typename Tag = void> struct StorageEmptyBaseClassOptimization : Object { + StorageEmptyBaseClassOptimization() = default; StorageEmptyBaseClassOptimization(Object &&o) : Object(std::move(o)) {} diff --git a/src/corelib/tools/qhash.cpp b/src/corelib/tools/qhash.cpp index f90c351118..12e90daecf 100644 --- a/src/corelib/tools/qhash.cpp +++ b/src/corelib/tools/qhash.cpp @@ -49,6 +49,8 @@ QT_BEGIN_NAMESPACE +void qt_from_latin1(char16_t *dst, const char *str, size_t size) noexcept; // qstring.cpp + // We assume that pointers and size_t have the same size. If that assumption should fail // on a platform the code selecting the different methods below needs to be fixed. static_assert(sizeof(size_t) == QT_POINTER_SIZE, "size_t and pointers have different size."); @@ -83,6 +85,7 @@ struct HashSeedStorage void resetSeed() { +#ifndef QT_BOOTSTRAPPED if (state().state < AlreadyInitialized) return; @@ -90,6 +93,7 @@ struct HashSeedStorage QRandomGenerator *generator = QRandomGenerator::system(); seeds[0].storeRelaxed(sizeof(size_t) > sizeof(quint32) ? generator->generate64() : generator->generate()); +#endif } void clearSeed() @@ -281,17 +285,11 @@ static inline uint64_t murmurhash(const void *key, uint64_t len, uint64_t seed) #endif -#if QT_POINTER_SIZE == 8 +namespace { // This is an inlined version of the SipHash implementation that is // trying to avoid some memcpy's from uint64 to uint8[] and back. -// - -// Use SipHash-1-2, which has similar performance characteristics as -// stablehash() above, instead of the SipHash-2-4 default -#define cROUNDS 1 -#define dROUNDS 2 -#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) +#define ROTL(x, b) (((x) << (b)) | ((x) >> (sizeof(x) * 8 - (b)))) #define SIPROUND \ do { \ @@ -311,8 +309,7 @@ static inline uint64_t murmurhash(const void *key, uint64_t len, uint64_t seed) v2 = ROTL(v2, 32); \ } while (0) -Q_NEVER_INLINE Q_DECL_HOT_FUNCTION -static uint64_t siphash(const uint8_t *in, uint64_t inlen, uint64_t seed, uint64_t seed2) +template <int cROUNDS = 2, int dROUNDS = 4> struct SipHash64 { /* "somepseudorandomlygeneratedbytes" */ uint64_t v0 = 0x736f6d6570736575ULL; @@ -320,17 +317,32 @@ static uint64_t siphash(const uint8_t *in, uint64_t inlen, uint64_t seed, uint64 uint64_t v2 = 0x6c7967656e657261ULL; uint64_t v3 = 0x7465646279746573ULL; uint64_t b; - uint64_t k0 = seed; - uint64_t k1 = seed2; - int i; - const uint8_t *end = in + (inlen & ~7ULL); - const int left = inlen & 7; + uint64_t k0; + uint64_t k1; + + inline SipHash64(uint64_t fulllen, uint64_t seed, uint64_t seed2); + inline void addBlock(const uint8_t *in, size_t inlen); + inline uint64_t finalize(const uint8_t *in, size_t left); +}; + +template <int cROUNDS, int dROUNDS> +SipHash64<cROUNDS, dROUNDS>::SipHash64(uint64_t inlen, uint64_t seed, uint64_t seed2) +{ b = inlen << 56; + k0 = seed; + k1 = seed2; v3 ^= k1; v2 ^= k0; v1 ^= k1; v0 ^= k0; +} +template <int cROUNDS, int dROUNDS> Q_DECL_HOT_FUNCTION void +SipHash64<cROUNDS, dROUNDS>::addBlock(const uint8_t *in, size_t inlen) +{ + Q_ASSERT((inlen & 7ULL) == 0); + int i; + const uint8_t *end = in + inlen; for (; in != end; in += 8) { uint64_t m = qFromUnaligned<uint64_t>(in); v3 ^= m; @@ -340,24 +352,31 @@ static uint64_t siphash(const uint8_t *in, uint64_t inlen, uint64_t seed, uint64 v0 ^= m; } +} - -#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU >= 700 - QT_WARNING_DISABLE_GCC("-Wimplicit-fallthrough") -#endif +template <int cROUNDS, int dROUNDS> Q_DECL_HOT_FUNCTION uint64_t +SipHash64<cROUNDS, dROUNDS>::finalize(const uint8_t *in, size_t left) +{ + int i; switch (left) { case 7: b |= ((uint64_t)in[6]) << 48; + Q_FALLTHROUGH(); case 6: b |= ((uint64_t)in[5]) << 40; + Q_FALLTHROUGH(); case 5: b |= ((uint64_t)in[4]) << 32; + Q_FALLTHROUGH(); case 4: b |= ((uint64_t)in[3]) << 24; + Q_FALLTHROUGH(); case 3: b |= ((uint64_t)in[2]) << 16; + Q_FALLTHROUGH(); case 2: b |= ((uint64_t)in[1]) << 8; + Q_FALLTHROUGH(); case 1: b |= ((uint64_t)in[0]); break; @@ -380,7 +399,8 @@ static uint64_t siphash(const uint8_t *in, uint64_t inlen, uint64_t seed, uint64 b = v0 ^ v1 ^ v2 ^ v3; return b; } -#else +#undef SIPROUND + // This is a "SipHash" implementation adopted for 32bit platforms. It performs // basically the same operations as the 64bit version using 4 byte at a time // instead of 8. @@ -391,12 +411,6 @@ static uint64_t siphash(const uint8_t *in, uint64_t inlen, uint64_t seed, uint64 // // For the v0-v4 constants, simply use the first four bytes of the 64 bit versions. // -// Use SipHash-1-2, which has similar performance characteristics as -// stablehash() above, instead of the SipHash-2-4 default -#define cROUNDS 1 -#define dROUNDS 2 - -#define ROTL(x, b) (uint32_t)(((x) << (b)) | ((x) >> (32 - (b)))) #define SIPROUND \ do { \ @@ -416,8 +430,7 @@ static uint64_t siphash(const uint8_t *in, uint64_t inlen, uint64_t seed, uint64 v2 = ROTL(v2, 16); \ } while (0) -Q_NEVER_INLINE Q_DECL_HOT_FUNCTION -static uint siphash(const uint8_t *in, uint inlen, uint seed, uint seed2) +template <int cROUNDS = 2, int dROUNDS = 4> struct SipHash32 { /* "somepseudorandomlygeneratedbytes" */ uint v0 = 0x736f6d65U; @@ -425,17 +438,32 @@ static uint siphash(const uint8_t *in, uint inlen, uint seed, uint seed2) uint v2 = 0x6c796765U; uint v3 = 0x74656462U; uint b; + uint k0; + uint k1; + + inline SipHash32(size_t fulllen, uint seed, uint seed2); + inline void addBlock(const uint8_t *in, size_t inlen); + inline uint finalize(const uint8_t *in, size_t left); +}; + +template <int cROUNDS, int dROUNDS> inline +SipHash32<cROUNDS, dROUNDS>::SipHash32(size_t inlen, uint seed, uint seed2) +{ uint k0 = seed; uint k1 = seed2; - int i; - const uint8_t *end = in + (inlen & ~3ULL); - const int left = inlen & 3; b = inlen << 24; v3 ^= k1; v2 ^= k0; v1 ^= k1; v0 ^= k0; +} +template <int cROUNDS, int dROUNDS> inline Q_DECL_HOT_FUNCTION void +SipHash32<cROUNDS, dROUNDS>::addBlock(const uint8_t *in, size_t inlen) +{ + Q_ASSERT((inlen & 3ULL) == 0); + int i; + const uint8_t *end = in + inlen; for (; in != end; in += 4) { uint m = qFromUnaligned<uint>(in); v3 ^= m; @@ -445,15 +473,19 @@ static uint siphash(const uint8_t *in, uint inlen, uint seed, uint seed2) v0 ^= m; } +} -#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU >= 700 - QT_WARNING_DISABLE_GCC("-Wimplicit-fallthrough") -#endif +template <int cROUNDS, int dROUNDS> inline Q_DECL_HOT_FUNCTION uint +SipHash32<cROUNDS, dROUNDS>::finalize(const uint8_t *in, size_t left) +{ + int i; switch (left) { case 3: b |= ((uint)in[2]) << 16; + Q_FALLTHROUGH(); case 2: b |= ((uint)in[1]) << 8; + Q_FALLTHROUGH(); case 1: b |= ((uint)in[0]); break; @@ -476,7 +508,70 @@ static uint siphash(const uint8_t *in, uint inlen, uint seed, uint seed2) b = v0 ^ v1 ^ v2 ^ v3; return b; } -#endif +#undef SIPROUND +#undef ROTL + +// Use SipHash-1-2, which has similar performance characteristics as +// stablehash() above, instead of the SipHash-2-4 default +template <int cROUNDS = 1, int dROUNDS = 2> +using SipHash = std::conditional_t<sizeof(void *) == 8, + SipHash64<cROUNDS, dROUNDS>, SipHash32<cROUNDS, dROUNDS>>; +} // unnamed namespace + +Q_NEVER_INLINE Q_DECL_HOT_FUNCTION +static size_t siphash(const uint8_t *in, size_t inlen, size_t seed, size_t seed2) +{ + constexpr size_t TailSizeMask = sizeof(void *) - 1; + SipHash<> hasher(inlen, seed, seed2); + hasher.addBlock(in, inlen & ~TailSizeMask); + return hasher.finalize(in + (inlen & ~TailSizeMask), inlen & TailSizeMask); +} + +enum ZeroExtension { + None = 0, + ByteToWord = 1, +}; + +template <ZeroExtension = None> static size_t +qHashBits_fallback(const uchar *p, size_t size, size_t seed, size_t seed2) noexcept; +template <> size_t qHashBits_fallback<None>(const uchar *p, size_t size, size_t seed, size_t seed2) noexcept +{ + if (size <= QT_POINTER_SIZE) + return murmurhash(p, size, seed); + + return siphash(reinterpret_cast<const uchar *>(p), size, seed, seed2); +} + +template <> size_t qHashBits_fallback<ByteToWord>(const uchar *data, size_t size, size_t seed, size_t seed2) noexcept +{ + auto quick_from_latin1 = [](char16_t *dest, const uchar *data, size_t size) { + // Quick, "inlined" version for very short blocks + std::copy_n(data, size, dest); + }; + if (size <= QT_POINTER_SIZE / 2) { + std::array<char16_t, QT_POINTER_SIZE / 2> buf; + quick_from_latin1(buf.data(), data, size); + return murmurhash(buf.data(), size * 2, seed); + } + + constexpr size_t TailSizeMask = sizeof(void *) / 2 - 1; + std::array<char16_t, 256> buf; + SipHash<> siphash(size * 2, seed, seed2); + ptrdiff_t offset = 0; + for ( ; offset + buf.size() < size; offset += buf.size()) { + qt_from_latin1(buf.data(), reinterpret_cast<const char *>(data) + offset, buf.size()); + siphash.addBlock(reinterpret_cast<uint8_t *>(buf.data()), sizeof(buf)); + } + if (size_t n = size - offset; n > TailSizeMask) { + n &= ~TailSizeMask; + qt_from_latin1(buf.data(), reinterpret_cast<const char *>(data) + offset, n); + siphash.addBlock(reinterpret_cast<uint8_t *>(buf.data()), n * 2); + offset += n; + } + + quick_from_latin1(buf.data(), data + offset, size - offset); + return siphash.finalize(reinterpret_cast<uint8_t *>(buf.data()), (size - offset) * 2); +} #if defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__) // GCC # define QHASH_AES_SANITIZER_BUILD @@ -523,10 +618,41 @@ namespace { // the scrambling round (step 3 in [1]) because it's just very good at // spreading the bits around. // + // Note on Latin-1 hashing (ZX == ByteToWord): for simplicity of the + // algorithm, we pass sizes equivalent to the UTF-16 content (ZX == None). + // That means we must multiply by 2 on entry, divide by 2 on pointer + // advancing, and load half as much data from memory (though we produce + // exactly as much data in registers). The compilers appear to optimize + // this out. + // // [1] https://en.wikipedia.org/wiki/Advanced_Encryption_Standard#High-level_description_of_the_algorithm + template <ZeroExtension ZX, typename T> static const T *advance(const T *ptr, ptrdiff_t n) + { + if constexpr (ZX == None) + return ptr + n; + + // see note above on ZX == ByteToWord hashing + auto p = reinterpret_cast<const uchar *>(ptr); + n *= sizeof(T); + return reinterpret_cast<const T *>(p + n/2); + } + + template <ZeroExtension> static __m128i loadu128(const void *ptr); + template <> Q_ALWAYS_INLINE QT_FUNCTION_TARGET(AES) __m128i loadu128<None>(const void *ptr) + { + return _mm_loadu_si128(reinterpret_cast<const __m128i *>(ptr)); + } + template <> Q_ALWAYS_INLINE QT_FUNCTION_TARGET(AES) __m128i loadu128<ByteToWord>(const void *ptr) + { + // use a MOVQ followed by PMOVZXBW + // the compiler usually combines them as a single, loading PMOVZXBW + __m128i data = _mm_loadl_epi64(static_cast<const __m128i *>(ptr)); + return _mm_cvtepu8_epi16(data); + } + // hash 16 bytes, running 3 scramble rounds of AES on itself (like label "final1") - static void QT_FUNCTION_TARGET(AES) QT_VECTORCALL + static void Q_ALWAYS_INLINE QT_FUNCTION_TARGET(AES) QT_VECTORCALL hash16bytes(__m128i &state0, __m128i data) { state0 = _mm_xor_si128(state0, data); @@ -536,11 +662,12 @@ namespace { } // hash twice 16 bytes, running 2 scramble rounds of AES on itself + template <ZeroExtension ZX> static void QT_FUNCTION_TARGET(AES) QT_VECTORCALL hash2x16bytes(__m128i &state0, __m128i &state1, const __m128i *src0, const __m128i *src1) { - __m128i data0 = _mm_loadu_si128(src0); - __m128i data1 = _mm_loadu_si128(src1); + __m128i data0 = loadu128<ZX>(src0); + __m128i data1 = loadu128<ZX>(src1); state0 = _mm_xor_si128(data0, state0); state1 = _mm_xor_si128(data1, state1); state0 = _mm_aesenc_si128(state0, state0); @@ -587,16 +714,18 @@ Q_ALWAYS_INLINE __m128i AESHashSeed::state1() const } } +template <ZeroExtension ZX> static size_t QT_FUNCTION_TARGET(AES) QT_VECTORCALL aeshash128_16to32(__m128i state0, __m128i state1, const __m128i *src, const __m128i *srcend) { { - if (src + 1 < srcend) { + const __m128i *src2 = advance<ZX>(srcend, -1); + if (advance<ZX>(src, 1) < srcend) { // epilogue: between 16 and 31 bytes - hash2x16bytes(state0, state1, src, srcend - 1); + hash2x16bytes<ZX>(state0, state1, src, src2); } else if (src != srcend) { // epilogue: between 1 and 16 bytes, overlap with the end - __m128i data = _mm_loadu_si128(srcend - 1); + __m128i data = loadu128<ZX>(src2); hash16bytes(state0, data); } @@ -607,8 +736,21 @@ aeshash128_16to32(__m128i state0, __m128i state1, const __m128i *src, const __m1 return mm_cvtsi128_sz(state0); } +// load all 16 bytes and mask off the bytes past the end of the source +static const qint8 maskarray[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +// load 16 bytes ending at the data end, then shuffle them to the beginning +static const qint8 shufflecontrol[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +template <ZeroExtension ZX> static size_t QT_FUNCTION_TARGET(AES) QT_VECTORCALL -aeshash128_lt16(__m128i state0, const uchar *p, size_t len) +aeshash128_lt16(__m128i state0, const __m128i *src, const __m128i *srcend, size_t len) { if (len) { // We're going to load 16 bytes and mask zero the part we don't care @@ -616,28 +758,18 @@ aeshash128_lt16(__m128i state0, const uchar *p, size_t len) // including NULLs at the end because the length is in the key) // WARNING: this may produce valgrind warnings, but it's safe - constexpr quintptr PageSize = 4096; + constexpr quintptr CachelineSize = 64; __m128i data; - if ((quintptr(p) & (PageSize / 2)) == 0) { - // lower half of the page: - // load all 16 bytes and mask off the bytes past the end of the source - static const qint8 maskarray[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - }; + if ((quintptr(src) & (CachelineSize / 2)) == 0) { + // lower half of the cacheline: __m128i mask = _mm_loadu_si128(reinterpret_cast<const __m128i *>(maskarray + 15 - len)); - data = _mm_loadu_si128(reinterpret_cast<const __m128i *>(p)); + data = loadu128<ZX>(src); data = _mm_and_si128(data, mask); } else { - // upper half of the page: - // load 16 bytes ending at the data end, then shuffle them to the beginning - static const qint8 shufflecontrol[] = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 - }; + // upper half of the cacheline: __m128i control = _mm_loadu_si128(reinterpret_cast<const __m128i *>(shufflecontrol + 15 - len)); - data = _mm_loadu_si128(reinterpret_cast<const __m128i *>(p + len) - 1); + data = loadu128<ZX>(advance<ZX>(srcend, -1)); data = _mm_shuffle_epi8(data, control); } @@ -646,24 +778,45 @@ aeshash128_lt16(__m128i state0, const uchar *p, size_t len) return mm_cvtsi128_sz(state0); } +template <ZeroExtension ZX> static size_t QT_FUNCTION_TARGET(AES) QT_VECTORCALL aeshash128_ge32(__m128i state0, __m128i state1, const __m128i *src, const __m128i *srcend) { // main loop: scramble two 16-byte blocks - for ( ; src + 2 < srcend; src += 2) - hash2x16bytes(state0, state1, src, src + 1); + for ( ; advance<ZX>(src, 2) < srcend; src = advance<ZX>(src, 2)) + hash2x16bytes<ZX>(state0, state1, src, advance<ZX>(src, 1)); - return aeshash128_16to32(state0, state1, src, srcend); + return aeshash128_16to32<ZX>(state0, state1, src, srcend); } # if QT_COMPILER_SUPPORTS_HERE(VAES) -static size_t QT_FUNCTION_TARGET(ARCH_ICL) QT_VECTORCALL +template <ZeroExtension> static __m256i loadu256(const void *ptr); +template <> Q_ALWAYS_INLINE QT_FUNCTION_TARGET(VAES) __m256i loadu256<None>(const void *ptr) +{ + return _mm256_loadu_si256(reinterpret_cast<const __m256i *>(ptr)); +} +template <> Q_ALWAYS_INLINE QT_FUNCTION_TARGET(VAES) __m256i loadu256<ByteToWord>(const void *ptr) +{ + // VPMOVZXBW xmm, ymm + __m128i data = _mm_loadu_si128(reinterpret_cast<const __m128i *>(ptr)); + return _mm256_cvtepu8_epi16(data); +} + +template <ZeroExtension ZX> +static size_t QT_FUNCTION_TARGET(VAES_AVX512) QT_VECTORCALL aeshash256_lt32_avx256(__m256i state0, const uchar *p, size_t len) { __m128i state0_128 = _mm256_castsi256_si128(state0); if (len) { - __mmask32 mask = _bzhi_u32(-1, unsigned(len)); - __m256i data = _mm256_maskz_loadu_epi8(mask, p); + __m256i data; + if constexpr (ZX == None) { + __mmask32 mask = _bzhi_u32(-1, unsigned(len)); + data = _mm256_maskz_loadu_epi8(mask, p); + } else { + __mmask16 mask = _bzhi_u32(-1, unsigned(len) / 2); + __m128i data0 = _mm_maskz_loadu_epi8(mask, p); + data = _mm256_cvtepu8_epi16(data0); + } __m128i data0 = _mm256_castsi256_si128(data); if (len >= sizeof(__m128i)) { state0 = _mm256_xor_si256(state0, data); @@ -683,8 +836,9 @@ aeshash256_lt32_avx256(__m256i state0, const uchar *p, size_t len) return mm_cvtsi128_sz(state0_128); } +template <ZeroExtension ZX> static size_t QT_FUNCTION_TARGET(VAES) QT_VECTORCALL -aeshash256_ge32(__m256i state0, const uchar *p, size_t len) +aeshash256_ge32(__m256i state0, const __m128i *s, const __m128i *end, size_t len) { static const auto hash32bytes = [](__m256i &state0, __m256i data) QT_FUNCTION_TARGET(VAES) { state0 = _mm256_xor_si256(state0, data); @@ -694,10 +848,10 @@ aeshash256_ge32(__m256i state0, const uchar *p, size_t len) }; // hash twice 32 bytes, running 2 scramble rounds of AES on itself - const auto hash2x32bytes = [](__m256i &state0, __m256i &state1, const __m256i *src0, - const __m256i *src1) QT_FUNCTION_TARGET(VAES) { - __m256i data0 = _mm256_loadu_si256(src0); - __m256i data1 = _mm256_loadu_si256(src1); + const auto hash2x32bytes = [](__m256i &state0, __m256i &state1, const void *src0, + const void *src1) QT_FUNCTION_TARGET(VAES) { + __m256i data0 = loadu256<ZX>(src0); + __m256i data1 = loadu256<ZX>(src1); state0 = _mm256_xor_si256(data0, state0); state1 = _mm256_xor_si256(data1, state1); state0 = _mm256_aesenc_epi128(state0, state0); @@ -706,21 +860,22 @@ aeshash256_ge32(__m256i state0, const uchar *p, size_t len) state1 = _mm256_aesenc_epi128(state1, state1); }; - const __m256i *src = reinterpret_cast<const __m256i *>(p); - const __m256i *srcend = reinterpret_cast<const __m256i *>(p + len); + const __m256i *src = reinterpret_cast<const __m256i *>(s); + const __m256i *srcend = reinterpret_cast<const __m256i *>(end); __m256i state1 = _mm256_aesenc_epi128(state0, mm256_set1_epz(len)); // main loop: scramble two 32-byte blocks - for ( ; src + 2 < srcend; src += 2) - hash2x32bytes(state0, state1, src, src + 1); + for ( ; advance<ZX>(src, 2) < srcend; src = advance<ZX>(src, 2)) + hash2x32bytes(state0, state1, src, advance<ZX>(src, 1)); - if (src + 1 < srcend) { + const __m256i *src2 = advance<ZX>(srcend, -1); + if (advance<ZX>(src, 1) < srcend) { // epilogue: between 32 and 31 bytes - hash2x32bytes(state0, state1, src, srcend - 1); + hash2x32bytes(state0, state1, src, src2); } else if (src != srcend) { // epilogue: between 1 and 32 bytes, overlap with the end - __m256i data = _mm256_loadu_si256(srcend - 1); + __m256i data = loadu256<ZX>(src2); hash32bytes(state0, data); } @@ -733,59 +888,69 @@ aeshash256_ge32(__m256i state0, const uchar *p, size_t len) return mm_cvtsi128_sz(_mm_xor_si128(low, high)); } +template <ZeroExtension ZX> static size_t QT_FUNCTION_TARGET(VAES) aeshash256(const uchar *p, size_t len, size_t seed, size_t seed2) noexcept { AESHashSeed state(seed, seed2); auto src = reinterpret_cast<const __m128i *>(p); - const auto srcend = reinterpret_cast<const __m128i *>(p + len); + const auto srcend = reinterpret_cast<const __m128i *>(advance<ZX>(p, len)); if (len < sizeof(__m128i)) - return aeshash128_lt16(state.state0, p, len); + return aeshash128_lt16<ZX>(state.state0, src, srcend, len); if (len <= sizeof(__m256i)) - return aeshash128_16to32(state.state0, state.state1(), src, srcend); + return aeshash128_16to32<ZX>(state.state0, state.state1(), src, srcend); - return aeshash256_ge32(state.state0_256(), p, len); + return aeshash256_ge32<ZX>(state.state0_256(), src, srcend, len); } +template <ZeroExtension ZX> static size_t QT_FUNCTION_TARGET(VAES_AVX512) aeshash256_avx256(const uchar *p, size_t len, size_t seed, size_t seed2) noexcept { AESHashSeed state(seed, seed2); + auto src = reinterpret_cast<const __m128i *>(p); + const auto srcend = reinterpret_cast<const __m128i *>(advance<ZX>(p, len)); + if (len <= sizeof(__m256i)) - return aeshash256_lt32_avx256(state.state0_256(), p, len); + return aeshash256_lt32_avx256<ZX>(state.state0_256(), p, len); - return aeshash256_ge32(state.state0_256(), p, len); + return aeshash256_ge32<ZX>(state.state0_256(), src, srcend, len); } # endif // VAES +template <ZeroExtension ZX> static size_t QT_FUNCTION_TARGET(AES) aeshash128(const uchar *p, size_t len, size_t seed, size_t seed2) noexcept { AESHashSeed state(seed, seed2); auto src = reinterpret_cast<const __m128i *>(p); - const auto srcend = reinterpret_cast<const __m128i *>(p + len); + const auto srcend = reinterpret_cast<const __m128i *>(advance<ZX>(p, len)); if (len < sizeof(__m128i)) - return aeshash128_lt16(state.state0, p, len); + return aeshash128_lt16<ZX>(state.state0, src, srcend, len); if (len <= sizeof(__m256i)) - return aeshash128_16to32(state.state0, state.state1(), src, srcend); + return aeshash128_16to32<ZX>(state.state0, state.state1(), src, srcend); - return aeshash128_ge32(state.state0, state.state1(), src, srcend); + return aeshash128_ge32<ZX>(state.state0, state.state1(), src, srcend); } +template <ZeroExtension ZX = None> static size_t aeshash(const uchar *p, size_t len, size_t seed, size_t seed2) noexcept { + if constexpr (ZX == ByteToWord) + len *= 2; // see note above on ZX == ByteToWord hashing + # if QT_COMPILER_SUPPORTS_HERE(VAES) if (qCpuHasFeature(VAES)) { if (qCpuHasFeature(AVX512VL)) - return aeshash256_avx256(p, len, seed, seed2); - return aeshash256(p, len, seed, seed2); + return aeshash256_avx256<ZX>(p, len, seed, seed2); + return aeshash256<ZX>(p, len, seed, seed2); } # endif - return aeshash128(p, len, seed, seed2); + return aeshash128<ZX>(p, len, seed, seed2); } #endif // x86 AESNI @@ -933,24 +1098,17 @@ size_t qHashBits(const void *p, size_t size, size_t seed) noexcept size_t seed2 = size; if (seed) seed2 = qt_qhash_seed.currentSeed(1); + + auto data = reinterpret_cast<const uchar *>(p); #ifdef AESHASH if (seed && qCpuHasFeature(AES) && qCpuHasFeature(SSE4_2)) - return aeshash(reinterpret_cast<const uchar *>(p), size, seed, seed2); + return aeshash(data, size, seed, seed2); #elif defined(Q_PROCESSOR_ARM) && QT_COMPILER_SUPPORTS_HERE(AES) && !defined(QHASH_AES_SANITIZER_BUILD) && !defined(QT_BOOTSTRAPPED) -# if defined(Q_OS_LINUX) - // Do specific runtime-only check as Yocto hard enables Crypto extension for - // all armv8 configs - if (seed && (qCpuFeatures() & CpuFeatureAES)) -# else if (seed && qCpuHasFeature(AES)) -# endif - return aeshash(reinterpret_cast<const uchar *>(p), size, seed, seed2); + return aeshash(data, size, seed, seed2); #endif - if (size <= QT_POINTER_SIZE) - return murmurhash(p, size, seed); - - return siphash(reinterpret_cast<const uchar *>(p), size, seed, seed2); + return qHashBits_fallback<>(data, size, seed, seed2); } size_t qHash(QByteArrayView key, size_t seed) noexcept @@ -963,6 +1121,7 @@ size_t qHash(QStringView key, size_t seed) noexcept return qHashBits(key.data(), key.size()*sizeof(QChar), seed); } +#ifndef QT_BOOTSTRAPPED size_t qHash(const QBitArray &bitArray, size_t seed) noexcept { qsizetype m = bitArray.d.size() - 1; @@ -975,10 +1134,30 @@ size_t qHash(const QBitArray &bitArray, size_t seed) noexcept result = ((result << 4) + bitArray.d.at(m)) & ((1 << n) - 1); return result; } +#endif size_t qHash(QLatin1StringView key, size_t seed) noexcept { - return qHashBits(reinterpret_cast<const uchar *>(key.data()), size_t(key.size()), seed); +#ifdef QT_BOOTSTRAPPED + // the seed is always 0 in bootstrapped mode (no seed generation code), + // so help the compiler do dead code elimination + seed = 0; +#endif + + auto data = reinterpret_cast<const uchar *>(key.data()); + size_t size = key.size(); + + // Mix in the length as a secondary seed. + // Multiplied by 2 to match the byte size of the equiavlent UTF-16 string. + size_t seed2 = size * 2; + if (seed) + seed2 = qt_qhash_seed.currentSeed(1); + +#if defined(AESHASH) + if (seed && qCpuHasFeature(AES) && qCpuHasFeature(SSE4_2)) + return aeshash<ByteToWord>(data, size, seed, seed2); +#endif + return qHashBits_fallback<ByteToWord>(data, size, seed, seed2); } /*! @@ -1383,6 +1562,26 @@ uint qt_hash(QStringView key, uint chained) noexcept Returns the hash value for the \a key, using \a seed to seed the calculation. */ +/*! \fn size_t qHash(quint128 key, size_t seed = 0) + \relates QHash + \since 6.8 + + Returns the hash value for the \a key, using \a seed to seed the calculation. + + \note This function is only available on platforms that support a native + 128-bit integer type. +*/ + +/*! \fn size_t qHash(qint128 key, size_t seed = 0) + \relates QHash + \since 6.8 + + Returns the hash value for the \a key, using \a seed to seed the calculation. + + \note This function is only available on platforms that support a native + 128-bit integer type. + */ + /*! \fn size_t qHash(char8_t key, size_t seed = 0) \relates QHash \since 6.0 @@ -1436,7 +1635,6 @@ size_t qHash(double key, size_t seed) noexcept } } -#if !defined(Q_OS_DARWIN) || defined(Q_QDOC) /*! \relates QHash \since 5.3 @@ -1454,7 +1652,6 @@ size_t qHash(long double key, size_t seed) noexcept return murmurhash(&key, sizeof(key), seed); } } -#endif /*! \fn size_t qHash(const QChar key, size_t seed = 0) \relates QHash @@ -1512,7 +1709,7 @@ size_t qHash(long double key, size_t seed) noexcept Returns the hash value for the \a key, using \a seed to seed the calculation. */ -/*! \fn template <class T> size_t qHash(std::nullptr_t key, size_t seed = 0) +/*! \fn size_t qHash(std::nullptr_t key, size_t seed = 0) \relates QHash \since 6.0 @@ -1783,8 +1980,8 @@ size_t qHash(long double key, size_t seed) noexcept Constructs a hash with a copy of each of the elements in the iterator range [\a begin, \a end). Either the elements iterated by the range must be - objects with \c{first} and \c{second} data members (like \c{QPair}, - \c{std::pair}, etc.) convertible to \c Key and to \c T respectively; or the + objects with \c{first} and \c{second} data members (like \c{std::pair}), + convertible to \c Key and to \c T respectively; or the iterators must have \c{key()} and \c{value()} member functions, returning a key convertible to \c Key and a value convertible to \c T respectively. */ @@ -2019,7 +2216,7 @@ size_t qHash(long double key, size_t seed) noexcept Returns \c true if the hash contains an item with the \a key; otherwise returns \c false. - \sa count(), QMultiHash::contains() + \sa count() */ /*! \fn template <class Key, class T> T QHash<Key, T>::value(const Key &key) const @@ -2042,6 +2239,12 @@ size_t qHash(long double key, size_t seed) noexcept a \l{default-constructed value} into the hash with the \a key, and returns a reference to it. +//! [qhash-iterator-invalidation-func-desc] + \warning Returned iterators/references should be considered invalidated + the next time you call a non-const function on the hash, or when the + hash is destroyed. +//! [qhash-iterator-invalidation-func-desc] + \sa insert(), value() */ @@ -2125,12 +2328,16 @@ size_t qHash(long double key, size_t seed) noexcept Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa constBegin(), end() */ /*! \fn template <class Key, class T> QHash<Key, T>::const_iterator QHash<Key, T>::begin() const \overload + + \include qhash.cpp qhash-iterator-invalidation-func-desc */ /*! \fn template <class Key, class T> QHash<Key, T>::const_iterator QHash<Key, T>::cbegin() const @@ -2139,6 +2346,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa begin(), cend() */ @@ -2147,6 +2356,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa begin(), constEnd() */ @@ -2156,6 +2367,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first key in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyEnd() */ @@ -2164,12 +2377,16 @@ size_t qHash(long double key, size_t seed) noexcept Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa begin(), constEnd() */ /*! \fn template <class Key, class T> QHash<Key, T>::const_iterator QHash<Key, T>::end() const \overload + + \include qhash.cpp qhash-iterator-invalidation-func-desc */ /*! \fn template <class Key, class T> QHash<Key, T>::const_iterator QHash<Key, T>::constEnd() const @@ -2177,6 +2394,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa constBegin(), end() */ @@ -2186,6 +2405,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa cbegin(), end() */ @@ -2195,6 +2416,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last key in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyBegin() */ @@ -2204,6 +2427,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyValueEnd() */ @@ -2213,6 +2438,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary entry after the last entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyValueBegin() */ @@ -2222,6 +2449,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyValueEnd() */ @@ -2231,6 +2460,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyValueBegin() */ @@ -2240,6 +2471,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary entry after the last entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyValueBegin() */ @@ -2249,6 +2482,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary entry after the last entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa constKeyValueBegin() */ @@ -2268,6 +2503,8 @@ size_t qHash(long double key, size_t seed) noexcept references to the ones in the hash. Specifically, mutating the value will modify the hash itself. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa QKeyValueIterator */ @@ -2285,6 +2522,8 @@ size_t qHash(long double key, size_t seed) noexcept \snippet code/src_corelib_tools_qhash.cpp 15 + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa remove(), take(), find() */ @@ -2304,12 +2543,16 @@ size_t qHash(long double key, size_t seed) noexcept \snippet code/src_corelib_tools_qhash.cpp 16 + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa value(), values() */ /*! \fn template <class Key, class T> QHash<Key, T>::const_iterator QHash<Key, T>::find(const Key &key) const \overload + + \include qhash.cpp qhash-iterator-invalidation-func-desc */ /*! \fn template <class Key, class T> QHash<Key, T>::const_iterator QHash<Key, T>::constFind(const Key &key) const @@ -2321,6 +2564,8 @@ size_t qHash(long double key, size_t seed) noexcept If the hash contains no item with the \a key, the function returns constEnd(). + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa find() */ @@ -2330,6 +2575,10 @@ size_t qHash(long double key, size_t seed) noexcept If there is already an item with the \a key, that item's value is replaced with \a value. + + Returns an iterator pointing to the new/updated element. + + \include qhash.cpp qhash-iterator-invalidation-func-desc */ /*! @@ -2341,6 +2590,8 @@ size_t qHash(long double key, size_t seed) noexcept construction. Returns an iterator pointing to the new element. + + \include qhash.cpp qhash-iterator-invalidation-func-desc */ @@ -2360,17 +2611,21 @@ size_t qHash(long double key, size_t seed) noexcept returns \c false. */ -/*! \fn template <class Key, class T> QPair<iterator, iterator> QMultiHash<Key, T>::equal_range(const Key &key) +/*! \fn template <class Key, class T> std::pair<iterator, iterator> QMultiHash<Key, T>::equal_range(const Key &key) \since 5.7 Returns a pair of iterators delimiting the range of values \c{[first, second)}, that are stored under \a key. If the range is empty then both iterators will be equal to end(). + + \include qhash.cpp qhash-iterator-invalidation-func-desc */ /*! - \fn template <class Key, class T> QPair<const_iterator, const_iterator> QMultiHash<Key, T>::equal_range(const Key &key) const + \fn template <class Key, class T> std::pair<const_iterator, const_iterator> QMultiHash<Key, T>::equal_range(const Key &key) const \overload \since 5.7 + + \include qhash.cpp qhash-iterator-invalidation-func-desc */ /*! \typedef QHash::ConstIterator @@ -2937,9 +3192,6 @@ size_t qHash(long double key, size_t seed) noexcept Constructs a multi-hash with a copy of each of the elements in the initializer list \a list. - - This function is only available if the program is being - compiled in C++11 mode. */ /*! \fn template <class Key, class T> QMultiHash<Key, T>::QMultiHash(const QHash<Key, T> &other) @@ -2953,8 +3205,8 @@ size_t qHash(long double key, size_t seed) noexcept Constructs a multi-hash with a copy of each of the elements in the iterator range [\a begin, \a end). Either the elements iterated by the range must be - objects with \c{first} and \c{second} data members (like \c{QPair}, - \c{std::pair}, etc.) convertible to \c Key and to \c T respectively; or the + objects with \c{first} and \c{second} data members (like \c{std::pair}), + convertible to \c Key and to \c T respectively; or the iterators must have \c{key()} and \c{value()} member functions, returning a key convertible to \c Key and a value convertible to \c T respectively. */ @@ -2969,6 +3221,10 @@ size_t qHash(long double key, size_t seed) noexcept If there are multiple items with the \a key, the most recently inserted item's value is replaced with \a value. + Returns an iterator pointing to the new/updated element. + + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa insert() */ @@ -2981,6 +3237,10 @@ size_t qHash(long double key, size_t seed) noexcept different from replace(), which overwrites the value of an existing item.) + Returns an iterator pointing to the new element. + + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa replace() */ @@ -2999,6 +3259,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns an iterator pointing to the new element. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa insert */ @@ -3015,6 +3277,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns an iterator pointing to the new element. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa replace, emplace */ @@ -3081,6 +3345,8 @@ size_t qHash(long double key, size_t seed) noexcept If the hash contains multiple items with the \a key, this function returns a reference to the most recently inserted value. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa insert(), value() */ @@ -3233,12 +3499,16 @@ size_t qHash(long double key, size_t seed) noexcept If the hash contains multiple items with the \a key and \a value, the iterator returned points to the most recently inserted item. + + \include qhash.cpp qhash-iterator-invalidation-func-desc */ /*! \fn template <class Key, class T> typename QMultiHash<Key, T>::const_iterator QMultiHash<Key, T>::find(const Key &key, const T &value) const \since 4.3 \overload + + \include qhash.cpp qhash-iterator-invalidation-func-desc */ /*! @@ -3250,6 +3520,8 @@ size_t qHash(long double key, size_t seed) noexcept If the hash contains no such item, the function returns constEnd(). + + \include qhash.cpp qhash-iterator-invalidation-func-desc */ /*! \fn template <class Key, class T> QMultiHash<Key, T>::iterator QMultiHash<Key, T>::begin() @@ -3257,12 +3529,16 @@ size_t qHash(long double key, size_t seed) noexcept Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa constBegin(), end() */ /*! \fn template <class Key, class T> QMultiHash<Key, T>::const_iterator QMultiHash<Key, T>::begin() const \overload + + \include qhash.cpp qhash-iterator-invalidation-func-desc */ /*! \fn template <class Key, class T> QMultiHash<Key, T>::const_iterator QMultiHash<Key, T>::cbegin() const @@ -3271,6 +3547,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa begin(), cend() */ @@ -3279,6 +3557,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa begin(), constEnd() */ @@ -3288,6 +3568,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first key in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyEnd() */ @@ -3296,6 +3578,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa begin(), constEnd() */ @@ -3309,6 +3593,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa constBegin(), end() */ @@ -3318,6 +3604,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last item in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa cbegin(), end() */ @@ -3327,6 +3615,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last key in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyBegin() */ @@ -3336,6 +3626,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyValueEnd() */ @@ -3345,6 +3637,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary entry after the last entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyValueBegin() */ @@ -3354,6 +3648,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyValueEnd() */ @@ -3363,6 +3659,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyValueBegin() */ @@ -3372,6 +3670,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary entry after the last entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa keyValueBegin() */ @@ -3381,6 +3681,8 @@ size_t qHash(long double key, size_t seed) noexcept Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary entry after the last entry in the hash. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa constKeyValueBegin() */ @@ -3400,6 +3702,8 @@ size_t qHash(long double key, size_t seed) noexcept references to the ones in the hash. Specifically, mutating the value will modify the hash itself. + \include qhash.cpp qhash-iterator-invalidation-func-desc + \sa QKeyValueIterator */ diff --git a/src/corelib/tools/qhash.h b/src/corelib/tools/qhash.h index 9b29429591..9cc6fbf30b 100644 --- a/src/corelib/tools/qhash.h +++ b/src/corelib/tools/qhash.h @@ -11,6 +11,7 @@ #include <QtCore/qiterator.h> #include <QtCore/qlist.h> #include <QtCore/qrefcount.h> +#include <QtCore/qttypetraits.h> #include <initializer_list> #include <functional> // for std::hash @@ -584,7 +585,7 @@ struct Data numBuckets = GrowthPolicy::bucketsForCapacity(qMax(size, reserved)); spans = allocateSpans(numBuckets).spans; size_t otherNSpans = other.numBuckets >> SpanConstants::SpanShift; - reallocationHelper(other, otherNSpans, true); + reallocationHelper(other, otherNSpans, numBuckets != other.numBuckets); } static Data *detached(Data *d) @@ -677,8 +678,10 @@ struct Data return size >= (numBuckets >> 1); } - Bucket findBucket(const Key &key) const noexcept + template <typename K> Bucket findBucket(const K &key) const noexcept { + static_assert(std::is_same_v<std::remove_cv_t<Key>, K> || + QHashHeterogeneousSearch<std::remove_cv_t<Key>, K>::value); Q_ASSERT(numBuckets > 0); size_t hash = QHashPrivate::calculateHash(key, seed); Bucket bucket(this, GrowthPolicy::bucketForHash(numBuckets, hash)); @@ -697,24 +700,12 @@ struct Data } } - Node *findNode(const Key &key) const noexcept + template <typename K> Node *findNode(const K &key) const noexcept { - Q_ASSERT(numBuckets > 0); - size_t hash = QHashPrivate::calculateHash(key, seed); - Bucket bucket(this, GrowthPolicy::bucketForHash(numBuckets, hash)); - // loop over the buckets until we find the entry we search for - // or an empty slot, in which case we know the entry doesn't exist - while (true) { - size_t offset = bucket.offset(); - if (offset == SpanConstants::UnusedEntry) { - return nullptr; - } else { - Node &n = bucket.nodeAtOffset(offset); - if (qHashEquals(n.key, key)) - return &n; - } - bucket.advanceWrapped(this); - } + auto bucket = findBucket(key); + if (bucket.isUnused()) + return nullptr; + return bucket.node(); } struct InsertionResult @@ -723,7 +714,7 @@ struct Data bool initialized; }; - InsertionResult findOrInsert(const Key &key) noexcept + template <typename K> InsertionResult findOrInsert(const K &key) noexcept { Bucket it(static_cast<Span *>(nullptr), 0); if (numBuckets > 0) { @@ -967,6 +958,11 @@ public: bool remove(const Key &key) { + return removeImpl(key); + } +private: + template <typename K> bool removeImpl(const K &key) + { if (isEmpty()) // prevents detaching shared null return false; auto it = d->findBucket(key); @@ -979,13 +975,21 @@ public: d->erase(it); return true; } + +public: template <typename Predicate> qsizetype removeIf(Predicate pred) { return QtPrivate::associative_erase_if(*this, pred); } + T take(const Key &key) { + return takeImpl(key); + } +private: + template <typename K> T takeImpl(const K &key) + { if (isEmpty()) // prevents detaching shared null return T(); auto it = d->findBucket(key); @@ -1000,6 +1004,7 @@ public: return value; } +public: bool contains(const Key &key) const noexcept { if (!d) @@ -1012,74 +1017,68 @@ public: } private: - const Key *keyImpl(const T &value) const noexcept + template <typename Fn> Key keyImpl(const T &value, Fn &&defaultFn) const noexcept { if (d) { const_iterator i = begin(); while (i != end()) { if (i.value() == value) - return &i.key(); + return i.key(); ++i; } } - return nullptr; + return defaultFn(); } public: Key key(const T &value) const noexcept { - if (auto *k = keyImpl(value)) - return *k; - else - return Key(); + return keyImpl(value, [] { return Key(); }); } Key key(const T &value, const Key &defaultKey) const noexcept { - if (auto *k = keyImpl(value)) - return *k; - else - return defaultKey; + return keyImpl(value, [&] { return defaultKey; }); } private: - T *valueImpl(const Key &key) const noexcept + template <typename K, typename Fn> T valueImpl(const K &key, Fn &&defaultValue) const noexcept { if (d) { Node *n = d->findNode(key); if (n) - return &n->value; + return n->value; } - return nullptr; + return defaultValue(); } public: T value(const Key &key) const noexcept { - if (T *v = valueImpl(key)) - return *v; - else - return T(); + return valueImpl(key, [] { return T(); }); } T value(const Key &key, const T &defaultValue) const noexcept { - if (T *v = valueImpl(key)) - return *v; - else - return defaultValue; + return valueImpl(key, [&] { return defaultValue; }); } T &operator[](const Key &key) { + return operatorIndexImpl(key); + } +private: + template <typename K> T &operatorIndexImpl(const K &key) + { const auto copy = isDetached() ? QHash() : *this; // keep 'key' alive across the detach detach(); auto result = d->findOrInsert(key); Q_ASSERT(!result.it.atEnd()); if (!result.initialized) - Node::createInPlace(result.it.node(), key, T()); + Node::createInPlace(result.it.node(), Key(key), T()); return result.it.node()->value; } +public: const T operator[](const Key &key) const noexcept { return value(key); @@ -1246,28 +1245,25 @@ public: return i; } - QPair<iterator, iterator> equal_range(const Key &key) + std::pair<iterator, iterator> equal_range(const Key &key) { - auto first = find(key); - auto second = first; - if (second != iterator()) - ++second; - return qMakePair(first, second); + return equal_range_impl(*this, key); } - - QPair<const_iterator, const_iterator> equal_range(const Key &key) const noexcept + std::pair<const_iterator, const_iterator> equal_range(const Key &key) const noexcept { - auto first = find(key); + return equal_range_impl(*this, key); + } +private: + template <typename Hash, typename K> static auto equal_range_impl(Hash &self, const K &key) + { + auto first = self.find(key); auto second = first; - if (second != iterator()) + if (second != decltype(first){}) ++second; - return qMakePair(first, second); + return std::make_pair(first, second); } - typedef iterator Iterator; - typedef const_iterator ConstIterator; - inline qsizetype count() const noexcept { return d ? qsizetype(d->size) : 0; } - iterator find(const Key &key) + template <typename K> iterator findImpl(const K &key) { if (isEmpty()) // prevents detaching shared null return end(); @@ -1279,7 +1275,7 @@ public: return end(); return iterator(it.toIterator(d)); } - const_iterator find(const Key &key) const noexcept + template <typename K> const_iterator constFindImpl(const K &key) const noexcept { if (isEmpty()) return end(); @@ -1288,6 +1284,19 @@ public: return end(); return const_iterator({d, it.toBucketIndex(d)}); } + +public: + typedef iterator Iterator; + typedef const_iterator ConstIterator; + inline qsizetype count() const noexcept { return d ? qsizetype(d->size) : 0; } + iterator find(const Key &key) + { + return findImpl(key); + } + const_iterator find(const Key &key) const noexcept + { + return constFindImpl(key); + } const_iterator constFind(const Key &key) const noexcept { return find(key); @@ -1351,8 +1360,65 @@ private: result.it.node()->emplaceValue(std::forward<Args>(args)...); return iterator(result.it); } -}; +public: +#ifdef __cpp_concepts + bool remove(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return removeImpl(key); + } + T take(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return takeImpl(key); + } + bool contains(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return d ? d->findNode(key) != nullptr : false; + } + qsizetype count(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return contains(key) ? 1 : 0; + } + T value(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + return valueImpl(key, [] { return T(); }); + } + T value(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &defaultValue) const noexcept + { + return valueImpl(key, [&] { return defaultValue; }); + } + T &operator[](const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return operatorIndexImpl(key); + } + const T operator[](const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + return value(key); + } + std::pair<iterator, iterator> + equal_range(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return equal_range_impl(*this, key); + } + std::pair<const_iterator, const_iterator> + equal_range(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + return equal_range_impl(*this, key); + } + iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return findImpl(key); + } + const_iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + return constFindImpl(key); + } + const_iterator constFind(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + return find(key); + } +#endif // __cpp_concepts +}; template <typename Key, typename T> @@ -1530,6 +1596,11 @@ public: qsizetype remove(const Key &key) { + return removeImpl(key); + } +private: + template <typename K> qsizetype removeImpl(const K &key) + { if (isEmpty()) // prevents detaching shared null return 0; auto it = d->findBucket(key); @@ -1545,13 +1616,21 @@ public: d->erase(it); return n; } + +public: template <typename Predicate> qsizetype removeIf(Predicate pred) { return QtPrivate::associative_erase_if(*this, pred); } + T take(const Key &key) { + return takeImpl(key); + } +private: + template <typename K> T takeImpl(const K &key) + { if (isEmpty()) // prevents detaching shared null return T(); auto it = d->findBucket(key); @@ -1576,6 +1655,7 @@ public: return t; } +public: bool contains(const Key &key) const noexcept { if (!d) @@ -1584,77 +1664,71 @@ public: } private: - const Key *keyImpl(const T &value) const noexcept + template <typename Fn> Key keyImpl(const T &value, Fn &&defaultValue) const noexcept { if (d) { auto i = d->begin(); while (i != d->end()) { Chain *e = i.node()->value; if (e->contains(value)) - return &i.node()->key; + return i.node()->key; ++i; } } - return nullptr; + return defaultValue(); } public: Key key(const T &value) const noexcept { - if (auto *k = keyImpl(value)) - return *k; - else - return Key(); + return keyImpl(value, [] { return Key(); }); } Key key(const T &value, const Key &defaultKey) const noexcept { - if (auto *k = keyImpl(value)) - return *k; - else - return defaultKey; + return keyImpl(value, [&] { return defaultKey; }); } private: - T *valueImpl(const Key &key) const noexcept + template <typename K, typename Fn> T valueImpl(const K &key, Fn &&defaultValue) const noexcept { if (d) { Node *n = d->findNode(key); if (n) { Q_ASSERT(n->value); - return &n->value->value; + return n->value->value; } } - return nullptr; + return defaultValue(); } public: T value(const Key &key) const noexcept { - if (auto *v = valueImpl(key)) - return *v; - else - return T(); + return valueImpl(key, [] { return T(); }); } T value(const Key &key, const T &defaultValue) const noexcept { - if (auto *v = valueImpl(key)) - return *v; - else - return defaultValue; + return valueImpl(key, [&] { return defaultValue; }); } T &operator[](const Key &key) { + return operatorIndexImpl(key); + } +private: + template <typename K> T &operatorIndexImpl(const K &key) + { const auto copy = isDetached() ? QMultiHash() : *this; // keep 'key' alive across the detach detach(); auto result = d->findOrInsert(key); Q_ASSERT(!result.it.atEnd()); if (!result.initialized) { - Node::createInPlace(result.it.node(), key, T()); + Node::createInPlace(result.it.node(), Key(key), T()); ++m_size; } return result.it.node()->value->value; } +public: const T operator[](const Key &key) const noexcept { return value(key); @@ -1685,9 +1759,15 @@ public: } return res; } + QList<T> values() const { return QList<T>(begin(), end()); } QList<T> values(const Key &key) const { + return valuesImpl(key); + } +private: + template <typename K> QList<T> valuesImpl(const K &key) const + { QList<T> values; if (d) { Node *n = d->findNode(key); @@ -1702,6 +1782,7 @@ public: return values; } +public: class const_iterator; class iterator @@ -1913,7 +1994,9 @@ public: typedef iterator Iterator; typedef const_iterator ConstIterator; inline qsizetype count() const noexcept { return size(); } - iterator find(const Key &key) + +private: + template <typename K> iterator findImpl(const K &key) { if (isEmpty()) return end(); @@ -1926,11 +2009,7 @@ public: return end(); return iterator(it.toIterator(d)); } - const_iterator find(const Key &key) const noexcept - { - return constFind(key); - } - const_iterator constFind(const Key &key) const noexcept + template <typename K> const_iterator constFindImpl(const K &key) const noexcept { if (isEmpty()) return end(); @@ -1939,6 +2018,20 @@ public: return constEnd(); return const_iterator(it.toIterator(d)); } +public: + iterator find(const Key &key) + { + return findImpl(key); + } + const_iterator constFind(const Key &key) const noexcept + { + return constFindImpl(key); + } + const_iterator find(const Key &key) const noexcept + { + return constFindImpl(key); + } + iterator insert(const Key &key, const T &value) { return emplace(key, value); @@ -2004,6 +2097,11 @@ public: bool contains(const Key &key, const T &value) const noexcept { + return containsImpl(key, value); + } +private: + template <typename K> bool containsImpl(const K &key, const T &value) const noexcept + { if (isEmpty()) return false; auto n = d->findNode(key); @@ -2012,8 +2110,14 @@ public: return n->value->contains(value); } +public: qsizetype remove(const Key &key, const T &value) { + return removeImpl(key, value); + } +private: + template <typename K> qsizetype removeImpl(const K &key, const T &value) + { if (isEmpty()) // prevents detaching shared null return 0; auto it = d->findBucket(key); @@ -2042,8 +2146,14 @@ public: return n; } +public: qsizetype count(const Key &key) const noexcept { + return countImpl(key); + } +private: + template <typename K> qsizetype countImpl(const K &key) const noexcept + { if (!d) return 0; auto it = d->findBucket(key); @@ -2059,8 +2169,14 @@ public: return n; } +public: qsizetype count(const Key &key, const T &value) const noexcept { + return countImpl(key, value); + } +private: + template <typename K> qsizetype countImpl(const K &key, const T &value) const noexcept + { if (!d) return 0; auto it = d->findBucket(key); @@ -2077,7 +2193,7 @@ public: return n; } - iterator find(const Key &key, const T &value) + template <typename K> iterator findImpl(const K &key, const T &value) { if (isEmpty()) return end(); @@ -2086,11 +2202,7 @@ public: auto it = constFind(key, value); return iterator(it.i, it.e); } - const_iterator find(const Key &key, const T &value) const noexcept - { - return constFind(key, value); - } - const_iterator constFind(const Key &key, const T &value) const noexcept + template <typename K> const_iterator constFindImpl(const K &key, const T &value) const noexcept { const_iterator i(constFind(key)); const_iterator end(constEnd()); @@ -2102,6 +2214,21 @@ public: return end; } +public: + iterator find(const Key &key, const T &value) + { + return findImpl(key, value); + } + + const_iterator constFind(const Key &key, const T &value) const noexcept + { + return constFindImpl(key, value); + } + const_iterator find(const Key &key, const T &value) const noexcept + { + return constFind(key, value); + } + QMultiHash &unite(const QMultiHash &other) { if (isEmpty()) { @@ -2137,29 +2264,39 @@ public: return *this; } - QPair<iterator, iterator> equal_range(const Key &key) + std::pair<iterator, iterator> equal_range(const Key &key) + { + return equal_range_impl(key); + } +private: + template <typename K> std::pair<iterator, iterator> equal_range_impl(const K &key) { const auto copy = isDetached() ? QMultiHash() : *this; // keep 'key' alive across the detach detach(); auto pair = std::as_const(*this).equal_range(key); - return qMakePair(iterator(pair.first.i), iterator(pair.second.i)); + return {iterator(pair.first.i), iterator(pair.second.i)}; } - QPair<const_iterator, const_iterator> equal_range(const Key &key) const noexcept +public: + std::pair<const_iterator, const_iterator> equal_range(const Key &key) const noexcept + { + return equal_range_impl(key); + } +private: + template <typename K> std::pair<const_iterator, const_iterator> equal_range_impl(const K &key) const noexcept { if (!d) - return qMakePair(end(), end()); + return {end(), end()}; auto bucket = d->findBucket(key); if (bucket.isUnused()) - return qMakePair(end(), end()); + return {end(), end()}; auto it = bucket.toIterator(d); auto end = it; ++end; - return qMakePair(const_iterator(it), const_iterator(end)); + return {const_iterator(it), const_iterator(end)}; } -private: void detach_helper() { if (!d) { @@ -2196,6 +2333,94 @@ private: } return iterator(result.it); } + +public: +#ifdef __cpp_concepts + qsizetype remove(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return removeImpl(key); + } + T take(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return takeImpl(key); + } + bool contains(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + if (!d) + return false; + return d->findNode(key) != nullptr; + } + T value(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + return valueImpl(key, [] { return T(); }); + } + T value(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &defaultValue) const noexcept + { + return valueImpl(key, [&] { return defaultValue; }); + } + T &operator[](const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return operatorIndexImpl(key); + } + const T operator[](const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + return value(key); + } + QList<T> values(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return valuesImpl(key); + } + iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return findImpl(key); + } + const_iterator constFind(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + return constFindImpl(key); + } + const_iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + return constFindImpl(key); + } + bool contains(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) const noexcept + { + return containsImpl(key, value); + } + qsizetype remove(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) + { + return removeImpl(key, value); + } + qsizetype count(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + return countImpl(key); + } + qsizetype count(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) const noexcept + { + return countImpl(key, value); + } + iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) + { + return findImpl(key, value); + } + const_iterator constFind(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) const noexcept + { + return constFindImpl(key, value); + } + const_iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) const noexcept + { + return constFind(key, value); + } + std::pair<iterator, iterator> + equal_range(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return equal_range_impl(key); + } + std::pair<const_iterator, const_iterator> + equal_range(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept + { + return equal_range_impl(key); + } +#endif // __cpp_concepts }; Q_DECLARE_ASSOCIATIVE_FORWARD_ITERATOR(Hash) diff --git a/src/corelib/tools/qhashfunctions.h b/src/corelib/tools/qhashfunctions.h index bcb8caf1f7..90a269deaa 100644 --- a/src/corelib/tools/qhashfunctions.h +++ b/src/corelib/tools/qhashfunctions.h @@ -1,5 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz <marc.mutz@kdab.com> +// Copyright (C) 2024 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QHASHFUNCTIONS_H @@ -7,10 +8,14 @@ #include <QtCore/qstring.h> #include <QtCore/qstringfwd.h> -#include <QtCore/qpair.h> #include <numeric> // for std::accumulate #include <functional> // for std::hash +#include <utility> // For std::pair + +#ifdef __cpp_concepts +# include <concepts> +#endif #if 0 #pragma qt_class(QHashFunctions) @@ -45,6 +50,21 @@ private: size_t data; }; +// Whether, ∀ t of type T && ∀ seed, qHash(Key(t), seed) == qHash(t, seed) +template <typename Key, typename T> struct QHashHeterogeneousSearch : std::false_type {}; + +// Specializations +template <> struct QHashHeterogeneousSearch<QString, QStringView> : std::true_type {}; +template <> struct QHashHeterogeneousSearch<QStringView, QString> : std::true_type {}; +template <> struct QHashHeterogeneousSearch<QByteArray, QByteArrayView> : std::true_type {}; +template <> struct QHashHeterogeneousSearch<QByteArrayView, QByteArray> : std::true_type {}; +#ifndef Q_PROCESSOR_ARM +template <> struct QHashHeterogeneousSearch<QString, QLatin1StringView> : std::true_type {}; +template <> struct QHashHeterogeneousSearch<QStringView, QLatin1StringView> : std::true_type {}; +template <> struct QHashHeterogeneousSearch<QLatin1StringView, QString> : std::true_type {}; +template <> struct QHashHeterogeneousSearch<QLatin1StringView, QStringView> : std::true_type {}; +#endif + namespace QHashPrivate { Q_DECL_CONST_FUNCTION constexpr size_t hash(size_t key, size_t seed) noexcept @@ -102,7 +122,39 @@ Q_DECL_CONST_FUNCTION constexpr inline size_t qHash(quint64 key, size_t seed = 0 key ^= (key >> 32); return QHashPrivate::hash(size_t(key), seed); } -Q_DECL_CONST_FUNCTION constexpr inline size_t qHash(qint64 key, size_t seed = 0) noexcept { return qHash(quint64(key), seed); } +Q_DECL_CONST_FUNCTION constexpr inline size_t qHash(qint64 key, size_t seed = 0) noexcept +{ + if constexpr (sizeof(qint64) > sizeof(size_t)) { + // Avoid QTBUG-116080: we XOR the top half with its own sign bit: + // - if the qint64 is in range of qint32, then signmask ^ high == 0 + // (for Qt 7 only) + // - if the qint64 is in range of quint32, then signmask == 0 and we + // do the same as the quint64 overload above + quint32 high = quint32(quint64(key) >> 32); + quint32 low = quint32(quint64(key)); + quint32 signmask = qint32(high) >> 31; // all zeroes or all ones + signmask = QT_VERSION_MAJOR > 6 ? signmask : 0; + low ^= signmask ^ high; + return qHash(low, seed); + } + return qHash(quint64(key), seed); +} +#if QT_SUPPORTS_INT128 +constexpr size_t qHash(quint128 key, size_t seed = 0) noexcept +{ + return qHash(quint64(key + (key >> 64)), seed); +} +constexpr size_t qHash(qint128 key, size_t seed = 0) noexcept +{ + // Avoid QTBUG-116080: same as above, but with double the sizes and without + // the need for compatibility + quint64 high = quint64(quint128(key) >> 64); + quint64 low = quint64(quint128(key)); + quint64 signmask = qint64(high) >> 63; // all zeroes or all ones + low += signmask ^ high; + return qHash(low, seed); +} +#endif // QT_SUPPORTS_INT128 Q_DECL_CONST_FUNCTION inline size_t qHash(float key, size_t seed = 0) noexcept { // ensure -0 gets mapped to 0 @@ -112,9 +164,7 @@ Q_DECL_CONST_FUNCTION inline size_t qHash(float key, size_t seed = 0) noexcept return QHashPrivate::hash(k, seed); } Q_CORE_EXPORT Q_DECL_CONST_FUNCTION size_t qHash(double key, size_t seed = 0) noexcept; -#if !defined(Q_OS_DARWIN) || defined(Q_QDOC) Q_CORE_EXPORT Q_DECL_CONST_FUNCTION size_t qHash(long double key, size_t seed = 0) noexcept; -#endif Q_DECL_CONST_FUNCTION constexpr inline size_t qHash(wchar_t key, size_t seed = 0) noexcept { return QHashPrivate::hash(size_t(key), seed); } Q_DECL_CONST_FUNCTION constexpr inline size_t qHash(char16_t key, size_t seed = 0) noexcept @@ -153,7 +203,9 @@ inline Q_DECL_PURE_FUNCTION size_t qHash(const QByteArray &key, size_t seed = 0 Q_CORE_EXPORT Q_DECL_PURE_FUNCTION size_t qHash(QStringView key, size_t seed = 0) noexcept; inline Q_DECL_PURE_FUNCTION size_t qHash(const QString &key, size_t seed = 0) noexcept { return qHash(QStringView{key}, seed); } +#ifndef QT_BOOTSTRAPPED Q_CORE_EXPORT Q_DECL_PURE_FUNCTION size_t qHash(const QBitArray &key, size_t seed = 0) noexcept; +#endif Q_CORE_EXPORT Q_DECL_PURE_FUNCTION size_t qHash(QLatin1StringView key, size_t seed = 0) noexcept; Q_DECL_CONST_FUNCTION constexpr inline size_t qHash(QKeyCombination key, size_t seed = 0) noexcept { return qHash(key.toCombined(), seed); } @@ -184,12 +236,35 @@ size_t qHash(const T &t, size_t seed) noexcept(noexcept(qHash(t))) { return qHash(t) ^ seed; } #endif // < Qt 7 +namespace QHashPrivate { +#ifdef __cpp_concepts +template <typename Key, typename T> concept HeterogeneouslySearchableWithHelper = + // if Key and T are not the same (member already exists) + !std::is_same_v<Key, T> + // but are comparable amongst each other + && std::equality_comparable_with<Key, T> + // and supports heteregenous hashing + && QHashHeterogeneousSearch<Key, T>::value; +template <typename Key, typename T> concept HeterogeneouslySearchableWith = + HeterogeneouslySearchableWithHelper<q20::remove_cvref_t<Key>, q20::remove_cvref_t<T>>; +#else +template <typename Key, typename T> constexpr bool HeterogeneouslySearchableWith = false; +#endif +} + template<typename T> bool qHashEquals(const T &a, const T &b) { return a == b; } +template <typename T1, typename T2> +std::enable_if_t<QHashPrivate::HeterogeneouslySearchableWith<T1, T2>, bool> +qHashEquals(const T1 &a, const T2 &b) +{ + return a == b; +} + namespace QtPrivate { struct QHashCombine @@ -326,7 +401,9 @@ QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_VALUE(QStringView) QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_VALUE(QLatin1StringView) QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_VALUE(QByteArrayView) QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_CREF(QByteArray) +#ifndef QT_BOOTSTRAPPED QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_CREF(QBitArray) +#endif QT_END_NAMESPACE diff --git a/src/corelib/tools/qiterator.h b/src/corelib/tools/qiterator.h index ab3d71f760..8a2b493ef4 100644 --- a/src/corelib/tools/qiterator.h +++ b/src/corelib/tools/qiterator.h @@ -5,6 +5,7 @@ #define QITERATOR_H #include <QtCore/qglobal.h> +#include <QtCore/qcontainertools_impl.h> QT_BEGIN_NAMESPACE @@ -253,26 +254,10 @@ public: return std::pair<Key, T>(i.key(), i.value()); } - struct pointer - { - pointer(value_type &&r_) : r(std::move(r_)) { } - - pointer() = default; - pointer(const pointer &other) = default; - pointer(pointer &&other) = default; - pointer &operator=(const pointer &other) = default; - pointer &operator=(pointer &&other) = default; - - value_type &operator*() const { return r; } - - value_type r; - const value_type *operator->() const { - return &r; - } - }; + using pointer = QtPrivate::ArrowProxy<value_type>; pointer operator->() const { - return pointer(std::pair<Key, T>(i.key(), i.value())); + return pointer{std::pair<Key, T>(i.key(), i.value())}; } friend bool operator==(QKeyValueIterator lhs, QKeyValueIterator rhs) noexcept { return lhs.i == rhs.i; } diff --git a/src/corelib/tools/qiterator.qdoc b/src/corelib/tools/qiterator.qdoc index f84d1cb3ba..041fb0701d 100644 --- a/src/corelib/tools/qiterator.qdoc +++ b/src/corelib/tools/qiterator.qdoc @@ -973,9 +973,8 @@ be preferred. QMutableHashIterator\<Key, T\> allows you to iterate over a QHash - (or a QMultiHash) and modify the hash. If you don't want to modify - the hash (or have a const QHash), use the slightly faster - QHashIterator instead. + and modify the hash. If you don't want to modify the hash (or have + a const QHash), use the slightly faster QHashIterator instead. The QMutableHashIterator constructor takes a QHash as argument. After construction, the iterator is located at the very beginning diff --git a/src/corelib/tools/qline.cpp b/src/corelib/tools/qline.cpp index 9216b8875b..e313b06aa9 100644 --- a/src/corelib/tools/qline.cpp +++ b/src/corelib/tools/qline.cpp @@ -14,6 +14,9 @@ QT_BEGIN_NAMESPACE \class QLine \inmodule QtCore \ingroup painting + \compares equality + \compareswith equality QLineF + \endcompareswith \brief The QLine class provides a two-dimensional vector using integer precision. @@ -133,18 +136,18 @@ QT_BEGIN_NAMESPACE */ /*! - \fn bool QLine::operator!=(const QLine &line) const + \fn bool QLine::operator!=(const QLine &lhs, const QLine &rhs) - Returns \c true if the given \a line is not the same as \e this line. + Returns \c true if the line \a lhs is not the same as line \a rhs. A line is different from another line if any of their start or end points differ, or the internal order of the points is different. */ /*! - \fn bool QLine::operator==(const QLine &line) const + \fn bool QLine::operator==(const QLine &lhs, const QLine &rhs) - Returns \c true if the given \a line is the same as \e this line. + Returns \c true if the line \a lhs is the same as line \a rhs. A line is identical to another line if the start and end points are identical, and the internal order of the points is the same. @@ -288,6 +291,9 @@ QDataStream &operator>>(QDataStream &stream, QLine &line) \class QLineF \inmodule QtCore \ingroup painting + \compares equality + \compareswith equality QLine + \endcompareswith \brief The QLineF class provides a two-dimensional vector using floating point precision. @@ -508,18 +514,18 @@ QDataStream &operator>>(QDataStream &stream, QLine &line) */ /*! - \fn bool QLineF::operator!=(const QLineF &line) const + \fn bool QLineF::operator!=(const QLineF &lhs, const QLineF &rhs) - Returns \c true if the given \a line is not the same as \e this line. + Returns \c true if the line \a lhs is not the same as line \a rhs. A line is different from another line if their start or end points differ, or the internal order of the points is different. */ /*! - \fn bool QLineF::operator==(const QLineF &line) const + \fn bool QLineF::operator==(const QLineF &lhs, const QLineF &rhs) - Returns \c true if the given \a line is the same as this line. + Returns \c true if the line \a lhs is the same as line \a rhs. A line is identical to another line if the start and end points are identical, and the internal order of the points is the same. @@ -781,6 +787,25 @@ qreal QLineF::angleTo(const QLineF &l) const return delta_normalized; } +/*! + \fn bool QLineF::qFuzzyCompare(const QLineF &lhs, const QLineF &rhs) + \since 6.8 + + Returns \c true if line \a lhs is approximately equal to line \a rhs; + otherwise returns \c false. + + The lines are considered approximately equal if their start and end + points are approximately equal. +*/ + +/*! + \fn bool QLineF::qFuzzyIsNull(const QLineF &line) + \since 6.8 + + Returns \c true if the start point of line \a line is approximately + equal to its end point; otherwise returns \c false. +*/ + #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const QLineF &p) { diff --git a/src/corelib/tools/qline.h b/src/corelib/tools/qline.h index e23ffbe9d5..03dac30e16 100644 --- a/src/corelib/tools/qline.h +++ b/src/corelib/tools/qline.h @@ -48,12 +48,20 @@ public: inline void setPoints(const QPoint &p1, const QPoint &p2); inline void setLine(int x1, int y1, int x2, int y2); +#if QT_CORE_REMOVED_SINCE(6, 8) constexpr inline bool operator==(const QLine &d) const noexcept; - constexpr inline bool operator!=(const QLine &d) const noexcept { return !(*this == d); } + constexpr inline bool operator!=(const QLine &d) const noexcept { return !operator==(d); } +#endif [[nodiscard]] constexpr inline QLineF toLineF() const noexcept; private: + friend constexpr bool comparesEqual(const QLine &lhs, const QLine &rhs) noexcept + { return lhs.pt1 == rhs.pt1 && lhs.pt2 == rhs.pt2; } +#if !QT_CORE_REMOVED_SINCE(6, 8) + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QLine) +#endif + QPoint pt1, pt2; }; Q_DECLARE_TYPEINFO(QLine, Q_PRIMITIVE_TYPE); @@ -161,10 +169,12 @@ inline void QLine::setLine(int aX1, int aY1, int aX2, int aY2) pt2 = QPoint(aX2, aY2); } +#if QT_CORE_REMOVED_SINCE(6, 8) constexpr inline bool QLine::operator==(const QLine &d) const noexcept { - return pt1 == d.pt1 && pt2 == d.pt2; + return comparesEqual(*this, d); } +#endif #ifndef QT_NO_DEBUG_STREAM Q_CORE_EXPORT QDebug operator<<(QDebug d, const QLine &p); @@ -233,12 +243,30 @@ public: inline void setPoints(const QPointF &p1, const QPointF &p2); inline void setLine(qreal x1, qreal y1, qreal x2, qreal y2); +#if QT_CORE_REMOVED_SINCE(6, 8) constexpr inline bool operator==(const QLineF &d) const; - constexpr inline bool operator!=(const QLineF &d) const { return !(*this == d); } + constexpr inline bool operator!=(const QLineF &d) const { return !operator==(d); } +#endif constexpr QLine toLine() const; private: + friend constexpr bool comparesEqual(const QLineF &lhs, const QLineF &rhs) noexcept + { return lhs.pt1 == rhs.pt1 && lhs.pt2 == rhs.pt2; } +#if !QT_CORE_REMOVED_SINCE(6, 8) + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QLineF) +#endif + + friend constexpr bool comparesEqual(const QLineF &lhs, const QLine &rhs) noexcept + { return comparesEqual(lhs, rhs.toLineF()); } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QLineF, QLine) + + friend constexpr bool qFuzzyCompare(const QLineF &lhs, const QLineF &rhs) noexcept + { return qFuzzyCompare(lhs.pt1, rhs.pt1) && qFuzzyCompare(lhs.pt2, rhs.pt2); } + + friend constexpr bool qFuzzyIsNull(const QLineF &line) noexcept + { return qFuzzyCompare(line.pt1, line.pt2); } + QPointF pt1, pt2; }; Q_DECLARE_TYPEINFO(QLineF, Q_PRIMITIVE_TYPE); @@ -283,7 +311,7 @@ constexpr inline qreal QLineF::y2() const constexpr inline bool QLineF::isNull() const { - return qFuzzyCompare(pt1.x(), pt2.x()) && qFuzzyCompare(pt1.y(), pt2.y()); + return qFuzzyCompare(pt1, pt2); } constexpr inline QPointF QLineF::p1() const @@ -383,12 +411,12 @@ inline void QLineF::setLine(qreal aX1, qreal aY1, qreal aX2, qreal aY2) pt2 = QPointF(aX2, aY2); } - +#if QT_CORE_REMOVED_SINCE(6, 8) constexpr inline bool QLineF::operator==(const QLineF &d) const { - return pt1 == d.pt1 && pt2 == d.pt2; + return comparesEqual(*this, d); } - +#endif #ifndef QT_NO_DEBUG_STREAM diff --git a/src/corelib/tools/qlist.h b/src/corelib/tools/qlist.h index 79de8fced0..1cced5acc2 100644 --- a/src/corelib/tools/qlist.h +++ b/src/corelib/tools/qlist.h @@ -10,6 +10,8 @@ #include <QtCore/qhashfunctions.h> #include <QtCore/qiterator.h> #include <QtCore/qcontainertools_impl.h> +#include <QtCore/qnamespace.h> +#include <QtCore/qttypetraits.h> #include <functional> #include <limits> @@ -116,8 +118,7 @@ public: public: using difference_type = qsizetype; using value_type = T; - // libstdc++ shipped with gcc < 11 does not have a fix for defect LWG 3346 -#if __cplusplus >= 202002L && (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 11) +#ifdef QT_COMPILER_HAS_LWG3346 using iterator_concept = std::contiguous_iterator_tag; using element_type = value_type; #endif @@ -187,8 +188,7 @@ public: public: using difference_type = qsizetype; using value_type = T; - // libstdc++ shipped with gcc < 11 does not have a fix for defect LWG 3346 -#if __cplusplus >= 202002L && (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE >= 11) +#ifdef QT_COMPILER_HAS_LWG3346 using iterator_concept = std::contiguous_iterator_tag; using element_type = const value_type; #endif @@ -259,6 +259,14 @@ private: const std::less<const T*> less = {}; return !less(d->end(), i.i) && !less(i.i, d->begin()); } + + void verify([[maybe_unused]] qsizetype pos = 0, [[maybe_unused]] qsizetype n = 1) const + { + Q_ASSERT(pos >= 0); + Q_ASSERT(pos <= size()); + Q_ASSERT(n >= 0); + Q_ASSERT(n <= size() - pos); + } public: QList(DataPointer dd) noexcept : d(dd) @@ -268,20 +276,20 @@ public: public: QList() = default; explicit QList(qsizetype size) - : d(Data::allocate(size)) + : d(size) { if (size) d->appendInitialize(size); } QList(qsizetype size, parameter_type t) - : d(Data::allocate(size)) + : d(size) { if (size) d->copyAppend(size, t); } inline QList(std::initializer_list<T> args) - : d(Data::allocate(qsizetype(args.size()))) + : d(qsizetype(args.size())) { if (args.size()) d->copyAppend(args.begin(), args.end()); @@ -289,10 +297,7 @@ public: QList<T> &operator=(std::initializer_list<T> args) { - d = DataPointer(Data::allocate(qsizetype(args.size()))); - if (args.size()) - d->copyAppend(args.begin(), args.end()); - return *this; + return assign(args); } template <typename InputIterator, if_input_iterator<InputIterator> = true> @@ -303,7 +308,7 @@ public: } else { const auto distance = std::distance(i1, i2); if (distance) { - d = DataPointer(Data::allocate(qsizetype(distance))); + d = DataPointer(qsizetype(distance)); // appendIteratorRange can deal with contiguous iterators on its own, // this is an optimization for C++17 code. if constexpr (std::is_same_v<std::decay_t<InputIterator>, iterator> || @@ -321,6 +326,13 @@ public: inline explicit QList(const String &str) { append(str); } + QList(qsizetype size, Qt::Initialization) + : d(size) + { + if (size) + d->appendUninitialized(size); + } + // compiler-generated special member functions are fine! void swap(QList &other) noexcept { d.swap(other.d); } @@ -401,6 +413,12 @@ public: if (size > this->size()) d->copyAppend(size - this->size(), c); } + void resizeForOverwrite(qsizetype size) + { + resize_internal(size); + if (size > this->size()) + d->appendUninitialized(size); + } inline qsizetype capacity() const { return qsizetype(d->constAllocatedCapacity()); } void reserve(qsizetype size); @@ -419,7 +437,7 @@ public: return; if (d->needsDetach()) { // must allocate memory - DataPointer detached(Data::allocate(d.allocatedCapacity())); + DataPointer detached(d.allocatedCapacity()); d.swap(detached); } else { d->truncate(0); @@ -636,27 +654,13 @@ public: QList<T> mid(qsizetype pos, qsizetype len = -1) const; QList<T> first(qsizetype n) const - { - Q_ASSERT(size_t(n) <= size_t(size())); - return QList<T>(begin(), begin() + n); - } + { verify(0, n); return QList<T>(begin(), begin() + n); } QList<T> last(qsizetype n) const - { - Q_ASSERT(size_t(n) <= size_t(size())); - return QList<T>(end() - n, end()); - } + { verify(0, n); return QList<T>(end() - n, end()); } QList<T> sliced(qsizetype pos) const - { - Q_ASSERT(size_t(pos) <= size_t(size())); - return QList<T>(begin() + pos, end()); - } + { verify(pos, 0); return QList<T>(begin() + pos, end()); } QList<T> sliced(qsizetype pos, qsizetype n) const - { - Q_ASSERT(size_t(pos) <= size_t(size())); - Q_ASSERT(n >= 0); - Q_ASSERT(pos + n <= size()); - return QList<T>(begin() + pos, begin() + pos + n); - } + { verify(pos, n); return QList<T>(begin() + pos, begin() + pos + n); } T value(qsizetype i) const { return value(i, T()); } T value(qsizetype i, parameter_type defaultValue) const; @@ -686,6 +690,10 @@ public: inline reference back() { return last(); } inline const_reference back() const noexcept { return last(); } void shrink_to_fit() { squeeze(); } + static qsizetype max_size() noexcept + { + return Data::max_size(); + } // comfort QList<T> &operator+=(const QList<T> &l) { append(l); return *this; } @@ -756,7 +764,7 @@ void QList<T>::reserve(qsizetype asize) } } - DataPointer detached(Data::allocate(qMax(asize, size()))); + DataPointer detached(qMax(asize, size())); detached->copyAppend(d->begin(), d->end()); if (detached.d_ptr()) detached->setFlag(Data::CapacityReserved); @@ -770,7 +778,7 @@ inline void QList<T>::squeeze() return; if (d->needsDetach() || size() < capacity()) { // must allocate memory - DataPointer detached(Data::allocate(size())); + DataPointer detached(size()); if (size()) { if (d.needsDetach()) detached->copyAppend(d.data(), d.data() + d.size); @@ -899,7 +907,7 @@ inline QList<T> &QList<T>::fill(parameter_type t, qsizetype newSize) newSize = size(); if (d->needsDetach() || newSize > capacity()) { // must allocate memory - DataPointer detached(Data::allocate(d->detachCapacity(newSize))); + DataPointer detached(d->detachCapacity(newSize)); detached->copyAppend(newSize, t); d.swap(detached); } else { @@ -981,7 +989,7 @@ inline QList<T> QList<T>::mid(qsizetype pos, qsizetype len) const } // Allocate memory - DataPointer copied(Data::allocate(l)); + DataPointer copied(l); copied->copyAppend(data() + p, data() + p + l); return copied; } diff --git a/src/corelib/tools/qlist.qdoc b/src/corelib/tools/qlist.qdoc index 554d07ec1b..e07b74cd53 100644 --- a/src/corelib/tools/qlist.qdoc +++ b/src/corelib/tools/qlist.qdoc @@ -247,6 +247,31 @@ \sa resize() */ +/*! \fn template <typename T> QList<T>::QList(qsizetype size, Qt::Initialization) + \since 6.8 + + Constructs a list with an initial size of \a size elements. + + QList will make an attempt at \b{not initializing} the elements. + +//! [qlist-uninitialized-strategy] + Specifically: + + \list + + \li if \c{T} has a constructor that accepts \c{Qt::Uninitialized}, + that constructor will be used to initialize the elements; + + \li otherwise, each element is default constructed. For + trivially constructible types (such as \c{int}, \c{float}, etc.) + this is equivalent to not initializing them. + + \endlist +//! [qlist-uninitialized-strategy] + + \sa resizeForOverwrite() +*/ + /*! \fn template <typename T> QList<T>::QList(qsizetype size, parameter_type value) Constructs a list with an initial size of \a size elements. @@ -274,7 +299,7 @@ Constructs a list from the std::initializer_list given by \a args. */ -/*! \fn template <typename T> template<typename InputIterator, if_input_iterator<InputIterator>> QList<T>::QList(InputIterator first, InputIterator last) +/*! \fn template<typename T> template <typename InputIterator, QList<T>::if_input_iterator<InputIterator> = true> QList<T>::QList(InputIterator first, InputIterator last) \since 5.14 Constructs a list with the contents in the iterator range [\a first, \a last). @@ -426,12 +451,13 @@ */ /*! \fn template <typename T> void QList<T>::resize(qsizetype size) + \fn template <typename T> void QList<T>::resize(qsizetype size, parameter_type c) \since 6.0 Sets the size of the list to \a size. If \a size is greater than the current size, elements are added to the end; the new elements are - initialized with a \l{default-constructed value}. If \a size is less - than the current size, elements are removed from the end. + initialized with either a \l{default-constructed value} or \a c. If \a size + is less than the current size, elements are removed from the end. If this list is not shared, the capacity() is preserved. Use squeeze() to shed excess capacity. @@ -443,6 +469,17 @@ \sa size() */ +/*! \fn template <typename T> void QList<T>::resizeForOverwrite(qsizetype size) + \since 6.8 + + Sets the size of the list to \a size. If \a size is less than the + current size, elements are removed from the end. If \a size is + greater than the current size, elements are added to the end; QList + will make an attempt at \b{not initializing} these new elements. + + \include qlist.qdoc qlist-uninitialized-strategy +*/ + /*! \fn template <typename T> qsizetype QList<T>::capacity() const Returns the maximum number of items that can be stored in the @@ -1292,6 +1329,15 @@ returns \c false. */ +/*! \fn template <typename T> qsizetype QList<T>::max_size() + \since 6.8 + + This function is provided for STL compatibility. + It returns the maximum number of elements that the list can + theoretically hold. In practice, the number can be much smaller, + limited by the amount of memory available to the system. +*/ + /*! \fn template <typename T> QList<T> &QList<T>::operator+=(const QList<T> &other) Appends the items of the \a other list to this list and @@ -1538,7 +1584,7 @@ \sa erase */ -/*! \fn template <typename T> QList<T>::assign(qsizetype n, parameter_type t) +/*! \fn template <typename T> QList<T>& QList<T>::assign(qsizetype n, parameter_type t) \since 6.6 Replaces the contents of this list with \a n copies of \a t. @@ -1549,7 +1595,7 @@ list or this list is shared. */ -/*! \fn template <typename T> template <typename InputIterator, if_input_iterator<InputIterator>> QList<T>::assign(InputIterator first, InputIterator last) +/*! \fn template <typename T> template <typename InputIterator, QList<T>::if_input_iterator<InputIterator>> QList<T>& QList<T>::assign(InputIterator first, InputIterator last) \since 6.6 Replaces the contents of this list with a copy of the elements in the @@ -1569,7 +1615,7 @@ *this. */ -/*! \fn template <typename T> QList<T>::assign(std::initializer_list<T> l) +/*! \fn template <typename T> QList<T>& QList<T>::assign(std::initializer_list<T> l) \since 6.6 Replaces the contents of this list with a copy of the elements of diff --git a/src/corelib/tools/qmap.h b/src/corelib/tools/qmap.h index 1a6506348f..326ae7d8a5 100644 --- a/src/corelib/tools/qmap.h +++ b/src/corelib/tools/qmap.h @@ -5,12 +5,14 @@ #ifndef QMAP_H #define QMAP_H +#include <QtCore/qhashfunctions.h> #include <QtCore/qiterator.h> #include <QtCore/qlist.h> #include <QtCore/qrefcount.h> #include <QtCore/qpair.h> #include <QtCore/qshareddata.h> #include <QtCore/qshareddata_impl.h> +#include <QtCore/qttypetraits.h> #include <functional> #include <initializer_list> @@ -762,7 +764,7 @@ public: return isEmpty(); } - QPair<iterator, iterator> equal_range(const Key &akey) + std::pair<iterator, iterator> equal_range(const Key &akey) { const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach detach(); @@ -770,13 +772,38 @@ public: return {iterator(result.first), iterator(result.second)}; } - QPair<const_iterator, const_iterator> equal_range(const Key &akey) const + std::pair<const_iterator, const_iterator> equal_range(const Key &akey) const { if (!d) return {}; auto result = d->m.equal_range(akey); return {const_iterator(result.first), const_iterator(result.second)}; } + +private: +#ifdef Q_QDOC + friend size_t qHash(const QMap &key, size_t seed = 0); +#else +# if defined(Q_CC_GHS) || defined (Q_CC_MSVC) + // GHS and MSVC tries to intantiate qHash() for the noexcept running into a + // non-SFINAE'ed hard error... Create an artificial SFINAE context as a + // work-around: + template <typename M, std::enable_if_t<std::is_same_v<M, QMap>, bool> = true> + friend QtPrivate::QHashMultiReturnType<typename M::key_type, typename M::mapped_type> +# else + using M = QMap; + friend size_t +# endif + qHash(const M &key, size_t seed = 0) + noexcept(QHashPrivate::noexceptPairHash<typename M::key_type, typename M::mapped_type>()) + { + if (!key.d) + return seed; + // don't use qHashRange to avoid its compile-time overhead: + return std::accumulate(key.d->m.begin(), key.d->m.end(), seed, + QtPrivate::QHashCombine{}); + } +#endif // !Q_QDOC }; Q_DECLARE_ASSOCIATIVE_ITERATOR(Map) @@ -788,6 +815,7 @@ qsizetype erase_if(QMap<Key, T> &map, Predicate pred) return QtPrivate::associative_erase_if(map, pred); } + // // QMultiMap // @@ -1492,7 +1520,7 @@ public: // STL compatibility inline bool empty() const { return isEmpty(); } - QPair<iterator, iterator> equal_range(const Key &akey) + std::pair<iterator, iterator> equal_range(const Key &akey) { const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key` alive across the detach detach(); @@ -1500,7 +1528,7 @@ public: return {iterator(result.first), iterator(result.second)}; } - QPair<const_iterator, const_iterator> equal_range(const Key &akey) const + std::pair<const_iterator, const_iterator> equal_range(const Key &akey) const { if (!d) return {}; diff --git a/src/corelib/tools/qmap.qdoc b/src/corelib/tools/qmap.qdoc index bbf20ef02b..0cabf3df38 100644 --- a/src/corelib/tools/qmap.qdoc +++ b/src/corelib/tools/qmap.qdoc @@ -1,6 +1,6 @@ // Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> // Copyright (C) 2016 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 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! \class QMap @@ -390,7 +390,7 @@ use that entails can be avoided by iterating from \l keyBegin() to \l keyEnd(). - \sa QMultiMap::uniqueKeys(), values(), key() + \sa values(), key() */ /*! \fn template <class Key, class T> QList<Key> QMap<Key, T>::keys(const T &value) const @@ -730,7 +730,7 @@ If there is already an item with the key \a key, that item's value is replaced with \a value. - \sa QMultiMap::insert() + Returns an iterator pointing to the new/updated element. */ /*! \fn template <class Key, class T> QMap<Key, T>::iterator QMap<Key, T>::insert(const_iterator pos, const Key &key, const T &value) @@ -756,7 +756,7 @@ \b {Note:} Be careful with the hint. Providing an iterator from an older shared instance might crash but there is also a risk that it will silently corrupt both the map and the \a pos map. - \sa QMultiMap::insert() + Returns an iterator pointing to the new/updated element. */ /*! \fn template <class Key, class T> void QMap<Key, T>::insert(const QMap<Key, T> &map) @@ -766,8 +766,6 @@ If a key is common to both maps, its value will be replaced with the value stored in \a map. - - \sa QMultiMap::insert() */ /*! \fn template <class Key, class T> void QMap<Key, T>::insert(QMap<Key, T> &&map) @@ -820,14 +818,14 @@ */ /*! - \fn template <class Key, class T> QPair<typename QMap<Key, T>::iterator, typename QMap<Key, T>::iterator> QMap<Key, T>::equal_range(const Key &key) + \fn template <class Key, class T> std::pair<typename QMap<Key, T>::iterator, typename QMap<Key, T>::iterator> QMap<Key, T>::equal_range(const Key &key) Returns a pair of iterators delimiting the range of values \c{[first, second)}, that are stored under \a key. */ /*! - \fn template <class Key, class T> QPair<typename QMap<Key, T>::const_iterator, typename QMap<Key, T>::const_iterator> QMap<Key, T>::equal_range(const Key &key) const + \fn template <class Key, class T> std::pair<typename QMap<Key, T>::const_iterator, typename QMap<Key, T>::const_iterator> QMap<Key, T>::equal_range(const Key &key) const \overload \since 5.6 */ @@ -1391,3 +1389,12 @@ Returns the number of elements removed, if any. */ + +/*! + \fn template <class Key, class T> size_t QMap<Key, T>::qHash(const QMap &key, size_t seed) noexcept + \since 6.8 + + Returns the hash value for \a key, using \a seed to seed the calculation. + + Types \c Key and \c T must be supported by qHash(). +*/ diff --git a/src/corelib/tools/qmargins.cpp b/src/corelib/tools/qmargins.cpp index 1d2cb7d6e5..c4cd0da30d 100644 --- a/src/corelib/tools/qmargins.cpp +++ b/src/corelib/tools/qmargins.cpp @@ -14,6 +14,10 @@ QT_BEGIN_NAMESPACE \ingroup painting \since 4.6 + \compares equality + \compareswith equality QMarginsF + \endcompareswith + \brief The QMargins class defines the four margins of a rectangle. QMargin defines a set of four margins; left, top, right, and bottom, @@ -107,15 +111,15 @@ QT_BEGIN_NAMESPACE */ /*! - \fn bool QMargins::operator==(const QMargins &m1, const QMargins &m2) + \fn bool QMargins::operator==(const QMargins &lhs, const QMargins &rhs) - Returns \c true if \a m1 and \a m2 are equal; otherwise returns \c false. + Returns \c true if \a lhs and \a rhs are equal; otherwise returns \c false. */ /*! - \fn bool QMargins::operator!=(const QMargins &m1, const QMargins &m2) + \fn bool QMargins::operator!=(const QMargins &lhs, const QMargins &rhs) - Returns \c true if \a m1 and \a m2 are different; otherwise returns \c false. + Returns \c true if \a lhs and \a rhs are different; otherwise returns \c false. */ /*! @@ -438,6 +442,10 @@ QDebug operator<<(QDebug dbg, const QMargins &m) \ingroup painting \since 5.3 + \compares equality + \compareswith equality QMargins + \endcompareswith + \brief The QMarginsF class defines the four margins of a rectangle. QMarginsF defines a set of four margins; left, top, right, and bottom, @@ -746,6 +754,22 @@ QDebug operator<<(QDebug dbg, const QMargins &m) \sa QMarginsF(), QMargins::toMarginsF() */ +/*! + \fn bool QMarginsF::qFuzzyCompare(const QMarginsF &lhs, const QMarginsF &rhs) + \since 6.8 + + Returns \c true if \a lhs is approximately equal to \a rhs; + otherwise returns \c false. +*/ + +/*! + \fn bool QMarginsF::qFuzzyIsNull(const QMarginsF &margins) + \since 6.8 + + Returns \c true if all components of margsins \a margins are + approximately equal to zero; otherwise returns \c false. +*/ + /***************************************************************************** QMarginsF stream functions *****************************************************************************/ diff --git a/src/corelib/tools/qmargins.h b/src/corelib/tools/qmargins.h index f8d7150dfd..3b29860d66 100644 --- a/src/corelib/tools/qmargins.h +++ b/src/corelib/tools/qmargins.h @@ -4,6 +4,7 @@ #ifndef QMARGINS_H #define QMARGINS_H +#include <QtCore/qcompare.h> #include <QtCore/qnamespace.h> #include <QtCore/q20type_traits.h> @@ -54,19 +55,14 @@ private: int m_right; int m_bottom; - friend constexpr inline bool operator==(const QMargins &m1, const QMargins &m2) noexcept + friend constexpr bool comparesEqual(const QMargins &lhs, const QMargins &rhs) noexcept { - return - m1.m_left == m2.m_left && - m1.m_top == m2.m_top && - m1.m_right == m2.m_right && - m1.m_bottom == m2.m_bottom; - } - - friend constexpr inline bool operator!=(const QMargins &m1, const QMargins &m2) noexcept - { - return !(m1 == m2); + return lhs.m_left == rhs.m_left + && lhs.m_top == rhs.m_top + && lhs.m_right == rhs.m_right + && lhs.m_bottom == rhs.m_bottom; } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QMargins) template <std::size_t I, typename M, @@ -304,18 +300,35 @@ private: qreal m_right; qreal m_bottom; - friend constexpr inline bool operator==(const QMarginsF &lhs, const QMarginsF &rhs) noexcept + QT_WARNING_PUSH + QT_WARNING_DISABLE_FLOAT_COMPARE + friend constexpr bool qFuzzyCompare(const QMarginsF &lhs, const QMarginsF &rhs) noexcept { - return qFuzzyCompare(lhs.left(), rhs.left()) - && qFuzzyCompare(lhs.top(), rhs.top()) - && qFuzzyCompare(lhs.right(), rhs.right()) - && qFuzzyCompare(lhs.bottom(), rhs.bottom()); + return ((!lhs.m_left || !rhs.m_left) ? qFuzzyIsNull(lhs.m_left - rhs.m_left) + : qFuzzyCompare(lhs.m_left, rhs.m_left)) + && ((!lhs.m_top || !rhs.m_top) ? qFuzzyIsNull(lhs.m_top - rhs.m_top) + : qFuzzyCompare(lhs.m_top, rhs.m_top)) + && ((!lhs.m_right || !rhs.m_right) ? qFuzzyIsNull(lhs.m_right - rhs.m_right) + : qFuzzyCompare(lhs.m_right, rhs.m_right)) + && ((!lhs.m_bottom || !rhs.m_bottom) ? qFuzzyIsNull(lhs.m_bottom - rhs.m_bottom) + : qFuzzyCompare(lhs.m_bottom, rhs.m_bottom)); + } + QT_WARNING_POP + friend constexpr bool qFuzzyIsNull(const QMarginsF &m) noexcept + { + return qFuzzyIsNull(m.m_left) && qFuzzyIsNull(m.m_top) + && qFuzzyIsNull(m.m_right) && qFuzzyIsNull(m.m_bottom); } - friend constexpr inline bool operator!=(const QMarginsF &lhs, const QMarginsF &rhs) noexcept + friend constexpr bool comparesEqual(const QMarginsF &lhs, const QMarginsF &rhs) noexcept { - return !(lhs == rhs); + return qFuzzyCompare(lhs, rhs); } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QMarginsF) + + friend constexpr bool comparesEqual(const QMarginsF &lhs, const QMargins &rhs) noexcept + { return comparesEqual(lhs, rhs.toMarginsF()); } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QMarginsF, QMargins) template <std::size_t I, typename M, diff --git a/src/corelib/tools/qmessageauthenticationcode.h b/src/corelib/tools/qmessageauthenticationcode.h index 4e88138763..7c5edfa7bf 100644 --- a/src/corelib/tools/qmessageauthenticationcode.h +++ b/src/corelib/tools/qmessageauthenticationcode.h @@ -55,6 +55,30 @@ public: static QByteArray hash(QByteArrayView message, QByteArrayView key, QCryptographicHash::Algorithm method); + static QByteArrayView + hashInto(QSpan<char> buffer, QByteArrayView message, QByteArrayView key, + QCryptographicHash::Algorithm method) noexcept + { return hashInto(as_writable_bytes(buffer), {&message, 1}, key, method); } + static QByteArrayView + hashInto(QSpan<uchar> buffer, QByteArrayView message, QByteArrayView key, + QCryptographicHash::Algorithm method) noexcept + { return hashInto(as_writable_bytes(buffer), {&message, 1}, key, method); } + static QByteArrayView + hashInto(QSpan<std::byte> buffer, QByteArrayView message, + QByteArrayView key, QCryptographicHash::Algorithm method) noexcept + { return hashInto(buffer, {&message, 1}, key, method); } + static QByteArrayView + hashInto(QSpan<char> buffer, QSpan<const QByteArrayView> messageParts, + QByteArrayView key, QCryptographicHash::Algorithm method) noexcept + { return hashInto(as_writable_bytes(buffer), messageParts, key, method); } + static QByteArrayView + hashInto(QSpan<uchar> buffer, QSpan<const QByteArrayView> messageParts, + QByteArrayView key, QCryptographicHash::Algorithm method) noexcept + { return hashInto(as_writable_bytes(buffer), messageParts, key, method); } + static QByteArrayView + hashInto(QSpan<std::byte> buffer, QSpan<const QByteArrayView> message, + QByteArrayView key, QCryptographicHash::Algorithm method) noexcept; + private: Q_DISABLE_COPY(QMessageAuthenticationCode) QMessageAuthenticationCodePrivate *d; diff --git a/src/corelib/tools/qminimalflatset_p.h b/src/corelib/tools/qminimalflatset_p.h new file mode 100644 index 0000000000..6074688f6e --- /dev/null +++ b/src/corelib/tools/qminimalflatset_p.h @@ -0,0 +1,156 @@ +// Copyright (C) 2022 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 QTCORE_QMINIMALFLATSET_P_H +#define QTCORE_QMINIMALFLATSET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qcontainerfwd.h> +#include <QtCore/qfunctionaltools_impl.h> // CompactStorage +#include <QtCore/private/qglobal_p.h> + +//#define QMINIMAL_FLAT_SET_DEBUG +#ifdef QMINIMAL_FLAT_SET_DEBUG +# include <QtCore/qscopeguard.h> +# include <QtCore/qdebug.h> +# define QMINIMAL_FLAT_SET_PRINT_AT_END \ + const auto sg = qScopeGuard([&] { qDebug() << this << *this; }); +#else +# define QMINIMAL_FLAT_SET_PRINT_AT_END +#endif + +#include <algorithm> // for std::lower_bound +#include <functional> // for std::less, std::ref + +QT_BEGIN_NAMESPACE + +/* + This is a minimal version of a QFlatSet, the std::set version of QFlatMap. + Like QFlatMap, it has linear insertion and removal, not logarithmic, like + real QMap and std::set, so it's only a good container if you either have + very few entries or lots, but with separate setup and lookup stages. + Because a full QFlatSet would be 10x the work on writing this minimal one, + we keep it here for now. When more users pop up and the class has matured a + bit, we can consider moving it as QFlatSet alongside QFlatMap. +*/ + +template <typename T, typename Container = QList<T>, typename Compare = std::less<T>> +class QMinimalFlatSet : QtPrivate::CompactStorage<Compare> +{ + Container c; + using CompareStorage = QtPrivate::CompactStorage<Compare>; +public: + QMinimalFlatSet() = default; + explicit QMinimalFlatSet(const Compare &cmp) : CompareStorage{cmp} {} + // Rule Of Zero applies + + using const_iterator = typename Container::const_iterator; + using iterator = const_iterator; + using const_reverse_iterator = typename Container::const_reverse_iterator; + using reverse_iterator = const_reverse_iterator; + using value_type = T; + using key_compare = Compare; + using value_compare = Compare; + + key_compare key_comp() const { return this->object(); } + value_compare value_comp() const { return key_comp(); } + + iterator begin() const { return c.cbegin(); } + iterator end() const { return c.cend(); } + iterator cbegin() const { return begin(); } + iterator cend() const { return cend(); } + + reverse_iterator rbegin() const { return c.crbegin(); } + reverse_iterator rend() const { return c.crend(); } + reverse_iterator crbegin() const { return rbegin(); } + reverse_iterator crend() const { return rend(); } + + void clear() { + QMINIMAL_FLAT_SET_PRINT_AT_END + c.clear(); + } + auto size() const { return c.size(); } + auto count() const { return size(); } + bool isEmpty() const { return size() == 0; } + + std::pair<iterator, bool> insert(value_type &&v) + { + QMINIMAL_FLAT_SET_PRINT_AT_END + const auto r = lookup(v); + if (r.exists) + return {r.it, false}; + else + return {c.insert(r.it, std::move(v)), true}; + } + + std::pair<iterator, bool> insert(const value_type &v) + { + QMINIMAL_FLAT_SET_PRINT_AT_END + const auto r = lookup(v); + if (r.exists) + return {r.it, false}; + else + return {c.insert(r.it, v), true}; + } + + void erase(const value_type &v) + { + QMINIMAL_FLAT_SET_PRINT_AT_END + const auto r = lookup(v); + if (r.exists) + c.erase(r.it); + } + void remove(const value_type &v) { erase(v); } + + bool contains(const value_type &v) const + { + return lookup(v).exists; + } + + const Container &values() const & { return c; } + Container values() && { return std::move(c); } + +private: + auto lookup(const value_type &v) const + { + struct R { + iterator it; + bool exists; + }; + + auto cmp = std::ref(this->object()); // don't let std::lower_bound copy it + + const auto it = std::lower_bound(c.cbegin(), c.cend(), v, cmp); + return R{it, it != c.cend() && !cmp(v, *it)}; + } + +#ifdef QMINIMAL_FLAT_SET_DEBUG + friend QDebug operator<<(QDebug dbg, const QMinimalFlatSet &set) + { + const QDebugStateSaver saver(dbg); + dbg.nospace() << "QMinimalFlatSet{"; + for (auto &e : set) + dbg << e << ", "; + return dbg << "}"; + } +#endif +}; + +#undef QMINIMAL_FLAT_SET_PRINT_AT_END + +template <typename T, qsizetype N = QVarLengthArrayDefaultPrealloc> +using QMinimalVarLengthFlatSet = QMinimalFlatSet<T, QVarLengthArray<T, N>>; + +QT_END_NAMESPACE + +#endif // QTCORE_QMINIMALFLATSET_P_H diff --git a/src/corelib/tools/qmultimap.qdoc b/src/corelib/tools/qmultimap.qdoc index 5bc4af0940..0b05192817 100644 --- a/src/corelib/tools/qmultimap.qdoc +++ b/src/corelib/tools/qmultimap.qdoc @@ -1,6 +1,6 @@ // Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> // Copyright (C) 2020 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 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! \class QMultiMap @@ -817,6 +817,8 @@ different from replace(), which overwrites the value of an existing item.) + Returns an iterator pointing to the new element. + \sa replace() */ @@ -839,6 +841,8 @@ is faster than inserting in sorted order with constEnd(), since constEnd() - 1 (which is needed to check if the hint is valid) needs \l{logarithmic time}. + Returns an iterator pointing to the new element. + \b {Note:} Be careful with the hint. Providing an iterator from an older shared instance might crash but there is also a risk that it will silently corrupt both the multi map and the \a pos multi map. */ @@ -886,6 +890,8 @@ If there are multiple items with the key \a key, the most recently inserted item's value is replaced with \a value. + Returns an iterator pointing to the new/updated element. + \sa insert() */ @@ -928,14 +934,14 @@ */ /*! - \fn template <class Key, class T> QPair<typename QMultiMap<Key, T>::iterator, typename QMultiMap<Key, T>::iterator> QMultiMap<Key, T>::equal_range(const Key &key) + \fn template <class Key, class T> std::pair<typename QMultiMap<Key, T>::iterator, typename QMultiMap<Key, T>::iterator> QMultiMap<Key, T>::equal_range(const Key &key) Returns a pair of iterators delimiting the range of values \c{[first, second)}, that are stored under \a key. */ /*! - \fn template <class Key, class T> QPair<typename QMultiMap<Key, T>::const_iterator, typename QMultiMap<Key, T>::const_iterator> QMultiMap<Key, T>::equal_range(const Key &key) const + \fn template <class Key, class T> std::pair<typename QMultiMap<Key, T>::const_iterator, typename QMultiMap<Key, T>::const_iterator> QMultiMap<Key, T>::equal_range(const Key &key) const \overload \since 5.6 */ diff --git a/src/corelib/tools/qpair.h b/src/corelib/tools/qpair.h index 8070ca0175..84f99075e1 100644 --- a/src/corelib/tools/qpair.h +++ b/src/corelib/tools/qpair.h @@ -4,6 +4,7 @@ #ifndef QPAIR_H #define QPAIR_H +#include <QtCore/qcontainerfwd.h> #include <QtCore/qglobal.h> QT_BEGIN_NAMESPACE @@ -12,8 +13,7 @@ QT_BEGIN_NAMESPACE #pragma qt_class(QPair) #endif -template <typename T1, typename T2> -using QPair = std::pair<T1, T2>; +#ifndef QT_NO_QPAIR template <typename T1, typename T2> constexpr decltype(auto) qMakePair(T1 &&value1, T2 &&value2) @@ -22,6 +22,8 @@ constexpr decltype(auto) qMakePair(T1 &&value1, T2 &&value2) return std::make_pair(std::forward<T1>(value1), std::forward<T2>(value2)); } +#endif // QT_NO_QPAIR + QT_END_NAMESPACE #endif // QPAIR_H diff --git a/src/corelib/tools/qpoint.cpp b/src/corelib/tools/qpoint.cpp index d1f3b12a68..775a354469 100644 --- a/src/corelib/tools/qpoint.cpp +++ b/src/corelib/tools/qpoint.cpp @@ -15,6 +15,10 @@ QT_BEGIN_NAMESPACE \ingroup painting \reentrant + \compares equality + \compareswith equality QPointF + \endcompareswith + \brief The QPoint class defines a point in the plane using integer precision. @@ -208,16 +212,17 @@ QT_BEGIN_NAMESPACE */ /*! - \fn bool QPoint::operator==(const QPoint &p1, const QPoint &p2) + \fn bool QPoint::operator==(const QPoint &lhs, const QPoint &rhs) - Returns \c true if \a p1 and \a p2 are equal; otherwise returns - false. + Returns \c true if \a lhs and \a rhs are equal; otherwise returns + \c false. */ /*! - \fn bool QPoint::operator!=(const QPoint &p1, const QPoint &p2) + \fn bool QPoint::operator!=(const QPoint &lhs, const QPoint &rhs) - Returns \c true if \a p1 and \a p2 are not equal; otherwise returns \c false. + Returns \c true if \a lhs and \a rhs are not equal; otherwise returns + \c false. */ /*! @@ -463,6 +468,10 @@ size_t qHash(QPoint key, size_t seed) noexcept \ingroup painting \reentrant + \compares equality + \compareswith equality QPoint + \endcompareswith + \brief The QPointF class defines a point in the plane using floating point precision. @@ -730,9 +739,9 @@ size_t qHash(QPoint key, size_t seed) noexcept */ /*! - \fn bool QPointF::operator==(const QPointF &p1, const QPointF &p2) + \fn bool QPointF::operator==(const QPointF &lhs, const QPointF &rhs) - Returns \c true if \a p1 is approximately equal to \a p2; otherwise + Returns \c true if \a lhs is approximately equal to \a rhs; otherwise returns \c false. \warning This function does not check for strict equality; instead, @@ -742,9 +751,9 @@ size_t qHash(QPoint key, size_t seed) noexcept */ /*! - \fn bool QPointF::operator!=(const QPointF &p1, const QPointF &p2); + \fn bool QPointF::operator!=(const QPointF &lhs, const QPointF &rhs) - Returns \c true if \a p1 is sufficiently different from \a p2; + Returns \c true if \a lhs is sufficiently different from \a rhs; otherwise returns \c false. \warning This function does not check for strict inequality; instead, @@ -753,6 +762,26 @@ size_t qHash(QPoint key, size_t seed) noexcept \sa qFuzzyCompare */ +/*! + \fn bool QPointF::qFuzzyCompare(const QPointF &p1, const QPointF &p2) + \since 6.8 + + Returns \c true if \a p1 is approximately equal to \a p2; otherwise + returns \c false. + + \sa qFuzzyIsNull +*/ + +/*! + \fn bool QPointF::qFuzzyIsNull(const QPointF &point) + \since 6.8 + + Returns \c true if \a point is approximately equal to a point + \c {(0.0, 0.0)}. + + \sa qFuzzyCompare +*/ + #ifndef QT_NO_DATASTREAM /*! \fn QDataStream &operator<<(QDataStream &stream, const QPointF &point) diff --git a/src/corelib/tools/qpoint.h b/src/corelib/tools/qpoint.h index 7df4d49005..50b4c864be 100644 --- a/src/corelib/tools/qpoint.h +++ b/src/corelib/tools/qpoint.h @@ -4,7 +4,9 @@ #ifndef QPOINT_H #define QPOINT_H +#include <QtCore/qcompare.h> #include <QtCore/qnamespace.h> +#include <QtCore/qnumeric.h> #include <QtCore/q20type_traits.h> #include <QtCore/q23utility.h> @@ -51,10 +53,10 @@ public: constexpr static inline int dotProduct(const QPoint &p1, const QPoint &p2) { return p1.xp * p2.xp + p1.yp * p2.yp; } - friend constexpr inline bool operator==(const QPoint &p1, const QPoint &p2) noexcept +private: + friend constexpr bool comparesEqual(const QPoint &p1, const QPoint &p2) noexcept { return p1.xp == p2.xp && p1.yp == p2.yp; } - friend constexpr inline bool operator!=(const QPoint &p1, const QPoint &p2) noexcept - { return p1.xp != p2.xp || p1.yp != p2.yp; } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QPoint) friend constexpr inline QPoint operator+(const QPoint &p1, const QPoint &p2) noexcept { return QPoint(p1.xp + p2.xp, p1.yp + p2.yp); } friend constexpr inline QPoint operator-(const QPoint &p1, const QPoint &p2) noexcept @@ -78,6 +80,7 @@ public: friend constexpr inline QPoint operator/(const QPoint &p, qreal c) { return QPoint(qRound(p.xp / c), qRound(p.yp / c)); } +public: #if defined(Q_OS_DARWIN) || defined(Q_QDOC) [[nodiscard]] Q_CORE_EXPORT CGPoint toCGPoint() const noexcept; #endif @@ -241,19 +244,25 @@ public: return p1.xp * p2.xp + p1.yp * p2.yp; } +private: QT_WARNING_PUSH QT_WARNING_DISABLE_FLOAT_COMPARE - friend constexpr inline bool operator==(const QPointF &p1, const QPointF &p2) + friend constexpr bool qFuzzyCompare(const QPointF &p1, const QPointF &p2) noexcept { return ((!p1.xp || !p2.xp) ? qFuzzyIsNull(p1.xp - p2.xp) : qFuzzyCompare(p1.xp, p2.xp)) && ((!p1.yp || !p2.yp) ? qFuzzyIsNull(p1.yp - p2.yp) : qFuzzyCompare(p1.yp, p2.yp)); } - friend constexpr inline bool operator!=(const QPointF &p1, const QPointF &p2) + QT_WARNING_POP + friend constexpr bool qFuzzyIsNull(const QPointF &point) noexcept { - return !(p1 == p2); + return qFuzzyIsNull(point.xp) && qFuzzyIsNull(point.yp); } - QT_WARNING_POP - + friend constexpr bool comparesEqual(const QPointF &p1, const QPointF &p2) noexcept + { return qFuzzyCompare(p1, p2); } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QPointF) + friend constexpr bool comparesEqual(const QPointF &p1, const QPoint &p2) noexcept + { return comparesEqual(p1, p2.toPointF()); } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QPointF, QPoint) friend constexpr inline QPointF operator+(const QPointF &p1, const QPointF &p2) { return QPointF(p1.xp + p2.xp, p1.yp + p2.yp); } friend constexpr inline QPointF operator-(const QPointF &p1, const QPointF &p2) @@ -272,6 +281,7 @@ public: return QPointF(p.xp / divisor, p.yp / divisor); } +public: constexpr QPoint toPoint() const; #if defined(Q_OS_DARWIN) || defined(Q_QDOC) diff --git a/src/corelib/tools/qrect.cpp b/src/corelib/tools/qrect.cpp index 6d345ce543..ce28a6d887 100644 --- a/src/corelib/tools/qrect.cpp +++ b/src/corelib/tools/qrect.cpp @@ -15,6 +15,10 @@ QT_BEGIN_NAMESPACE \ingroup painting \reentrant + \compares equality + \compareswith equality QRectF + \endcompareswith + \brief The QRect class defines a rectangle in the plane using integer precision. @@ -1105,18 +1109,18 @@ bool QRect::intersects(const QRect &r) const noexcept } /*! - \fn bool QRect::operator==(const QRect &r1, const QRect &r2) + \fn bool QRect::operator==(const QRect &lhs, const QRect &rhs) - Returns \c true if the rectangles \a r1 and \a r2 are equal, + Returns \c true if the rectangles \a lhs and \a rhs are equal, otherwise returns \c false. */ /*! - \fn bool QRect::operator!=(const QRect &r1, const QRect &r2) + \fn bool QRect::operator!=(const QRect &lhs, const QRect &rhs) - Returns \c true if the rectangles \a r1 and \a r2 are different, otherwise - returns \c false. + Returns \c true if the rectangles \a lhs and \a rhs are different, + otherwise returns \c false. */ /*! @@ -1279,6 +1283,10 @@ QDebug operator<<(QDebug dbg, const QRect &r) \ingroup painting \reentrant + \compares equality + \compareswith equality QRect + \endcompareswith + \brief The QRectF class defines a finite rectangle in the plane using floating point precision. @@ -2346,10 +2354,10 @@ QRect QRectF::toAlignedRect() const noexcept */ /*! - \fn bool QRectF::operator==(const QRectF &r1, const QRectF &r2) + \fn bool QRectF::operator==(const QRectF &lhs, const QRectF &rhs) - Returns \c true if the rectangles \a r1 and \a r2 are \b approximately equal, - otherwise returns \c false. + Returns \c true if the rectangles \a lhs and \a rhs are \b approximately + equal, otherwise returns \c false. \warning This function does not check for strict equality; instead, it uses a fuzzy comparison to compare the rectangles' coordinates. @@ -2359,9 +2367,9 @@ QRect QRectF::toAlignedRect() const noexcept /*! - \fn bool QRectF::operator!=(const QRectF &r1, const QRectF &r2) + \fn bool QRectF::operator!=(const QRectF &lhs, const QRectF &rhs) - Returns \c true if the rectangles \a r1 and \a r2 are sufficiently + Returns \c true if the rectangles \a lhs and \a rhs are sufficiently different, otherwise returns \c false. \warning This function does not check for strict inequality; instead, @@ -2429,6 +2437,22 @@ QRect QRectF::toAlignedRect() const noexcept \sa marginsRemoved(), operator+=(), marginsAdded() */ +/*! + \fn bool QRectF::qFuzzyCompare(const QRectF &lhs, const QRectF &rhs) + \since 6.8 + + Returns \c true if the rectangle \a lhs is approximately equal to the + rectangle \a rhs; otherwise returns \c false. +*/ + +/*! + \fn bool QRectF::qFuzzyIsNull(const QRectF &rect) + \since 6.8 + + Returns \c true if both width and height of the rectangle \a rect are + approximately equal to zero; otherwise returns \c false. +*/ + /***************************************************************************** QRectF stream functions *****************************************************************************/ diff --git a/src/corelib/tools/qrect.h b/src/corelib/tools/qrect.h index e69a217f48..fb938b0056 100644 --- a/src/corelib/tools/qrect.h +++ b/src/corelib/tools/qrect.h @@ -119,12 +119,13 @@ public: [[nodiscard]] static constexpr inline QRect span(const QPoint &p1, const QPoint &p2) noexcept; - friend constexpr inline bool operator==(const QRect &r1, const QRect &r2) noexcept +private: + friend constexpr bool comparesEqual(const QRect &r1, const QRect &r2) noexcept { return r1.x1==r2.x1 && r1.x2==r2.x2 && r1.y1==r2.y1 && r1.y2==r2.y2; } - friend constexpr inline bool operator!=(const QRect &r1, const QRect &r2) noexcept - { return r1.x1!=r2.x1 || r1.x2!=r2.x2 || r1.y1!=r2.y1 || r1.y2!=r2.y2; } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QRect) friend constexpr inline size_t qHash(const QRect &, size_t) noexcept; +public: #if defined(Q_OS_DARWIN) || defined(Q_QDOC) [[nodiscard]] CGRect toCGRect() const noexcept; #endif @@ -572,17 +573,30 @@ public: constexpr inline QRectF &operator+=(const QMarginsF &margins) noexcept; constexpr inline QRectF &operator-=(const QMarginsF &margins) noexcept; - friend constexpr inline bool operator==(const QRectF &r1, const QRectF &r2) noexcept +private: + friend constexpr bool comparesEqual(const QRectF &r1, const QRectF &r2) noexcept { return r1.topLeft() == r2.topLeft() && r1.size() == r2.size(); } - friend constexpr inline bool operator!=(const QRectF &r1, const QRectF &r2) noexcept + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QRectF) + + friend constexpr bool comparesEqual(const QRectF &r1, const QRect &r2) noexcept + { return r1.topLeft() == r2.topLeft() && r1.size() == r2.size(); } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QRectF, QRect) + + friend constexpr bool qFuzzyCompare(const QRectF &lhs, const QRectF &rhs) noexcept { - return r1.topLeft() != r2.topLeft() - || r1.size() != r2.size(); + return qFuzzyCompare(lhs.topLeft(), rhs.topLeft()) + && qFuzzyCompare(lhs.bottomRight(), rhs.bottomRight()); } + friend constexpr bool qFuzzyIsNull(const QRectF &rect) noexcept + { + return qFuzzyIsNull(rect.w) && qFuzzyIsNull(rect.h); + } + +public: [[nodiscard]] constexpr inline QRect toRect() const noexcept; [[nodiscard]] QRect toAlignedRect() const noexcept; diff --git a/src/corelib/tools/qringbuffer.cpp b/src/corelib/tools/qringbuffer.cpp index ea4224eefe..0645759118 100644 --- a/src/corelib/tools/qringbuffer.cpp +++ b/src/corelib/tools/qringbuffer.cpp @@ -3,7 +3,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "private/qringbuffer_p.h" -#include "private/qbytearray_p.h" #include <type_traits> @@ -91,7 +90,7 @@ void QRingBuffer::free(qint64 bytes) clear(); // try to minify/squeeze us } } else { - Q_ASSERT(bytes < MaxByteArraySize); + Q_ASSERT(bytes < QByteArray::max_size()); chunk.advance(bytes); bufferSize -= bytes; } @@ -106,7 +105,7 @@ void QRingBuffer::free(qint64 bytes) char *QRingBuffer::reserve(qint64 bytes) { - Q_ASSERT(bytes > 0 && bytes < MaxByteArraySize); + Q_ASSERT(bytes > 0 && bytes < QByteArray::max_size()); const qsizetype chunkSize = qMax(qint64(basicBlockSize), bytes); qsizetype tail = 0; @@ -136,7 +135,7 @@ char *QRingBuffer::reserve(qint64 bytes) */ char *QRingBuffer::reserveFront(qint64 bytes) { - Q_ASSERT(bytes > 0 && bytes < MaxByteArraySize); + Q_ASSERT(bytes > 0 && bytes < QByteArray::max_size()); const qsizetype chunkSize = qMax(qint64(basicBlockSize), bytes); if (bufferSize == 0) { @@ -182,7 +181,7 @@ void QRingBuffer::chop(qint64 bytes) clear(); // try to minify/squeeze us } } else { - Q_ASSERT(bytes < MaxByteArraySize); + Q_ASSERT(bytes < QByteArray::max_size()); chunk.grow(-bytes); bufferSize -= bytes; } diff --git a/src/corelib/tools/qscopedpointer.cpp b/src/corelib/tools/qscopedpointer.cpp index 28cc39ae5d..515eb9dc75 100644 --- a/src/corelib/tools/qscopedpointer.cpp +++ b/src/corelib/tools/qscopedpointer.cpp @@ -269,7 +269,7 @@ QT_BEGIN_NAMESPACE */ /*! - \fn template <typename T, typename Cleanup> template <typename D> QScopedArrayPointer<T, Cleanup>::QScopedArrayPointer(D * p) + \fn template <typename T, typename Cleanup> template <typename D, QScopedArrayPointer<T, Cleanup>::if_same_type<D> = true> QScopedArrayPointer<T, Cleanup>::QScopedArrayPointer(D * p) Constructs a QScopedArrayPointer and stores the array of objects pointed to by \a p. diff --git a/src/corelib/tools/qscopedpointer.h b/src/corelib/tools/qscopedpointer.h index 1637bb40a5..59bae9b967 100644 --- a/src/corelib/tools/qscopedpointer.h +++ b/src/corelib/tools/qscopedpointer.h @@ -67,9 +67,10 @@ typedef QScopedPointerObjectDeleteLater<QObject> QScopedPointerDeleteLater; #endif template <typename T, typename Cleanup = QScopedPointerDeleter<T> > -class [[nodiscard]] QScopedPointer +class QScopedPointer { public: + Q_NODISCARD_CTOR explicit QScopedPointer(T *p = nullptr) noexcept : d(p) { } @@ -187,15 +188,17 @@ private: }; template <typename T, typename Cleanup = QScopedPointerArrayDeleter<T> > -class [[nodiscard]] QScopedArrayPointer : public QScopedPointer<T, Cleanup> +class QScopedArrayPointer : public QScopedPointer<T, Cleanup> { template <typename Ptr> using if_same_type = typename std::enable_if<std::is_same<typename std::remove_cv<T>::type, Ptr>::value, bool>::type; public: + Q_NODISCARD_CTOR inline QScopedArrayPointer() : QScopedPointer<T, Cleanup>(nullptr) {} inline ~QScopedArrayPointer() = default; template <typename D, if_same_type<D> = true> + Q_NODISCARD_CTOR explicit QScopedArrayPointer(D *p) : QScopedPointer<T, Cleanup>(p) { diff --git a/src/corelib/tools/qscopedvaluerollback.h b/src/corelib/tools/qscopedvaluerollback.h index 53a31e4675..0ae3efd0c0 100644 --- a/src/corelib/tools/qscopedvaluerollback.h +++ b/src/corelib/tools/qscopedvaluerollback.h @@ -9,14 +9,16 @@ QT_BEGIN_NAMESPACE template <typename T> -class [[nodiscard]] QScopedValueRollback +class QScopedValueRollback { public: + Q_NODISCARD_CTOR explicit constexpr QScopedValueRollback(T &var) : varRef(var), oldValue(var) { } + Q_NODISCARD_CTOR explicit constexpr QScopedValueRollback(T &var, T value) : varRef(var), oldValue(std::move(var)) // ### C++20: std::exchange(var, std::move(value)) { diff --git a/src/corelib/tools/qscopeguard.h b/src/corelib/tools/qscopeguard.h index 5bd202ce33..9be6634cc8 100644 --- a/src/corelib/tools/qscopeguard.h +++ b/src/corelib/tools/qscopeguard.h @@ -13,19 +13,22 @@ QT_BEGIN_NAMESPACE template <typename F> -class [[nodiscard]] QScopeGuard +class QScopeGuard { public: + Q_NODISCARD_CTOR explicit QScopeGuard(F &&f) noexcept : m_func(std::move(f)) { } + Q_NODISCARD_CTOR explicit QScopeGuard(const F &f) noexcept : m_func(f) { } + Q_NODISCARD_CTOR QScopeGuard(QScopeGuard &&other) noexcept : m_func(std::move(other.m_func)) , m_invoke(std::exchange(other.m_invoke, false)) diff --git a/src/corelib/tools/qset.h b/src/corelib/tools/qset.h index cdd9ceda20..6eaeb8fc41 100644 --- a/src/corelib/tools/qset.h +++ b/src/corelib/tools/qset.h @@ -6,6 +6,7 @@ #include <QtCore/qhash.h> #include <QtCore/qcontainertools_impl.h> +#include <QtCore/qttypetraits.h> #include <initializer_list> #include <iterator> @@ -189,14 +190,18 @@ public: inline QSet<T> &operator+=(const T &value) { insert(value); return *this; } inline QSet<T> &operator-=(const QSet<T> &other) { subtract(other); return *this; } inline QSet<T> &operator-=(const T &value) { remove(value); return *this; } - inline QSet<T> operator|(const QSet<T> &other) const - { QSet<T> result = *this; result |= other; return result; } - inline QSet<T> operator&(const QSet<T> &other) const - { QSet<T> result = *this; result &= other; return result; } - inline QSet<T> operator+(const QSet<T> &other) const - { QSet<T> result = *this; result += other; return result; } - inline QSet<T> operator-(const QSet<T> &other) const - { QSet<T> result = *this; result -= other; return result; } + + friend QSet operator|(const QSet &lhs, const QSet &rhs) { return QSet(lhs) |= rhs; } + friend QSet operator|(QSet &&lhs, const QSet &rhs) { lhs |= rhs; return std::move(lhs); } + + friend QSet operator&(const QSet &lhs, const QSet &rhs) { return QSet(lhs) &= rhs; } + friend QSet operator&(QSet &&lhs, const QSet &rhs) { lhs &= rhs; return std::move(lhs); } + + friend QSet operator+(const QSet &lhs, const QSet &rhs) { return QSet(lhs) += rhs; } + friend QSet operator+(QSet &&lhs, const QSet &rhs) { lhs += rhs; return std::move(lhs); } + + friend QSet operator-(const QSet &lhs, const QSet &rhs) { return QSet(lhs) -= rhs; } + friend QSet operator-(QSet &&lhs, const QSet &rhs) { lhs -= rhs; return std::move(lhs); } QList<T> values() const; @@ -224,10 +229,13 @@ Q_INLINE_TEMPLATE void QSet<T>::reserve(qsizetype asize) { q_hash.reserve(asize) template <class T> Q_INLINE_TEMPLATE QSet<T> &QSet<T>::unite(const QSet<T> &other) { - if (!q_hash.isSharedWith(other.q_hash)) { - for (const T &e : other) - insert(e); - } + if (q_hash.isSharedWith(other.q_hash)) + return *this; + QSet<T> tmp = other; + if (size() < other.size()) + swap(tmp); + for (const auto &e : std::as_const(tmp)) + insert(e); return *this; } diff --git a/src/corelib/tools/qset.qdoc b/src/corelib/tools/qset.qdoc index 31b431e43f..4ef7a80a52 100644 --- a/src/corelib/tools/qset.qdoc +++ b/src/corelib/tools/qset.qdoc @@ -86,7 +86,7 @@ initializer list \a list. */ -/*! \fn template <class T> template<typename InputIterator> QSet<T>::QSet(InputIterator first, InputIterator last) +/*! \fn template <class T> template <typename InputIterator, QtPrivate::IfIsInputIterator<InputIterator> = true> QSet<T>::QSet(InputIterator first, InputIterator last) \since 5.14 Constructs a set with the contents in the iterator range [\a first, \a last). @@ -567,29 +567,30 @@ */ /*! - \fn template <class T> QSet<T> QSet<T>::operator|(const QSet<T> &other) const - \fn template <class T> QSet<T> QSet<T>::operator+(const QSet<T> &other) const + \fn template <class T> QSet<T> QSet<T>::operator|(const QSet &lhs, const QSet &rhs) + \fn template <class T> QSet<T> QSet<T>::operator|(QSet &&lhs, const QSet &rhs) + \fn template <class T> QSet<T> QSet<T>::operator+(const QSet &lhs, const QSet &rhs) + \fn template <class T> QSet<T> QSet<T>::operator+(QSet &&lhs, const QSet &rhs) - Returns a new QSet that is the union of this set and the - \a other set. + Returns a new QSet that is the union of sets \a lhs and \a rhs. \sa unite(), operator|=(), operator&(), operator-() */ /*! - \fn template <class T> QSet<T> QSet<T>::operator&(const QSet<T> &other) const + \fn template <class T> QSet<T> QSet<T>::operator&(const QSet &lhs, const QSet &rhs) + \fn template <class T> QSet<T> QSet<T>::operator&(QSet &&lhs, const QSet &rhs) - Returns a new QSet that is the intersection of this set and the - \a other set. + Returns a new QSet that is the intersection of sets \a lhs and \a rhs. \sa intersect(), operator&=(), operator|(), operator-() */ /*! - \fn template <class T> QSet<T> QSet<T>::operator-(const QSet<T> &other) const + \fn template <class T> QSet<T> QSet<T>::operator-(const QSet &lhs, const QSet &rhs) + \fn template <class T> QSet<T> QSet<T>::operator-(QSet &&lhs, const QSet &rhs) - Returns a new QSet that is the set difference of this set and - the \a other set, i.e., this set - \a other set. + Returns a new QSet that is the set difference of sets \a lhs and \a rhs. \sa subtract(), operator-=(), operator|(), operator&() */ @@ -886,3 +887,10 @@ from the set \a set. Returns the number of elements removed, if any. */ + +/*! \fn template <class T> template <class Pred> qsizetype QSet<T>::removeIf(Pred pred) + \since 6.1 + + Removes, from this set, all elements for which the predicate \a pred + returns \c true. Returns the number of elements removed, if any. +*/ diff --git a/src/corelib/tools/qshareddata.h b/src/corelib/tools/qshareddata.h index 826152b63a..4c4153a506 100644 --- a/src/corelib/tools/qshareddata.h +++ b/src/corelib/tools/qshareddata.h @@ -51,13 +51,17 @@ public: const T *constData() const noexcept { return d; } T *take() noexcept { return std::exchange(d, nullptr); } + Q_NODISCARD_CTOR QSharedDataPointer() noexcept : d(nullptr) { } ~QSharedDataPointer() { if (d && !d->ref.deref()) delete d; } + Q_NODISCARD_CTOR explicit QSharedDataPointer(T *data) noexcept : d(data) { if (d) d->ref.ref(); } + Q_NODISCARD_CTOR QSharedDataPointer(T *data, QAdoptSharedDataTag) noexcept : d(data) {} + Q_NODISCARD_CTOR QSharedDataPointer(const QSharedDataPointer &o) noexcept : d(o.d) { if (d) d->ref.ref(); } @@ -82,6 +86,7 @@ public: reset(o); return *this; } + Q_NODISCARD_CTOR QSharedDataPointer(QSharedDataPointer &&o) noexcept : d(std::exchange(o.d, nullptr)) {} QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QSharedDataPointer) @@ -139,17 +144,22 @@ public: void detach() { if (d && d->ref.loadRelaxed() != 1) detach_helper(); } + Q_NODISCARD_CTOR QExplicitlySharedDataPointer() noexcept : d(nullptr) { } ~QExplicitlySharedDataPointer() { if (d && !d->ref.deref()) delete d; } + Q_NODISCARD_CTOR explicit QExplicitlySharedDataPointer(T *data) noexcept : d(data) { if (d) d->ref.ref(); } + Q_NODISCARD_CTOR QExplicitlySharedDataPointer(T *data, QAdoptSharedDataTag) noexcept : d(data) {} + Q_NODISCARD_CTOR QExplicitlySharedDataPointer(const QExplicitlySharedDataPointer &o) noexcept : d(o.d) { if (d) d->ref.ref(); } template<typename X> + Q_NODISCARD_CTOR QExplicitlySharedDataPointer(const QExplicitlySharedDataPointer<X> &o) noexcept #ifdef QT_ENABLE_QEXPLICITLYSHAREDDATAPOINTER_STATICCAST : d(static_cast<T *>(o.data())) @@ -179,6 +189,7 @@ public: reset(o); return *this; } + Q_NODISCARD_CTOR QExplicitlySharedDataPointer(QExplicitlySharedDataPointer &&o) noexcept : d(std::exchange(o.d, nullptr)) {} QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QExplicitlySharedDataPointer) diff --git a/src/corelib/tools/qsharedpointer.cpp b/src/corelib/tools/qsharedpointer.cpp index 7ed3847182..217a3a4ff4 100644 --- a/src/corelib/tools/qsharedpointer.cpp +++ b/src/corelib/tools/qsharedpointer.cpp @@ -139,8 +139,7 @@ can also exceptionally be -1, indicating that there are no QSharedPointers attached to an object, which is tracked too. The only case where this is possible is that of QWeakPointers and QPointers tracking a QObject. Note - that QWeakPointers tracking a QObject is a deprecated feature as of Qt 5.0, - kept only for compatibility with Qt 4.x. + that QWeakPointers tracking a QObject is deprecated. The weak reference count controls the lifetime of the d-pointer itself. It can be thought of as an internal/intrusive reference count for @@ -175,7 +174,7 @@ last QSharedPointer instance had. This class is never instantiated directly: the constructors and - destructor are private and, in C++11, deleted. Only the create() function + destructor are deleted. Only the create() function may be called to return an object of this type. See below for construction details. @@ -214,8 +213,7 @@ Like ExternalRefCountWithCustomDeleter, this class is never instantiated directly. This class also provides a create() member that returns the - pointer, and hides its constructors and destructor. With C++11, they're - deleted. + pointer, and deletes its constructors and destructor. The size of this class depends on the size of \tt T. @@ -711,6 +709,49 @@ */ /*! + \fn template <class T> template <class X> bool QSharedPointer<T>::owner_before(const QSharedPointer<X> &other) const noexcept + \fn template <class T> template <class X> bool QSharedPointer<T>::owner_before(const QWeakPointer<X> &other) const noexcept + \fn template <class T> template <class X> bool QWeakPointer<T>::owner_before(const QSharedPointer<X> &other) const noexcept + \fn template <class T> template <class X> bool QWeakPointer<T>::owner_before(const QWeakPointer<X> &other) const noexcept + \since 6.7 + + Returns \c true if and only if this smart pointer precedes \a other + in an implementation-defined owner-based ordering. The ordering is such + that two smart pointers are considered equivalent if they are both + empty or if they both own the same object (even if their apparent type + and pointer are different). + + \sa owner_equal +*/ + +/*! + \fn template <class T> template <class X> bool QSharedPointer<T>::owner_equal(const QSharedPointer<X> &other) const noexcept + \fn template <class T> template <class X> bool QSharedPointer<T>::owner_equal(const QWeakPointer<X> &other) const noexcept + \fn template <class T> template <class X> bool QWeakPointer<T>::owner_equal(const QSharedPointer<X> &other) const noexcept + \fn template <class T> template <class X> bool QWeakPointer<T>::owner_equal(const QWeakPointer<X> &other) const noexcept + + \since 6.7 + + Returns \c true if and only if this smart pointer and \a other + share ownership. + + \sa owner_before, owner_hash +*/ + +/*! + \fn template <class T> size_t QSharedPointer<T>::owner_hash() const noexcept + \fn template <class T> size_t QWeakPointer<T>::owner_hash() const noexcept + + \since 6.7 + + Returns a owner-based hash value for this smart pointer object. + Smart pointers that compare equal (as per \c{owner_equal}) will + have an identical owner-based hash. + + \sa owner_equal +*/ + +/*! \fn template <class T> QWeakPointer<T>::QWeakPointer() Creates a QWeakPointer that points to nothing. @@ -945,7 +986,7 @@ */ /*! - \fn template <class T> template <class X> bool operator==(const QSharedPointer<T> &ptr1, const QSharedPointer<X> &ptr2) + \fn template<class T, class X> bool operator==(const QSharedPointer<T> &ptr1, const QSharedPointer<X> &ptr2) \relates QSharedPointer Returns \c true if \a ptr1 and \a ptr2 refer to the same pointer. @@ -958,7 +999,7 @@ */ /*! - \fn template <class T> template <class X> bool operator!=(const QSharedPointer<T> &ptr1, const QSharedPointer<X> &ptr2) + \fn template<class T, class X> bool operator!=(const QSharedPointer<T> &ptr1, const QSharedPointer<X> &ptr2) \relates QSharedPointer Returns \c true if \a ptr1 and \a ptr2 refer to distinct pointers. @@ -971,7 +1012,7 @@ */ /*! - \fn template <class T> template <class X> bool operator==(const QSharedPointer<T> &ptr1, const X *ptr2) + \fn template<class T, class X> bool operator==(const QSharedPointer<T> &ptr1, const X *ptr2) \relates QSharedPointer Returns \c true if \a ptr1 and \a ptr2 refer to the same pointer. @@ -984,7 +1025,7 @@ */ /*! - \fn template <class T> template <class X> bool operator!=(const QSharedPointer<T> &ptr1, const X *ptr2) + \fn template<class T, class X> bool operator!=(const QSharedPointer<T> &ptr1, const X *ptr2) \relates QSharedPointer Returns \c true if \a ptr1 and \a ptr2 refer to distinct pointers. @@ -997,7 +1038,7 @@ */ /*! - \fn template <class T> template <class X> bool operator==(const T *ptr1, const QSharedPointer<X> &ptr2) + \fn template<class T, class X> bool operator==(const T *ptr1, const QSharedPointer<X> &ptr2) \relates QSharedPointer Returns \c true if the pointer \a ptr1 is the @@ -1011,7 +1052,7 @@ */ /*! - \fn template <class T> template <class X> bool operator!=(const T *ptr1, const QSharedPointer<X> &ptr2) + \fn template<class T, class X> bool operator!=(const T *ptr1, const QSharedPointer<X> &ptr2) \relates QSharedPointer Returns \c true if the pointer \a ptr1 is not the @@ -1025,7 +1066,7 @@ */ /*! - \fn template <class T> template <class X> bool operator==(const QSharedPointer<T> &ptr1, const QWeakPointer<X> &ptr2) + \fn template<class T, class X> bool operator==(const QSharedPointer<T> &ptr1, const QWeakPointer<X> &ptr2) \relates QWeakPointer Returns \c true if \a ptr1 and \a ptr2 refer to the same pointer. @@ -1038,7 +1079,7 @@ */ /*! - \fn template <class T> template <class X> bool operator!=(const QSharedPointer<T> &ptr1, const QWeakPointer<X> &ptr2) + \fn template<class T, class X> bool operator!=(const QSharedPointer<T> &ptr1, const QWeakPointer<X> &ptr2) \relates QWeakPointer Returns \c true if \a ptr1 and \a ptr2 refer to distinct pointers. @@ -1051,7 +1092,7 @@ */ /*! - \fn template <class T> template <class X> bool operator==(const QWeakPointer<T> &ptr1, const QSharedPointer<X> &ptr2) + \fn template<class T, class X> bool operator==(const QWeakPointer<T> &ptr1, const QSharedPointer<X> &ptr2) \relates QWeakPointer Returns \c true if \a ptr1 and \a ptr2 refer to the same pointer. @@ -1144,7 +1185,7 @@ */ /*! - \fn template <class T> template <class X> bool operator!=(const QWeakPointer<T> &ptr1, const QSharedPointer<X> &ptr2) + \fn template<class T, class X> bool operator!=(const QWeakPointer<T> &ptr1, const QSharedPointer<X> &ptr2) \relates QWeakPointer Returns \c true if \a ptr1 and \a ptr2 refer to distinct pointers. @@ -1157,7 +1198,7 @@ */ /*! - \fn template <class X> template <class T> QSharedPointer<X> qSharedPointerCast(const QSharedPointer<T> &other) + \fn template <class X, class T> QSharedPointer<X> qSharedPointerCast(const QSharedPointer<T> &other) \relates QSharedPointer Returns a shared pointer to the pointer held by \a other, cast to @@ -1172,7 +1213,7 @@ */ /*! - \fn template <class X> template <class T> QSharedPointer<X> qSharedPointerCast(const QWeakPointer<T> &other) + \fn template <class X, class T> QSharedPointer<X> qSharedPointerCast(const QWeakPointer<T> &other) \relates QSharedPointer \relates QWeakPointer @@ -1193,7 +1234,7 @@ */ /*! - \fn template <class X> template <class T> QSharedPointer<X> qSharedPointerDynamicCast(const QSharedPointer<T> &src) + \fn template <class X, class T> QSharedPointer<X> qSharedPointerDynamicCast(const QSharedPointer<T> &src) \relates QSharedPointer Returns a shared pointer to the pointer held by \a src, using a @@ -1209,7 +1250,7 @@ */ /*! - \fn template <class X> template <class T> QSharedPointer<X> qSharedPointerDynamicCast(const QWeakPointer<T> &src) + \fn template <class X, class T> QSharedPointer<X> qSharedPointerDynamicCast(const QWeakPointer<T> &src) \relates QSharedPointer \relates QWeakPointer @@ -1231,7 +1272,7 @@ */ /*! - \fn template <class X> template <class T> QSharedPointer<X> qSharedPointerConstCast(const QSharedPointer<T> &src) + \fn template <class X, class T> QSharedPointer<X> qSharedPointerConstCast(const QSharedPointer<T> &src) \relates QSharedPointer Returns a shared pointer to the pointer held by \a src, cast to @@ -1243,7 +1284,7 @@ */ /*! - \fn template <class X> template <class T> QSharedPointer<X> qSharedPointerConstCast(const QWeakPointer<T> &src) + \fn template <class X, class T> QSharedPointer<X> qSharedPointerConstCast(const QWeakPointer<T> &src) \relates QSharedPointer \relates QWeakPointer @@ -1261,7 +1302,7 @@ */ /*! - \fn template <class X> template <class T> QSharedPointer<X> qSharedPointerObjectCast(const QSharedPointer<T> &src) + \fn template <class X, class T> QSharedPointer<X> qSharedPointerObjectCast(const QSharedPointer<T> &src) \relates QSharedPointer \since 4.6 @@ -1333,7 +1374,7 @@ */ /*! - \fn template <class X> template <class T> QSharedPointer<X> qSharedPointerObjectCast(const QWeakPointer<T> &src) + \fn template <class X, class T> QSharedPointer<X> qSharedPointerObjectCast(const QWeakPointer<T> &src) \relates QSharedPointer \relates QWeakPointer \since 4.6 @@ -1359,7 +1400,7 @@ /*! - \fn template <class X> template <class T> QWeakPointer<X> qWeakPointerCast(const QWeakPointer<T> &src) + \fn template <class X, class T> QWeakPointer<X> qWeakPointerCast(const QWeakPointer<T> &src) \relates QWeakPointer Returns a weak pointer to the pointer held by \a src, cast to @@ -1379,6 +1420,7 @@ QT_BEGIN_NAMESPACE +QT6_ONLY( /*! \internal This function is called for a just-created QObject \a obj, to enable @@ -1386,7 +1428,9 @@ QT_BEGIN_NAMESPACE */ void QtSharedPointer::ExternalRefCountData::setQObjectShared(const QObject *, bool) {} +) +QT6_ONLY( /*! \internal This function is called when a QSharedPointer is created from a QWeakPointer @@ -1399,6 +1443,7 @@ void QtSharedPointer::ExternalRefCountData::checkQObjectShared(const QObject *) if (strongref.loadRelaxed() < 0) qWarning("QSharedPointer: cannot create a QSharedPointer from a QObject-tracking QWeakPointer"); } +) QtSharedPointer::ExternalRefCountData *QtSharedPointer::ExternalRefCountData::getAndRef(const QObject *obj) { diff --git a/src/corelib/tools/qsharedpointer.h b/src/corelib/tools/qsharedpointer.h index 2a60f3ca5e..116c9afa00 100644 --- a/src/corelib/tools/qsharedpointer.h +++ b/src/corelib/tools/qsharedpointer.h @@ -72,6 +72,19 @@ public: template <typename... Args> static inline QSharedPointer<T> create(Args &&... args); + + // owner-based comparisons + template <typename X> + bool owner_before(const QSharedPointer<X> &other) const noexcept; + template <typename X> + bool owner_before(const QWeakPointer<X> &other) const noexcept; + + template <typename X> + bool owner_equal(const QSharedPointer<X> &other) const noexcept; + template <typename X> + bool owner_equal(const QWeakPointer<X> &other) const noexcept; + + size_t owner_hash() const noexcept; }; template <class T> @@ -108,6 +121,19 @@ public: QSharedPointer<T> toStrongRef() const; QSharedPointer<T> lock() const; + + // owner-based comparisons + template <typename X> + bool owner_before(const QWeakPointer<X> &other) const noexcept; + template <typename X> + bool owner_before(const QSharedPointer<X> &other) const noexcept; + + template <typename X> + bool owner_equal(const QWeakPointer<X> &other) const noexcept; + template <typename X> + bool owner_equal(const QSharedPointer<X> &other) const noexcept; + + size_t owner_hash() const noexcept; }; template <class T> diff --git a/src/corelib/tools/qsharedpointer_impl.h b/src/corelib/tools/qsharedpointer_impl.h index dd6bd22fca..456be91d03 100644 --- a/src/corelib/tools/qsharedpointer_impl.h +++ b/src/corelib/tools/qsharedpointer_impl.h @@ -28,6 +28,7 @@ QT_END_NAMESPACE #include <QtCore/qatomic.h> #include <QtCore/qhashfunctions.h> #include <QtCore/qmetatype.h> // for IsPointerToTypeDerivedFromQObject +#include <QtCore/qxptype_traits.h> #include <memory> @@ -115,8 +116,10 @@ namespace QtSharedPointer { #ifndef QT_NO_QOBJECT Q_CORE_EXPORT static ExternalRefCountData *getAndRef(const QObject *); + QT6_ONLY( Q_CORE_EXPORT void setQObjectShared(const QObject *, bool enable); - Q_CORE_EXPORT void checkQObjectShared(const QObject *); + ) + QT6_ONLY(Q_CORE_EXPORT void checkQObjectShared(const QObject *);) #endif inline void checkQObjectShared(...) { } inline void setQObjectShared(...) { } @@ -276,23 +279,29 @@ public: T &operator*() const { return *data(); } T *operator->() const noexcept { return data(); } + Q_NODISCARD_CTOR constexpr QSharedPointer() noexcept : value(nullptr), d(nullptr) { } ~QSharedPointer() { deref(); } + Q_NODISCARD_CTOR constexpr QSharedPointer(std::nullptr_t) noexcept : value(nullptr), d(nullptr) { } template <class X, IfCompatible<X> = true> + Q_NODISCARD_CTOR inline explicit QSharedPointer(X *ptr) : value(ptr) // noexcept { internalConstruct(ptr, QtSharedPointer::NormalDeleter()); } template <class X, typename Deleter, IfCompatible<X> = true> + Q_NODISCARD_CTOR inline QSharedPointer(X *ptr, Deleter deleter) : value(ptr) // throws { internalConstruct(ptr, deleter); } template <typename Deleter> + Q_NODISCARD_CTOR QSharedPointer(std::nullptr_t, Deleter deleter) : value(nullptr) { internalConstruct(static_cast<T *>(nullptr), deleter); } + Q_NODISCARD_CTOR QSharedPointer(const QSharedPointer &other) noexcept : value(other.value), d(other.d) { if (d) ref(); } QSharedPointer &operator=(const QSharedPointer &other) noexcept @@ -301,6 +310,7 @@ public: swap(copy); return *this; } + Q_NODISCARD_CTOR QSharedPointer(QSharedPointer &&other) noexcept : value(other.value), d(other.d) { @@ -310,6 +320,7 @@ public: QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QSharedPointer) template <class X, IfCompatible<X> = true> + Q_NODISCARD_CTOR QSharedPointer(QSharedPointer<X> &&other) noexcept : value(other.value), d(other.d) { @@ -326,6 +337,7 @@ public: } template <class X, IfCompatible<X> = true> + Q_NODISCARD_CTOR QSharedPointer(const QSharedPointer<X> &other) noexcept : value(other.value), d(other.d) { if (d) ref(); } @@ -338,6 +350,7 @@ public: } template <class X, IfCompatible<X> = true> + Q_NODISCARD_CTOR inline QSharedPointer(const QWeakPointer<X> &other) : value(nullptr), d(nullptr) { *this = other; } @@ -383,10 +396,10 @@ public: inline void clear() { QSharedPointer copy; swap(copy); } - QWeakPointer<T> toWeakRef() const; + [[nodiscard]] QWeakPointer<T> toWeakRef() const; template <typename... Args> - static QSharedPointer create(Args && ...arguments) + [[nodiscard]] static QSharedPointer create(Args && ...arguments) { typedef QtSharedPointer::ExternalRefCountWithContiguousData<T> Private; # ifdef QT_SHAREDPOINTER_TRACK_POINTERS @@ -433,7 +446,25 @@ public: #undef DECLARE_TEMPLATE_COMPARE_SET #undef DECLARE_COMPARE_SET + template <typename X> + bool owner_before(const QSharedPointer<X> &other) const noexcept + { return std::less<>()(d, other.d); } + template <typename X> + bool owner_before(const QWeakPointer<X> &other) const noexcept + { return std::less<>()(d, other.d); } + + template <typename X> + bool owner_equal(const QSharedPointer<X> &other) const noexcept + { return d == other.d; } + template <typename X> + bool owner_equal(const QWeakPointer<X> &other) const noexcept + { return d == other.d; } + + size_t owner_hash() const noexcept + { return std::hash<Data *>()(d); } + private: + Q_NODISCARD_CTOR explicit QSharedPointer(Qt::Initialization) {} void deref() noexcept @@ -470,7 +501,6 @@ private: #ifdef QT_SHAREDPOINTER_TRACK_POINTERS internalSafetyCheckAdd(d, ptr); #endif - d->setQObjectShared(ptr, true); enableSharedFromThis(ptr); } @@ -498,12 +528,10 @@ private: tmp = o->strongref.loadRelaxed(); // failed, try again } - if (tmp > 0) { + if (tmp > 0) o->weakref.ref(); - } else { - o->checkQObjectShared(actual); + else o = nullptr; - } } qt_ptr_swap(d, o); @@ -526,6 +554,12 @@ class QWeakPointer template <typename X> using IfCompatible = typename std::enable_if<std::is_convertible<X*, T*>::value, bool>::type; + template <typename X> + using IfVirtualBase = typename std::enable_if<qxp::is_virtual_base_of_v<T, X>, bool>::type; + + template <typename X> + using IfNotVirtualBase = typename std::enable_if<!qxp::is_virtual_base_of_v<T, X>, bool>::type; + public: typedef T element_type; typedef T value_type; @@ -539,11 +573,14 @@ public: explicit operator bool() const noexcept { return !isNull(); } bool operator !() const noexcept { return isNull(); } + Q_NODISCARD_CTOR constexpr QWeakPointer() noexcept : d(nullptr), value(nullptr) { } inline ~QWeakPointer() { if (d && !d->weakref.deref()) delete d; } + Q_NODISCARD_CTOR QWeakPointer(const QWeakPointer &other) noexcept : d(other.d), value(other.value) { if (d) d->weakref.ref(); } + Q_NODISCARD_CTOR QWeakPointer(QWeakPointer &&other) noexcept : d(other.d), value(other.value) { @@ -552,9 +589,18 @@ public: } QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QWeakPointer) - template <class X, IfCompatible<X> = true> + template <class X, IfCompatible<X> = true, IfNotVirtualBase<X> = true> + Q_NODISCARD_CTOR QWeakPointer(QWeakPointer<X> &&other) noexcept - : d(other.d), value(other.value) + : d(std::exchange(other.d, nullptr)), + value(std::exchange(other.value, nullptr)) + { + } + + template <class X, IfCompatible<X> = true, IfVirtualBase<X> = true> + Q_NODISCARD_CTOR + QWeakPointer(QWeakPointer<X> &&other) noexcept + : d(other.d), value(other.toStrongRef().get()) // must go through QSharedPointer, see below { other.d = nullptr; other.value = nullptr; @@ -581,6 +627,7 @@ public: qt_ptr_swap(this->value, other.value); } + Q_NODISCARD_CTOR inline QWeakPointer(const QSharedPointer<T> &o) : d(o.d), value(o.data()) { if (d) d->weakref.ref();} inline QWeakPointer &operator=(const QSharedPointer<T> &o) @@ -590,6 +637,7 @@ public: } template <class X, IfCompatible<X> = true> + Q_NODISCARD_CTOR inline QWeakPointer(const QWeakPointer<X> &o) : d(nullptr), value(nullptr) { *this = o; } @@ -603,6 +651,7 @@ public: } template <class X, IfCompatible<X> = true> + Q_NODISCARD_CTOR inline QWeakPointer(const QSharedPointer<X> &o) : d(nullptr), value(nullptr) { *this = o; } @@ -615,9 +664,9 @@ public: inline void clear() { *this = QWeakPointer(); } - inline QSharedPointer<T> toStrongRef() const { return QSharedPointer<T>(*this); } + [[nodiscard]] QSharedPointer<T> toStrongRef() const { return QSharedPointer<T>(*this); } // std::weak_ptr compatibility: - inline QSharedPointer<T> lock() const { return toStrongRef(); } + [[nodiscard]] QSharedPointer<T> lock() const { return toStrongRef(); } template <class X> bool operator==(const QWeakPointer<X> &o) const noexcept @@ -651,6 +700,23 @@ public: friend bool operator!=(std::nullptr_t, const QWeakPointer &p) { return !p.isNull(); } + template <typename X> + bool owner_before(const QWeakPointer<X> &other) const noexcept + { return std::less<>()(d, other.d); } + template <typename X> + bool owner_before(const QSharedPointer<X> &other) const noexcept + { return std::less<>()(d, other.d); } + + template <typename X> + bool owner_equal(const QWeakPointer<X> &other) const noexcept + { return d == other.d; } + template <typename X> + bool owner_equal(const QSharedPointer<X> &other) const noexcept + { return d == other.d; } + + size_t owner_hash() const noexcept + { return std::hash<Data *>()(d); } + private: friend struct QtPrivate::EnableInternalData; template <class X> friend class QSharedPointer; @@ -659,10 +725,11 @@ private: template <class X> inline QWeakPointer &assign(X *ptr) - { return *this = QWeakPointer<X>(ptr, true); } + { return *this = QWeakPointer<T>(ptr, true); } #ifndef QT_NO_QOBJECT template <class X, IfCompatible<X> = true> + Q_NODISCARD_CTOR inline QWeakPointer(X *ptr, bool) : d(ptr ? Data::getAndRef(ptr) : nullptr), value(ptr) { } #endif diff --git a/src/corelib/tools/qsize.cpp b/src/corelib/tools/qsize.cpp index d5e8e4c71b..27ff1d164d 100644 --- a/src/corelib/tools/qsize.cpp +++ b/src/corelib/tools/qsize.cpp @@ -266,15 +266,15 @@ QSize QSize::scaled(const QSize &s, Qt::AspectRatioMode mode) const noexcept */ /*! - \fn bool QSize::operator==(const QSize &s1, const QSize &s2) + \fn bool QSize::operator==(const QSize &lhs, const QSize &rhs) - Returns \c true if \a s1 and \a s2 are equal; otherwise returns \c false. + Returns \c true if \a lhs and \a rhs are equal; otherwise returns \c false. */ /*! - \fn bool QSize::operator!=(const QSize &s1, const QSize &s2) + \fn bool QSize::operator!=(const QSize &lhs, const QSize &rhs) - Returns \c true if \a s1 and \a s2 are different; otherwise returns \c false. + Returns \c true if \a lhs and \a rhs are different; otherwise returns \c false. */ /*! @@ -714,9 +714,9 @@ QSizeF QSizeF::scaled(const QSizeF &s, Qt::AspectRatioMode mode) const noexcept */ /*! - \fn bool QSizeF::operator==(const QSizeF &s1, const QSizeF &s2) + \fn bool QSizeF::operator==(const QSizeF &lhs, const QSizeF &rhs) - Returns \c true if \a s1 and \a s2 are approximately equal; otherwise + Returns \c true if \a lhs and \a rhs are approximately equal; otherwise returns false. \warning This function does not check for strict equality; instead, @@ -726,9 +726,9 @@ QSizeF QSizeF::scaled(const QSizeF &s, Qt::AspectRatioMode mode) const noexcept */ /*! - \fn bool QSizeF::operator!=(const QSizeF &s1, const QSizeF &s2) + \fn bool QSizeF::operator!=(const QSizeF &lhs, const QSizeF &rhs) - Returns \c true if \a s1 and \a s2 are sufficiently different; otherwise + Returns \c true if \a lhs and \a rhs are sufficiently different; otherwise returns \c false. \warning This function does not check for strict inequality; instead, @@ -808,7 +808,24 @@ QSizeF QSizeF::scaled(const QSizeF &s, Qt::AspectRatioMode mode) const noexcept \sa expandedTo(), scale() */ +/*! + \fn bool QSizeF::qFuzzyCompare(const QSizeF &lhs, const QSizeF &rhs) + \since 6.8 + + Returns \c true if the size \a lhs is approximately equal to the + size \a rhs; otherwise returns \c false. + + The sizes are considered approximately equal if their width and + height are approximately equal. +*/ +/*! + \fn bool QSizeF::qFuzzyIsNull(const QSizeF &size) + \since 6.8 + + Returns \c true if both width and height of the size \a size + are approximately equal to zero. +*/ /***************************************************************************** QSizeF stream functions diff --git a/src/corelib/tools/qsize.h b/src/corelib/tools/qsize.h index a5eaf34afe..67f7146201 100644 --- a/src/corelib/tools/qsize.h +++ b/src/corelib/tools/qsize.h @@ -59,10 +59,10 @@ public: constexpr inline QSize &operator*=(qreal c) noexcept; inline QSize &operator/=(qreal c); - friend inline constexpr bool operator==(const QSize &s1, const QSize &s2) noexcept +private: + friend constexpr bool comparesEqual(const QSize &s1, const QSize &s2) noexcept { return s1.wd == s2.wd && s1.ht == s2.ht; } - friend inline constexpr bool operator!=(const QSize &s1, const QSize &s2) noexcept - { return s1.wd != s2.wd || s1.ht != s2.ht; } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QSize) friend inline constexpr QSize operator+(const QSize &s1, const QSize &s2) noexcept { return QSize(s1.wd + s2.wd, s1.ht + s2.ht); } friend inline constexpr QSize operator-(const QSize &s1, const QSize &s2) noexcept @@ -75,6 +75,7 @@ public: { Q_ASSERT(!qFuzzyIsNull(c)); return QSize(qRound(s.wd / c), qRound(s.ht / c)); } friend inline constexpr size_t qHash(const QSize &, size_t) noexcept; +public: #if defined(Q_OS_DARWIN) || defined(Q_QDOC) [[nodiscard]] CGSize toCGSize() const noexcept; #endif @@ -242,16 +243,25 @@ public: constexpr inline QSizeF &operator*=(qreal c) noexcept; inline QSizeF &operator/=(qreal c); +private: QT_WARNING_PUSH QT_WARNING_DISABLE_FLOAT_COMPARE - friend constexpr inline bool operator==(const QSizeF &s1, const QSizeF &s2) + friend constexpr bool qFuzzyCompare(const QSizeF &s1, const QSizeF &s2) noexcept { + // Cannot use qFuzzyCompare(), because it will give incorrect results + // if one of the arguments is 0.0. return ((!s1.wd || !s2.wd) ? qFuzzyIsNull(s1.wd - s2.wd) : qFuzzyCompare(s1.wd, s2.wd)) && ((!s1.ht || !s2.ht) ? qFuzzyIsNull(s1.ht - s2.ht) : qFuzzyCompare(s1.ht, s2.ht)); } QT_WARNING_POP - friend constexpr inline bool operator!=(const QSizeF &s1, const QSizeF &s2) - { return !(s1 == s2); } + friend constexpr bool qFuzzyIsNull(const QSizeF &size) noexcept + { return qFuzzyIsNull(size.wd) && qFuzzyIsNull(size.ht); } + friend constexpr bool comparesEqual(const QSizeF &lhs, const QSizeF &rhs) noexcept + { return qFuzzyCompare(lhs, rhs); } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QSizeF) + friend constexpr bool comparesEqual(const QSizeF &lhs, const QSize &rhs) noexcept + { return comparesEqual(lhs, rhs.toSizeF()); } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QSizeF, QSize) friend constexpr inline QSizeF operator+(const QSizeF &s1, const QSizeF &s2) noexcept { return QSizeF(s1.wd + s2.wd, s1.ht + s2.ht); } friend constexpr inline QSizeF operator-(const QSizeF &s1, const QSizeF &s2) noexcept @@ -263,6 +273,7 @@ public: friend inline QSizeF operator/(const QSizeF &s, qreal c) { Q_ASSERT(!qFuzzyIsNull(c)); return QSizeF(s.wd / c, s.ht / c); } +public: constexpr inline QSize toSize() const noexcept; #if defined(Q_OS_DARWIN) || defined(Q_QDOC) diff --git a/src/corelib/tools/qspan.h b/src/corelib/tools/qspan.h new file mode 100644 index 0000000000..2671ba374f --- /dev/null +++ b/src/corelib/tools/qspan.h @@ -0,0 +1,475 @@ +// Copyright (C) 2023 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 QSPAN_H +#define QSPAN_H + +#include <QtCore/qcompilerdetection.h> +#include <QtCore/qtypes.h> +#include <QtCore/qcontainerfwd.h> + +#include <array> +#include <cstddef> +#include <cassert> +#include <initializer_list> +#include <QtCore/q20iterator.h> +#include <QtCore/q20memory.h> +#ifdef __cpp_lib_span +#include <span> +#endif +#include <QtCore/q20type_traits.h> + +QT_BEGIN_NAMESPACE + +// like std::dynamic_extent +namespace q20 { + inline constexpr auto dynamic_extent = std::size_t(-1); +} // namespace q20 + +QT_BEGIN_INCLUDE_NAMESPACE +#ifdef __cpp_lib_span +#ifdef __cpp_lib_concepts +namespace std::ranges { +// Officially, these are defined in <ranges>, but that is a heavy-hitter header. +// OTOH, <span> must specialize these variable templates, too, so we assume that +// <span> includes some meaningful subset of <ranges> and just go ahead and use them: +template <typename T, std::size_t E> +constexpr inline bool enable_borrowed_range<QT_PREPEND_NAMESPACE(QSpan)<T, E>> = true; +template <typename T, std::size_t E> +constexpr inline bool enable_view<QT_PREPEND_NAMESPACE(QSpan)<T, E>> = true; +} // namespace std::ranges +#endif // __cpp_lib_concepts +#endif // __cpp_lib_span +QT_END_INCLUDE_NAMESPACE + +namespace QSpanPrivate { + +template <typename T, std::size_t E> class QSpanBase; + +template <typename T> +struct is_qspan_helper : std::false_type {}; +template <typename T, std::size_t E> +struct is_qspan_helper<QSpan<T, E>> : std::true_type {}; +template <typename T, std::size_t E> +struct is_qspan_helper<QSpanBase<T, E>> : std::true_type {}; +template <typename T> +using is_qspan = is_qspan_helper<q20::remove_cvref_t<T>>; + +template <typename T> +struct is_std_span_helper : std::false_type {}; +#ifdef __cpp_lib_span +template <typename T, std::size_t E> +struct is_std_span_helper<std::span<T, E>> : std::true_type {}; +#endif // __cpp_lib_span +template <typename T> +using is_std_span = is_std_span_helper<q20::remove_cvref_t<T>>; + +template <typename T> +struct is_std_array_helper : std::false_type {}; +template <typename T, std::size_t N> +struct is_std_array_helper<std::array<T, N>> : std::true_type {}; +template <typename T> +using is_std_array = is_std_array_helper<q20::remove_cvref_t<T>>; + +template <typename From, typename To> +using is_qualification_conversion = + std::is_convertible<From(*)[], To(*)[]>; // https://eel.is/c++draft/span.cons#note-1 +template <typename From, typename To> +constexpr inline bool is_qualification_conversion_v = is_qualification_conversion<From, To>::value; + +namespace AdlTester { +#define MAKE_ADL_TEST(what) \ + using std:: what; /* bring into scope */ \ + template <typename T> using what ## _result = decltype( what (std::declval<T&&>())); \ + /* end */ +MAKE_ADL_TEST(begin) +MAKE_ADL_TEST(data) +MAKE_ADL_TEST(size) +#undef MAKE_ADL_TEST +} + +// Replacements for std::ranges::XXX(), but only bringing in ADL XXX()s, +// not doing the extra work C++20 requires +template <typename Range> +AdlTester::begin_result<Range> adl_begin(Range &&r) { using std::begin; return begin(r); } +template <typename Range> +AdlTester::data_result<Range> adl_data(Range &&r) { using std::data; return data(r); } +template <typename Range> +AdlTester::size_result<Range> adl_size(Range &&r) { using std::size; return size(r); } + +// Replacement for std::ranges::iterator_t (which depends on C++20 std::ranges::begin) +// This one uses adl_begin() instead. +template <typename Range> +using iterator_t = decltype(QSpanPrivate::adl_begin(std::declval<Range&>())); +template <typename Range> +using range_reference_t = q20::iter_reference_t<QSpanPrivate::iterator_t<Range>>; + +template <typename T> +class QSpanCommon { +protected: + template <typename Iterator> + using is_compatible_iterator = std::conjunction< + // ### C++20: extend to contiguous_iteratorss + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits<Iterator>::iterator_category + >, + is_qualification_conversion< + std::remove_reference_t<q20::iter_reference_t<Iterator>>, + T + > + >; + template <typename Iterator, typename End> + using is_compatible_iterator_and_sentinel = std::conjunction< + // ### C++20: extend to contiguous_iterators and real sentinels + is_compatible_iterator<Iterator>, + std::negation<std::is_convertible<End, std::size_t>> + >; + template <typename Range, typename = void> // wrap use of SFINAE-unfriendly iterator_t: + struct is_compatible_range_helper : std::false_type {}; + template <typename Range> + struct is_compatible_range_helper<Range, std::void_t<QSpanPrivate::iterator_t<Range>>> + : is_compatible_iterator<QSpanPrivate::iterator_t<Range>> {}; + template <typename Range> + using is_compatible_range = std::conjunction< + // ### C++20: extend to contiguous_iterators + std::negation<is_qspan<Range>>, + std::negation<is_std_span<Range>>, + std::negation<is_std_array<Range>>, + std::negation<std::is_array<q20::remove_cvref_t<Range>>>, + is_compatible_range_helper<Range> + >; + + // constraints + template <typename Iterator> + using if_compatible_iterator = std::enable_if_t< + is_compatible_iterator<Iterator>::value + , bool>; + template <typename Iterator, typename End> + using if_compatible_iterator_and_sentinel = std::enable_if_t< + is_compatible_iterator_and_sentinel<Iterator, End>::value + , bool>; + template <typename Range> + using if_compatible_range = std::enable_if_t<is_compatible_range<Range>::value, bool>; +}; // class QSpanCommon + +template <typename T, std::size_t E> +class QSpanBase : protected QSpanCommon<T> +{ + static_assert(E < size_t{(std::numeric_limits<qsizetype>::max)()}, + "QSpan only supports extents that fit into the signed size type (qsizetype)."); + + struct Enabled_t { explicit Enabled_t() = default; }; + static inline constexpr Enabled_t Enable{}; + + template <typename S, std::size_t N> + using if_compatible_array = std::enable_if_t< + N == E && is_qualification_conversion_v<S, T> + , bool>; + + template <typename S> + using if_qualification_conversion = std::enable_if_t< + is_qualification_conversion_v<S, T> + , bool>; +protected: + using Base = QSpanCommon<T>; + + // data members: + T *m_data; + static constexpr qsizetype m_size = qsizetype(E); + + // types and constants: + // (in QSpan only) + + // constructors (need to be public d/t the way ctor inheriting works): +public: + template <std::size_t E2 = E, std::enable_if_t<E2 == 0, bool> = true> + Q_IMPLICIT constexpr QSpanBase() noexcept : m_data{nullptr} {} + + template <typename It, typename Base::template if_compatible_iterator<It> = true> + explicit constexpr QSpanBase(It first, qsizetype count) + : m_data{q20::to_address(first)} + { + Q_ASSERT(count == m_size); + } + + template <typename It, typename End, typename Base::template if_compatible_iterator_and_sentinel<It, End> = true> + explicit constexpr QSpanBase(It first, End last) + : QSpanBase(first, last - first) {} + + template <size_t N, std::enable_if_t<N == E, bool> = true> + Q_IMPLICIT constexpr QSpanBase(q20::type_identity_t<T> (&arr)[N]) noexcept + : QSpanBase(arr, N) {} + + template <typename S, size_t N, if_compatible_array<S, N> = true> + Q_IMPLICIT constexpr QSpanBase(std::array<S, N> &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template <typename S, size_t N, if_compatible_array<S, N> = true> + Q_IMPLICIT constexpr QSpanBase(const std::array<S, N> &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template <typename Range, typename Base::template if_compatible_range<Range> = true> + Q_IMPLICIT constexpr QSpanBase(Range &&r) + : QSpanBase(QSpanPrivate::adl_data(r), // no forward<>() here (std doesn't have it, either) + qsizetype(QSpanPrivate::adl_size(r))) // ditto, no forward<>() + {} + + template <typename S, if_qualification_conversion<S> = true> + Q_IMPLICIT constexpr QSpanBase(QSpan<S, E> other) noexcept + : QSpanBase(other.data(), other.size()) + {} + + template <typename S, if_qualification_conversion<S> = true> + Q_IMPLICIT constexpr QSpanBase(QSpan<S> other) + : QSpanBase(other.data(), other.size()) + {} + + template <typename U = T, std::enable_if_t<std::is_const_v<U>, bool> = true> + Q_IMPLICIT constexpr QSpanBase(std::initializer_list<std::remove_cv_t<T>> il) + : QSpanBase(il.begin(), il.size()) + {} + +#ifdef __cpp_lib_span + template <typename S, if_qualification_conversion<S> = true> + Q_IMPLICIT constexpr QSpanBase(std::span<S, E> other) noexcept + : QSpanBase(other.data(), other.size()) + {} + + template <typename S, if_qualification_conversion<S> = true> + Q_IMPLICIT constexpr QSpanBase(std::span<S> other) + : QSpanBase(other.data(), other.size()) + {} +#endif // __cpp_lib_span +}; // class QSpanBase (fixed extent) + +template <typename T> +class QSpanBase<T, q20::dynamic_extent> : protected QSpanCommon<T> +{ + template <typename S> + using if_qualification_conversion = std::enable_if_t< + is_qualification_conversion_v<S, T> + , bool>; +protected: + using Base = QSpanCommon<T>; + + // data members: + T *m_data; + qsizetype m_size; + + // constructors (need to be public d/t the way ctor inheriting works): +public: + Q_IMPLICIT constexpr QSpanBase() noexcept : m_data{nullptr}, m_size{0} {} + + template <typename It, typename Base::template if_compatible_iterator<It> = true> + Q_IMPLICIT constexpr QSpanBase(It first, qsizetype count) + : m_data{q20::to_address(first)}, m_size{count} {} + + template <typename It, typename End, typename Base::template if_compatible_iterator_and_sentinel<It, End> = true> + Q_IMPLICIT constexpr QSpanBase(It first, End last) + : QSpanBase(first, last - first) {} + + template <size_t N> + Q_IMPLICIT constexpr QSpanBase(q20::type_identity_t<T> (&arr)[N]) noexcept + : QSpanBase(arr, N) {} + + template <typename S, size_t N, if_qualification_conversion<S> = true> + Q_IMPLICIT constexpr QSpanBase(std::array<S, N> &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template <typename S, size_t N, if_qualification_conversion<S> = true> + Q_IMPLICIT constexpr QSpanBase(const std::array<S, N> &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template <typename Range, typename Base::template if_compatible_range<Range> = true> + Q_IMPLICIT constexpr QSpanBase(Range &&r) + : QSpanBase(QSpanPrivate::adl_data(r), // no forward<>() here (std doesn't have it, either) + qsizetype(QSpanPrivate::adl_size(r))) // ditto, no forward<>() + {} + + template <typename S, size_t N, if_qualification_conversion<S> = true> + Q_IMPLICIT constexpr QSpanBase(QSpan<S, N> other) noexcept + : QSpanBase(other.data(), other.size()) + {} + + template <typename U = T, std::enable_if_t<std::is_const_v<U>, bool> = true> + Q_IMPLICIT constexpr QSpanBase(std::initializer_list<std::remove_cv_t<T>> il) noexcept + : QSpanBase(il.begin(), il.size()) + {} + +#ifdef __cpp_lib_span + template <typename S, size_t N, if_qualification_conversion<S> = true> + Q_IMPLICIT constexpr QSpanBase(std::span<S, N> other) noexcept + : QSpanBase(other.data(), other.size()) + {} +#endif // __cpp_lib_span +}; // class QSpanBase (dynamic extent) + +} // namespace QSpanPrivate + +template <typename T, std::size_t E> +class QSpan +#ifndef Q_QDOC + : private QSpanPrivate::QSpanBase<T, E> +#endif +{ + using Base = QSpanPrivate::QSpanBase<T, E>; + Q_ALWAYS_INLINE constexpr void verify([[maybe_unused]] qsizetype pos = 0, + [[maybe_unused]] qsizetype n = 1) const + { + Q_ASSERT(pos >= 0); + Q_ASSERT(pos <= size()); + Q_ASSERT(n >= 0); + Q_ASSERT(n <= size() - pos); + } + + template <std::size_t N> + static constexpr bool subspan_always_succeeds_v = N <= E && E != q20::dynamic_extent; +public: + // constants and types + using value_type = std::remove_cv_t<T>; +#ifdef QT_COMPILER_HAS_LWG3346 + using iterator_concept = std::contiguous_iterator_tag; + using element_type = T; +#endif + using size_type = qsizetype; // difference to std::span + using difference_type = qptrdiff; // difference to std::span + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using iterator = pointer; // implementation-defined choice + using const_iterator = const_pointer; // implementation-defined choice + using reverse_iterator = std::reverse_iterator<iterator>; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + static constexpr std::size_t extent = E; + + // [span.cons], constructors, copy, and assignment + using Base::Base; +#ifdef Q_QDOC + template <typename It> using if_compatible_iterator = bool; + template <typename S> using if_qualification_conversion = bool; + template <typename Range> using if_compatible_range = bool; + template <typename It, if_compatible_iterator<It> = true> constexpr QSpan(It first, qsizetype count); + template <typename It, if_compatible_iterator<It> = true> constexpr QSpan(It first, It last); + template <size_t N> constexpr QSpan(q20::type_identity_t<T> (&arr)[N]) noexcept; + template <typename S, size_t N, if_qualification_conversion<S> = true> constexpr QSpan(std::array<S, N> &arr) noexcept; + template <typename S, size_t N, if_qualification_conversion<S> = true> constexpr QSpan(const std::array<S, N> &arr) noexcept; + template <typename Range, if_compatible_range<Range> = true> constexpr QSpan(Range &&r); + template <typename S, size_t N, if_qualification_conversion<S> = true> constexpr QSpan(QSpan<S, N> other) noexcept; + template <typename S, size_t N, if_qualification_conversion<S> = true> constexpr QSpan(std::span<S, N> other) noexcept; + constexpr QSpan(std::initializer_list<value_type> il); +#endif // Q_QDOC + + // [span.obs] + [[nodiscard]] constexpr size_type size() const noexcept { return this->m_size; } + [[nodiscard]] constexpr size_type size_bytes() const noexcept { return size() * sizeof(T); } + [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } + + // [span.elem] + [[nodiscard]] constexpr reference operator[](size_type idx) const + { verify(idx); return data()[idx]; } + [[nodiscard]] constexpr reference front() const { verify(); return *data(); } + [[nodiscard]] constexpr reference back() const { verify(); return data()[size() - 1]; } + [[nodiscard]] constexpr pointer data() const noexcept { return this->m_data; } + + // [span.iterators] + [[nodiscard]] constexpr iterator begin() const noexcept { return data(); } + [[nodiscard]] constexpr iterator end() const noexcept { return data() + size(); } + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); } + [[nodiscard]] constexpr const_iterator cend() const noexcept { return end(); } + [[nodiscard]] constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } + [[nodiscard]] constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } + [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return rend(); } + + // [span.sub] + template <std::size_t Count> + [[nodiscard]] constexpr QSpan<T, Count> first() const + noexcept(subspan_always_succeeds_v<Count>) + { + static_assert(Count <= E, + "Count cannot be larger than the span's extent."); + verify(0, Count); + return QSpan<T, Count>{data(), Count}; + } + + template <std::size_t Count> + [[nodiscard]] constexpr QSpan<T, Count> last() const + noexcept(subspan_always_succeeds_v<Count>) + { + static_assert(Count <= E, + "Count cannot be larger than the span's extent."); + verify(0, Count); + return QSpan<T, Count>{data() + (size() - Count), Count}; + } + + template <std::size_t Offset> + [[nodiscard]] constexpr auto subspan() const + noexcept(subspan_always_succeeds_v<Offset>) + { + static_assert(Offset <= E, + "Offset cannot be larger than the span's extent."); + verify(Offset, 0); + if constexpr (E == q20::dynamic_extent) + return QSpan<T>{data() + Offset, qsizetype(size() - Offset)}; + else + return QSpan<T, E - Offset>{data() + Offset, qsizetype(E - Offset)}; + } + + template <std::size_t Offset, std::size_t Count> + [[nodiscard]] constexpr auto subspan() const + noexcept(subspan_always_succeeds_v<Offset + Count>) + { return subspan<Offset>().template first<Count>(); } + + [[nodiscard]] constexpr QSpan<T> first(size_type n) const { verify(0, n); return {data(), n}; } + [[nodiscard]] constexpr QSpan<T> last(size_type n) const { verify(0, n); return {data() + (size() - n), n}; } + [[nodiscard]] constexpr QSpan<T> subspan(size_type pos) const { verify(pos, 0); return {data() + pos, size() - pos}; } + [[nodiscard]] constexpr QSpan<T> subspan(size_type pos, size_type n) const { return subspan(pos).first(n); } + + // Qt-compatibility API: + [[nodiscard]] bool isEmpty() const noexcept { return empty(); } + // nullary first()/last() clash with first<>() and last<>(), so they're not provided for QSpan + [[nodiscard]] constexpr QSpan<T> sliced(size_type pos) const { return subspan(pos); } + [[nodiscard]] constexpr QSpan<T> sliced(size_type pos, size_type n) const { return subspan(pos, n); } + +private: + // [span.objectrep] + [[nodiscard]] friend + QSpan<const std::byte, E == q20::dynamic_extent ? q20::dynamic_extent : E * sizeof(T)> + as_bytes(QSpan s) noexcept + { + using R = QSpan<const std::byte, E == q20::dynamic_extent ? q20::dynamic_extent : E * sizeof(T)>; + return R{reinterpret_cast<const std::byte *>(s.data()), s.size_bytes()}; + } + + template <typename U> + using if_mutable = std::enable_if_t<!std::is_const_v<U>, bool>; + +#ifndef Q_QDOC + template <typename T2 = T, if_mutable<T2> = true> +#endif + [[nodiscard]] friend + QSpan<std::byte, E == q20::dynamic_extent ? q20::dynamic_extent : E * sizeof(T)> + as_writable_bytes(QSpan s) noexcept + { + using R = QSpan<std::byte, E == q20::dynamic_extent ? q20::dynamic_extent : E * sizeof(T)>; + return R{reinterpret_cast<std::byte *>(s.data()), s.size_bytes()}; + } +}; // class QSpan + +// [span.deduct] +template <class It, class EndOrSize> +QSpan(It, EndOrSize) -> QSpan<std::remove_reference_t<q20::iter_reference_t<It>>>; +template <class T, std::size_t N> +QSpan(T (&)[N]) -> QSpan<T, N>; +template <class T, std::size_t N> +QSpan(std::array<T, N> &) -> QSpan<T, N>; +template <class T, std::size_t N> +QSpan(const std::array<T, N> &) -> QSpan<const T, N>; +template <class R> +QSpan(R&&) -> QSpan<std::remove_reference_t<QSpanPrivate::range_reference_t<R>>>; + +QT_END_NAMESPACE + +#endif // QSPAN_H diff --git a/src/corelib/tools/qspan.qdoc b/src/corelib/tools/qspan.qdoc new file mode 100644 index 0000000000..9b55b09bf7 --- /dev/null +++ b/src/corelib/tools/qspan.qdoc @@ -0,0 +1,683 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QSpan + \inmodule QtCore + \since 6.7 + \brief A non-owning container over contiguous data. + \ingroup tools + \reentrant + + A QSpan references a contiguous portion of another contiguous container. + It acts as an interface type for all kinds of contiguous containers, + without the need to construct an owning container such as QList or + std::vector first. + + The data referenced by a QSpan may be represented as an array (or + array-compatible data-structure such as QList, std::vector, + QVarLengthArray, etc.). QSpan itself merely stores a pointer to the data, + so users must ensure that QSpan objects do not outlive the data they + reference. + + Unlike views such as QStringView, QLatin1StringView and QUtf8StringView, + referenced data can be modified through a QSpan object. To prevent this, + construct a QSpan over a \c{const T}: + + \code + int numbers[] = {0, 1, 2}; + QSpan<int> span = numbers; + span[0] = 42; + // numbers == {42, 1, 2}; + QSpan<const int> cspan = numbers; + cspan[0] = 0; // ERROR: cspan[0] is read-only + \endcode + + A QSpan can be \e{fixed-size} or \e{variable-sized}. + + A variable-sized span is formed by omitting the second template argument + (or setting it to \c{std::dynamic_extent}, which is, however, only + available in C++20 builds), as seen in the example above. + + A fixed-size span is formed by passing a number as the second template + argument: + + \code + int numbers[] = {0, 1, 2}; + QSpan<int, 3> span = numbers; + QSpan<const int, 3> = numbers; // also OK + \endcode + + As the name suggests, a fixed-size span's size() is fixed at compile-time + whereas the size() of a variable-sized span is determined only at run-time. + + A fixed-size span is not default-constructible (unless its \l extent is zero + (0)). A variable-sized span \e{is} default-constructible and will have + \c{data() == nullptr} and \c{size() == 0}. + + A fixed-size span can be implicitly converted into a variable-sized one. + The opposite direction (variable-length into fixed-length) has the + precondition that both span's sizes must match. + + Unlike with owning containers, \c{const} is \e{shallow} in QSpan: you can + still modify the data through a const QSpan (but not through a + \c{QSpan<const T>}), and begin() and end() are not overloaded on + \c{const}/non-\c{const}. There are cbegin() and cend(), though, that return + const_iterators which prevent modification of the data even though \c{T} is + not const: + \code + int numbers[] = {0, 1, 2}; + const QSpan<int> span = numbers; + span.front() = 42; // OK, numbers[0] == 42 now + *span.begin() = 31; // OK, numbers[0] == 31 now + *span.cbegin() = -1; // ERROR: cannot assign through a const_iterator + \endcode + + QSpan should be passed by value, not by reference-to-const: + + \code + void consume(QSpan<const int> data); // OK + void consume(const QSpan<const int> &data); // works, but is non-idiomatic and less efficient + \endcode + + \c{QSpan<T,N>} is a \e{Literal Type}, regardless of whether \c{T} is a + Literal Type or not. + + \section2 QSpan vs. std::span + \target span-STL + + QSpan is closely modelled after + \l{https://en.cppreference.com/w/cpp/container/span}{std::span}, but has a + few differences which we'll discuss here. Since they both implicitly + convert into each other, you're free to choose whichever one you like best + in your own code. + + \list + \li QSpan is using the signed qsizetype as \c{size_type} + whereas \c{std::span} uses \c{size_t}. + \li All QSpan constructors are implicit; + many \c{std::span} ones are \c{explicit}. + \li QSpan can be constructed from rvalue owning containers, \c{std::span} can not. + \endlist + + The last two are required for source-compatibility when functions that took + owning containers are converted to take QSpan instead, which is a + vitally-important use-case in Qt. The use of qsizetype is for consistency + with the rest of Qt containers. QSpan template arguments still use size_t + to avoid introducing unnecessary error conditions (negative sizes). + + \section2 Compatible Iterators + \target span-compatible-iterators + + QSpan can be constructed from an iterator and size or from an + iterator pair, provided the iterators are \e{compatible} ones. + Eventually, this should mean C++20 \c{std::contiguous_iterator} and + \c{std::sentinel_for}, but while Qt still supports C++17, only raw pointers + are considered contiguous iterators. + + \section2 Compatible Ranges + \target span-compatible-ranges + + QSpan can also be constructed from a \e{compatible} range. A range is + compatible if it has \l{span-compatible-iterators}{compatible iterators}. + + \sa QList, QStringView, QLatin1StringView, QUtf8StringView +*/ + +// +// Nested types and constants +// + +/*! + \typedef QSpan::element_type + + An alias for \c{T}. Includes the \c{const}, if any. + + This alias is provided for compatbility with the STL. + + \sa value_type, pointer +*/ + +/*! + \typedef QSpan::value_type + + An alias for \c{T}. Excludes the \c{const}, if any. + + This alias is provided for compatbility with the STL. + + \sa element_type +*/ + +/*! + \typedef QSpan::size_type + + An alias for qsizetype. This \l{span-STL}{differs from \c{std::span}}. + + This alias is provided for compatbility with the STL. +*/ + +/*! + \typedef QSpan::difference_type + + An alias for qptrdiff. This \l{span-STL}{differs from \c{std::span}}. + + This alias is provided for compatbility with the STL. +*/ + +/*! + \typedef QSpan::pointer + + An alias for \c{T*} and \c{element_type*}, respectively. Includes the \c{const}, if any. + + This alias is provided for compatbility with the STL. + + \sa element_type, const_pointer, reference, iterator +*/ + +/*! + \typedef QSpan::const_pointer + + An alias for \c{const T*} and \c{const element_type*}, respectively. + + This alias is provided for compatbility with the STL. + + \sa element_type, pointer, const_reference, const_iterator +*/ + +/*! + \typedef QSpan::reference + + An alias for \c{T&} and \c{element_type&}, respectively. Includes the \c{const}, if any. + + This alias is provided for compatbility with the STL. + + \sa element_type, const_reference, pointer +*/ + +/*! + \typedef QSpan::const_reference + + An alias for \c{const T&} and \c{const element_type&}, respectively. + + This alias is provided for compatbility with the STL. + + \sa element_type, reference, const_pointer +*/ + +/*! + \typedef QSpan::iterator + + An alias for \c{T*} and \c{pointer}, respectively. Includes the \c{const}, if any. + + \sa pointer, const_iterator, reverse_iterator +*/ + +/*! + \typedef QSpan::const_iterator + + An alias for \c{const T*} and \c{const_pointer}, respectively. + + \sa const_pointer, iterator, const_reverse_iterator +*/ + +/*! + \typedef QSpan::reverse_iterator + + An alias for \c{std::reverse_iterator<iterator>}. Includes the \c{const}, if any. + + \sa iterator, const_reverse_iterator +*/ + +/*! + \typedef QSpan::const_reverse_iterator + + An alias for \c{std::reverse_iterator<const_iterator>}. + + \sa const_iterator, reverse_iterator +*/ + +/*! + \variable QSpan::extent + + The second template argument of \c{QSpan<T, E>}, that is, \c{E}. This is + \c{std::dynamic_extent} for variable-sized spans. + + \note While all other sizes and indexes in QSpan use qsizetype, this + variable, like \c{E}, is actually of type \c{size_t}, for compatibility with + \c{std::span} and \c{std::dynamic_extent}. + + \sa size() +*/ + +// +// Constructors and SMFs +// + +/*! + \fn template <typename T, size_t E> QSpan<T,E>::QSpan() + + Default constructor. + + This constructor is only present if \c{E} is either zero (0) or + \c{std::dynamic_extent}. In other words: only fixed-zero-sized or variable-sized spans + are default-constructible. + + \sa extent +*/ + +/*! + \fn template <typename T, size_t E> QSpan<T,E>::QSpan(const QSpan &other) + \fn template <typename T, size_t E> QSpan<T,E>::QSpan(QSpan &&other) + \fn template <typename T, size_t E> QSpan<T,E> &QSpan<T,E>::operator=(const QSpan &other) + \fn template <typename T, size_t E> QSpan<T,E> &QSpan<T,E>::operator=(QSpan &&other) + \fn template <typename T, size_t E> QSpan<T,E>::~QSpan() + + These Special Member Functions are implicitly-defined. + + \note Moves are equivalent to copies. Only data() and size() are copied + from span to span, not the referenced data. +*/ + +/*! + \fn template <typename T, size_t E> template <typename It, QSpan<T, E>::if_compatible_iterator<It>> QSpan<T,E>::QSpan(It first, qsizetype count) + + Constructs a QSpan referencing the data starting at \a first and having length + \a count. + + \c{[first, count)} must be a valid range. + + \note This constructor participates in overload resolution only if \c{It} + is \l{span-compatible-iterators}{a compatible iterator}. +*/ + +/*! + \fn template <typename T, size_t E> template <typename It, QSpan<T, E>::if_compatible_iterator<It>> QSpan<T,E>::QSpan(It first, It last) + + Constructs a QSpan referencing the data starting at \a first and having length + (\a last - \a first). + + \c{[first, last)} must be a valid range. + + \note This constructor participates in overload resolution only if \c{It} + is \l{span-compatible-iterators}{a compatible iterator}. +*/ + +/*! + \fn template <typename T, size_t E> template <size_t N> QSpan<T,E>::QSpan(q20::type_identity_t<T> (&arr)[N]); + \fn template <typename T, size_t E> template <typename S, size_t N, QSpan<T, E>::if_qualification_conversion<S> = true> QSpan<T,E>::QSpan(std::array<S, N> &arr); + \fn template <typename T, size_t E> template <typename S, size_t N, QSpan<T, E>::if_qualification_conversion<S> = true> QSpan<T,E>::QSpan(const std::array<S, N> &arr); + + Constructs a QSpan referencing the data in the supplied array \a arr. + + \note This constructor participates in overload resolution only if + \list + \li either \c{N} or \l{extent} are \c{std::dynamic_extent} or otherwise \l{extent} \c{==} \c{N} + \li and either \c{S} or \c{const S} are the same as \c{T}. + \endlist + + \note \c{q20::type_identity_t} is a C++17 backport of C++20's + \l{https://en.cppreference.com/w/cpp/types/type_identity}{\c{std::type_identity_t}}. +*/ + +/*! + \fn template <typename T, size_t E> template <typename Range, QSpan<T, E>::if_compatible_range<Range> = true> QSpan<T,E>::QSpan(Range &&r) + + Constructs a QSpan referencing the data in the supplied range \a r. + + \note This constructor participates in overload resolution only if \c{Range} + is \l{span-compatible-ranges}{a compatible range}. +*/ + +/*! + \fn template <typename T, size_t E> template <typename S, size_t N, QSpan<T, E>::if_qualification_conversion<S> = true> QSpan<T,E>::QSpan(QSpan<S, N> other); + \fn template <typename T, size_t E> template <typename S, size_t N, QSpan<T, E>::if_qualification_conversion<S> = true> QSpan<T,E>::QSpan(std::span<S, N> other); + + Constructs a QSpan referencing the data in the supplied span \a other. + + \note This constructor participates in overload resolution only if + \list + \li either \c{N} or \l{extent} are \c{std::dynamic_extent} or \l{extent} \c{==} \c{N} + \li and either \c{S} or \c{const S} are the same as \c{T}. + \endlist +*/ + +/*! + \fn template <typename T, size_t E> QSpan<T, E>::QSpan(std::initializer_list<value_type> il); + + Constructs a QSpan referencing the data in the supplied initializer list \a il. + + \note This constructor participates in overload resolution only if \c{T} is \c{const}-qualified. + + \note This constructor is \c{noexcept} only if \c{E} is \c{std::dynamic_extent}. + + \note If \c{E} is not \c{std::dynamic_extent} and the size of \a il is not \c{E}, the behavior is undefined. +*/ + +// +// Member functions: sizes +// + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::size() const + + Returns the size of the span, that is, the number of elements it references. + + \sa size_bytes(), empty(), isEmpty() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::size_bytes() const + + Returns the size of the span in bytes, that is, the number of elements + multiplied by \c{sizeof(T)}. + + \sa size(), empty(), isEmpty() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::empty() const + \fn template <typename T, size_t E> auto QSpan<T, E>::isEmpty() const + + Returns whether the span is empty, that is, whether \c{size() == 0}. + + These functions do the same thing: empty() is provided for STL + compatibility and isEmpty() is provided for Qt compatibility. + + \sa size(), size_bytes() +*/ + +// +// element access +// + +/*! + \fn template <typename T, size_t E> QSpan<T, E>::operator[](size_type idx) const + + Returns a reference to the element at index \a idx in the span. + + The index must be in range, that is, \a idx >= 0 and \a idx < size(), + otherwise the behavior is undefined. + + \sa front(), back(), size(), empty() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::front() const + + Returns a reference to the first element in the span. + + The span must not be empty, otherwise the behavior is undefined. + + \sa operator[](), back(), size(), empty() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::back() const + + Returns a reference to the last element in the span. + + The span must not be empty, otherwise the behavior is undefined. + + \sa operator[](), front(), size(), empty() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::data() const + + Returns a pointer to the beginning of the span. + + The same as calling begin(). + + \sa begin(), front() +*/ + +// +// iterators +// + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::begin() const + + Returns an interator pointing at the beginning of the span. + + Because QSpan iterators are just pointers, this is the same as calling + data(). + + \sa end(), cbegin(), rbegin(), crbegin(), data() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::end() const + + Returns an iterator pointing to one past the end of the span. + + Because QSpan iterators are just pointers, this it the same as calling + \c{data() + size()}. + + \sa begin(), cend(), rend(), crend(), data(), size() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::cbegin() const + + Returns a const_iterator pointing to the beginning of the span. + + This will return a read-only iterator even if \c{T} is not \c{const}: + \code + QSpan<int> span = ~~~; + *span.begin() = 42; // OK + *span.cbegin() = 42; // ERROR: cannot assign through a const_iterator + \endcode + + \sa cend(), begin(), crbegin(), rbegin(), data() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::cend() const + + Returns a const_iterator pointing to one past the end of the span. + + \sa cbegin(), end(), crend(), rend(), data(), size() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::rbegin() const + + Returns a reverse_iterator pointing to the beginning of the reversed span. + + \sa rend(), crbegin(), begin(), cbegin() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::rend() const + + Returns a reverse_iterator pointing to one past the end of the reversed span. + + \sa rbegin(), crend(), end(), cend() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::crbegin() const + + Returns a const_reverse_iterator pointing to the beginning of the reversed span. + + \sa crend(), rbegin(), cbegin(), begin() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::crend() const + + Returns a const_reverse_iterator pointing to one past the end of the reversed span. + + \sa crbegin(), rend(), cend(), end() +*/ + +// +// compile-time subspans: +// + +/*! + \fn template <typename T, size_t E> template <std::size_t Count> auto QSpan<T, E>::first() const + \keyword first-t + + Returns a fixed-sized span of size \c{Count} referencing the first \c{Count} elements of \c{*this}. + + The span must hold at least \c{Count} elements (\c{E} >= \c{Count} \e{and} + size() >= \c{Count}), otherwise the behavior is undefined. + + \sa first(QSpan<T,E>::size_type), last(), subspan() +*/ + +/*! + \fn template <typename T, size_t E> template <std::size_t Count> auto QSpan<T, E>::last() const + \keyword last-t + + Returns a fixed-sized span of size \c{Count} referencing the last \c{Count} elements of \c{*this}. + + The span must hold at least \c{Count} elements (\c{E} >= \c{Count} \e{and} + size() >= \c{Count}), otherwise the behavior is undefined. + + \sa last(QSpan<T,E>::size_type), first(), subspan() +*/ + +/*! + \fn template <typename T, size_t E> template <std::size_t Offset> auto QSpan<T, E>::subspan() const + \keyword subspan-t1 + + Returns a span of size \c{E - Offset} referencing the remainder of this span + after dropping the first \c{Offset} elements. + + If \c{*this} is a variable-sized span, the return type is a variable-sized + span, otherwise it is a fixed-sized span. + + This span must hold at least \c{Offset} elements (\c{E} >= \c{Offset} \e{and} + size() >= \c{Offset}), otherwise the behavior is undefined. + + \sa subspan(QSpan<T,E>::size_type), subspan(), first(), last() +*/ + +/*! + \fn template <typename T, size_t E> template <std::size_t Offset, std::size_t Count> auto QSpan<T, E>::subspan() const + \keyword subspan-t2 + + Returns a span of size \c{Count} referencing the \c{Count} elements of this + span starting at \c{Offset}. + + If \c{*this} is a variable-sized span, the return type is a variable-sized + span, otherwise it is a fixed-sized span. + + This span must hold at least \c{Offset + Count} elements (\c{E} >= + \c{Offset + Count} \e{and} size() >= \c{Offset + Count}), otherwise the + behavior is undefined. + + \sa subspan(QSpan<T,E>::size_type, QSpan<T,E>::size_type), subspan(), first(), last() +*/ + +// +// runtime subspans: +// + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::first(qsizetype n) const + \keyword first-n + + Returns a variable-sized span of size \a n referencing the first \a n elements of \c{*this}. + + \a n must be non-negative. + + The span must hold at least \a n elements (\c{E} >= \a n \e{and} size() >= + \a n), otherwise the behavior is undefined. + + \sa {first-t}{first<N>()}, last(QSpan<T,E>::size_type), subspan(QSpan<T,E>::size_type), + subspan(QSpan<T,E>::size_type, QSpan<T,E>::size_type) + \sa sliced() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::last(qsizetype n) const + \keyword last-n + + Returns a variable-sized span of size \a n referencing the last \a n elements of \c{*this}. + + \a n must be non-negative. + + The span must hold at least \a n elements (\c{E} >= \a n \e{and} + size() >= \a n), otherwise the behavior is undefined. + + \sa last(), first(QSpan<T,E>::size_type), subspan(QSpan<T,E>::size_type), + subspan(QSpan<T,E>::size_type, QSpan<T,E>::size_type), sliced() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::subspan(qsizetype pos) const + \fn template <typename T, size_t E> auto QSpan<T, E>::sliced(qsizetype pos) const + \keyword subspan-n1 + + Returns a variable-sized span of size \c{size() - pos} referencing the + remainder of this span after dropping the first \a pos elements. + + \a pos must be non-negative. + + This span must hold at least \a pos elements (\c{E} >= \a pos \e{and} + size() >= \a pos), otherwise the behavior is undefined. + + These functions do the same thing: subspan() is provided for STL + compatibility and sliced() is provided for Qt compatibility. + + \sa subspan(), first(QSpan<T,E>::size_type), last(QSpan<T,E>::size_type) +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::subspan(qsizetype pos, qsizetype n) const + \fn template <typename T, size_t E> auto QSpan<T, E>::sliced(qsizetype pos, qsizetype n) const + \keyword subspan-n2 + + Returns a variable-sized span of size \a n referencing the \a n elements of + this span starting at \a pos. + + Both \a pos and \a n must be non-negative. + + This span must hold at least \c{pos + n} elements (\c{E} >= + \c{pos + n} \e{and} size() >= \c{pos + n}), otherwise the + behavior is undefined. + + These functions do the same thing: subspan() is provided for STL + compatibility and sliced() is provided for Qt compatibility. + + \sa subspan(), first(QSpan<T,E>::size_type), last(QSpan<T,E>::size_type) +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::as_bytes(QSpan s) + \since 6.8 + + Returns \a s as a \c{QSpan<const std::byte, E'>} whose size() is equal to + \c{s.size_bytes()}. + + If \c{E} is \c{std::dynamic_extent} then so is \c{E'}. + Otherwise, \c{E' = E * sizeof(T)}. + + \note \c{q20::dynamic_extent} is a C++17 backport of C++20's + \l{https://en.cppreference.com/w/cpp/container/span/dynamic_extent}{\c{std::dynamic_extent}}. + + \sa as_writable_bytes(), size_bytes() +*/ + +/*! + \fn template <typename T, size_t E> auto QSpan<T, E>::as_writable_bytes(QSpan s) + \since 6.8 + + Returns \a s as a \c{QSpan<std::byte, E'>} whose size() is equal to + \c{s.size_bytes()}. + + If \c{E} is \c{std::dynamic_extent} then so is \c{E'}. + Otherwise, \c{E' = E * sizeof(T)}. + + \note This function participates in overload resolution only if + \c{!std::is_const_v<T>}. + + \note \c{q20::dynamic_extent} is a C++17 backport of C++20's + \l{https://en.cppreference.com/w/cpp/container/span/dynamic_extent}{\c{std::dynamic_extent}}. + + \sa as_bytes(), size_bytes() +*/ diff --git a/src/corelib/tools/qspan_p.h b/src/corelib/tools/qspan_p.h new file mode 100644 index 0000000000..0072e3ef64 --- /dev/null +++ b/src/corelib/tools/qspan_p.h @@ -0,0 +1,24 @@ +// Copyright (C) 2023 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 QSPAN_P_H +#define QSPAN_P_H + +#include <QtCore/qspan.h> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +QT_END_NAMESPACE + +#endif // QSPAN_P_H diff --git a/src/corelib/tools/qtaggedpointer.h b/src/corelib/tools/qtaggedpointer.h index bc43f765aa..6c467d59f8 100644 --- a/src/corelib/tools/qtaggedpointer.h +++ b/src/corelib/tools/qtaggedpointer.h @@ -43,10 +43,10 @@ public: static constexpr quintptr tagMask() { return QtPrivate::TagInfo<T>::alignment - 1; } static constexpr quintptr pointerMask() { return ~tagMask(); } - constexpr QTaggedPointer() noexcept : d(0) {} - constexpr QTaggedPointer(std::nullptr_t) noexcept : QTaggedPointer() {} + Q_NODISCARD_CTOR constexpr QTaggedPointer() noexcept : d(0) {} + Q_NODISCARD_CTOR constexpr QTaggedPointer(std::nullptr_t) noexcept : QTaggedPointer() {} - explicit QTaggedPointer(T *pointer, Tag tag = Tag()) noexcept + Q_NODISCARD_CTOR explicit QTaggedPointer(T *pointer, Tag tag = Tag()) noexcept : d(quintptr(pointer) | quintptr(tag)) { static_assert(sizeof(Type*) == sizeof(QTaggedPointer)); diff --git a/src/corelib/tools/qtimeline.cpp b/src/corelib/tools/qtimeline.cpp index 3bfe000bca..5512da867f 100644 --- a/src/corelib/tools/qtimeline.cpp +++ b/src/corelib/tools/qtimeline.cpp @@ -55,16 +55,16 @@ void QTimeLinePrivate::setCurrentTime(int msecs) { Q_Q(QTimeLine); currentTime.removeBindingUnlessInWrapper(); - auto previousCurrentTime = currentTime.value(); + const auto previousCurrentTime = currentTime.valueBypassingBindings(); - qreal lastValue = q->currentValue(); - int lastFrame = q->currentFrame(); + const qreal lastValue = q->valueForTime(previousCurrentTime); + const int lastFrame = q->frameForTime(previousCurrentTime); // Determine if we are looping. - int elapsed = (direction == QTimeLine::Backward) ? (-msecs + duration) : msecs; - int loopCountNow = elapsed / duration; + const int elapsed = (direction == QTimeLine::Backward) ? (-msecs + duration) : msecs; + const int loopCountNow = elapsed / duration; - bool looping = (loopCountNow != currentLoopCount); + const bool looping = (loopCountNow != currentLoopCount); #ifdef QTIMELINE_DEBUG qDebug() << "QTimeLinePrivate::setCurrentTime:" << msecs << duration << "with loopCountNow" << loopCountNow << "currentLoopCount" << currentLoopCount << "looping" << looping; @@ -75,7 +75,7 @@ void QTimeLinePrivate::setCurrentTime(int msecs) // Normalize msecs to be between 0 and duration, inclusive. currentTime.setValueBypassingBindings(elapsed % duration); if (direction.value() == QTimeLine::Backward) - currentTime.setValueBypassingBindings(duration - currentTime); + currentTime.setValueBypassingBindings(duration - currentTime.valueBypassingBindings()); // Check if we have reached the end of loopcount. bool finished = false; @@ -85,12 +85,14 @@ void QTimeLinePrivate::setCurrentTime(int msecs) currentLoopCount = loopCount - 1; } - int currentFrame = q->frameForTime(currentTime); + const int currentFrame = q->frameForTime(currentTime.valueBypassingBindings()); #ifdef QTIMELINE_DEBUG - qDebug() << "QTimeLinePrivate::setCurrentTime: frameForTime" << currentTime << currentFrame; + qDebug() << "QTimeLinePrivate::setCurrentTime: frameForTime" + << currentTime.valueBypassingBindings() << currentFrame; #endif - if (!qFuzzyCompare(lastValue, q->currentValue())) - emit q->valueChanged(q->currentValue(), QTimeLine::QPrivateSignal()); + const qreal currentValue = q->valueForTime(currentTime.valueBypassingBindings()); + if (!qFuzzyCompare(lastValue, currentValue)) + emit q->valueChanged(currentValue, QTimeLine::QPrivateSignal()); if (lastFrame != currentFrame) { const int transitionframe = (direction == QTimeLine::Forward ? endFrame : startFrame); if (looping && !finished && transitionframe != currentFrame) { @@ -123,7 +125,7 @@ void QTimeLinePrivate::setCurrentTime(int msecs) q->stop(); emit q->finished(QTimeLine::QPrivateSignal()); } - if (currentTime.value() != previousCurrentTime) + if (currentTime.valueBypassingBindings() != previousCurrentTime) currentTime.notify(); } QBindable<int> QTimeLine::bindableCurrentTime() @@ -334,11 +336,12 @@ QTimeLine::Direction QTimeLine::direction() const void QTimeLine::setDirection(Direction direction) { Q_D(QTimeLine); - auto previousDirection = d->direction.value(); - d->direction.setValue(direction); + d->direction.removeBindingUnlessInWrapper(); + const auto previousDirection = d->direction.valueBypassingBindings(); + d->direction.setValueBypassingBindings(direction); d->startTime = d->currentTime; d->timer.start(); - if (previousDirection != d->direction.value()) + if (previousDirection != d->direction.valueBypassingBindings()) d->direction.notify(); } @@ -372,12 +375,11 @@ void QTimeLine::setDuration(int duration) qWarning("QTimeLine::setDuration: cannot set duration <= 0"); return; } - if (duration == d->duration) { - d->duration.removeBindingUnlessInWrapper(); - return; + d->duration.removeBindingUnlessInWrapper(); + if (duration != d->duration.valueBypassingBindings()) { + d->duration.setValueBypassingBindings(duration); + d->duration.notify(); } - d->duration.setValue(duration); - d->duration.notify(); } QBindable<int> QTimeLine::bindableDuration() diff --git a/src/corelib/tools/qtools_p.h b/src/corelib/tools/qtools_p.h index 93432a9524..105aa40c02 100644 --- a/src/corelib/tools/qtools_p.h +++ b/src/corelib/tools/qtools_p.h @@ -115,9 +115,6 @@ constexpr inline int qt_lencmp(qsizetype lhs, qsizetype rhs) noexcept } // namespace QtMiscUtils -// We typically need an extra bit for qNextPowerOfTwo when determining the next allocation size. -constexpr qsizetype MaxAllocSize = (std::numeric_limits<qsizetype>::max)(); - struct CalculateGrowingBlockSizeResult { qsizetype size; diff --git a/src/corelib/tools/qtyperevision.cpp b/src/corelib/tools/qtyperevision.cpp new file mode 100644 index 0000000000..6426236288 --- /dev/null +++ b/src/corelib/tools/qtyperevision.cpp @@ -0,0 +1,217 @@ +// Copyright (C) 2021 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 + +#include <QtCore/qtyperevision.h> +#include <QtCore/qhashfunctions.h> + +#ifndef QT_NO_DATASTREAM +# include <QtCore/qdatastream.h> +#endif + +#ifndef QT_NO_DEBUG_STREAM +# include <QtCore/qdebug.h> +#endif + +#include <algorithm> +#include <limits> + +QT_BEGIN_NAMESPACE + +QT_IMPL_METATYPE_EXTERN(QTypeRevision) + +/*! + \class QTypeRevision + \inmodule QtCore + \since 6.0 + \brief The QTypeRevision class contains a lightweight representation of + a version number with two 8-bit segments, major and minor, either + of which can be unknown. + \compares strong + + Use this class to describe revisions of a type. Compatible revisions can be + expressed as increments of the minor version. Breaking changes can be + expressed as increments of the major version. The return values of + \l QMetaMethod::revision() and \l QMetaProperty::revision() can be passed to + \l QTypeRevision::fromEncodedVersion(). The resulting major and minor versions + specify in which Qt versions the properties and methods were added. + + \sa QMetaMethod::revision(), QMetaProperty::revision() +*/ + +/*! + \fn template<typename Integer, QTypeRevision::if_valid_segment_type<Integer> = true> static bool QTypeRevision::isValidSegment(Integer segment) + + Returns true if the given number can be used as either major or minor + version in a QTypeRevision. The valid range for \a segment is \c {>= 0} and \c {< 255}. +*/ + +/*! + \fn QTypeRevision::QTypeRevision() + + Produces an invalid revision. + + \sa isValid() +*/ + +/*! + \fn template<typename Major, typename Minor, QTypeRevision::if_valid_segment_type<Major> = true, QTypeRevision::if_valid_segment_type<Minor> = true> static QTypeRevision QTypeRevision::fromVersion(Major majorVersion, Minor minorVersion) + + Produces a QTypeRevision from the given \a majorVersion and \a minorVersion, + both of which need to be a valid segments. + + \sa isValidSegment() +*/ + +/*! + \fn template<typename Major, QTypeRevision::if_valid_segment_type<Major> = true> static QTypeRevision QTypeRevision::fromMajorVersion(Major majorVersion) + + Produces a QTypeRevision from the given \a majorVersion with an invalid minor + version. \a majorVersion needs to be a valid segment. + + \sa isValidSegment() +*/ + +/*! + \fn template<typename Minor, QTypeRevision::if_valid_segment_type<Minor> = true> static QTypeRevision QTypeRevision::fromMinorVersion(Minor minorVersion) + + Produces a QTypeRevision from the given \a minorVersion with an invalid major + version. \a minorVersion needs to be a valid segment. + + \sa isValidSegment() +*/ + +/*! + \fn template<typename Integer, QTypeRevision::if_valid_value_type<Integer> = true> static QTypeRevision QTypeRevision::fromEncodedVersion(Integer value) + + Produces a QTypeRevision from the given \a value. \a value encodes both the + minor and major versions in the least significant and second least + significant byte, respectively. + + \a value must not have any bits outside the least significant two bytes set. + \c Integer needs to be at least 16 bits wide, and must not have a sign bit + in the least significant 16 bits. + + \sa toEncodedVersion() +*/ + +/*! + \fn static QTypeRevision QTypeRevision::zero() + + Produces a QTypeRevision with major and minor version \c{0}. +*/ + +/*! + \fn bool QTypeRevision::hasMajorVersion() const + + Returns true if the major version is known, otherwise false. + + \sa majorVersion(), hasMinorVersion() +*/ + +/*! + \fn quint8 QTypeRevision::majorVersion() const + + Returns the major version encoded in the revision. + + \sa hasMajorVersion(), minorVersion() +*/ + +/*! + \fn bool QTypeRevision::hasMinorVersion() const + + Returns true if the minor version is known, otherwise false. + + \sa minorVersion(), hasMajorVersion() +*/ + +/*! + \fn quint8 QTypeRevision::minorVersion() const + + Returns the minor version encoded in the revision. + + \sa hasMinorVersion(), majorVersion() +*/ + +/*! + \fn bool QTypeRevision::isValid() const + + Returns true if the major version or the minor version is known, + otherwise false. + + \sa hasMajorVersion(), hasMinorVersion() +*/ + +/*! + \fn template<typename Integer, QTypeRevision::if_valid_value_type<Integer> = true> Integer QTypeRevision::toEncodedVersion() const + + Transforms the revision into an integer value, encoding the minor + version into the least significant byte, and the major version into + the second least significant byte. + + \c Integer needs to be at least 16 bits wide, and must not have a sign bit + in the least significant 16 bits. + + \sa fromEncodedVersion() +*/ + +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream& operator<<(QDataStream &out, const QTypeRevision &revision) + \relates QTypeRevision + \since 6.0 + + Writes the revision \a revision to stream \a out. + */ +QDataStream &operator<<(QDataStream &out, const QTypeRevision &revision) +{ + return out << revision.toEncodedVersion<quint16>(); +} + +/*! + \fn QDataStream& operator>>(QDataStream &in, QTypeRevision &revision) + \relates QTypeRevision + \since 6.0 + + Reads a revision from stream \a in and stores it in \a revision. + */ +QDataStream &operator>>(QDataStream &in, QTypeRevision &revision) +{ + quint16 value; + in >> value; + revision = QTypeRevision::fromEncodedVersion(value); + return in; +} +#endif + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QTypeRevision &revision) +{ + QDebugStateSaver saver(debug); + if (revision.hasMajorVersion()) { + if (revision.hasMinorVersion()) + debug.nospace() << revision.majorVersion() << '.' << revision.minorVersion(); + else + debug.nospace().noquote() << revision.majorVersion() << ".x"; + } else { + if (revision.hasMinorVersion()) + debug << revision.minorVersion(); + else + debug.noquote() << "invalid"; + } + return debug; +} +#endif + +/*! + \relates QHash + \since 6.0 + + Returns the hash value for the \a key, using \a seed to seed the + calculation. +*/ +size_t qHash(const QTypeRevision &key, size_t seed) +{ + return qHash(key.toEncodedVersion<quint16>(), seed); +} + +QT_END_NAMESPACE diff --git a/src/corelib/tools/qtyperevision.h b/src/corelib/tools/qtyperevision.h new file mode 100644 index 0000000000..8f255a77e8 --- /dev/null +++ b/src/corelib/tools/qtyperevision.h @@ -0,0 +1,167 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// Copyright (C) 2022 Intel Corporation. +// Copyright (C) 2015 Keith Gardner <kreios4004@gmail.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QTYPEREVISION_H +#define QTYPEREVISION_H + +#include <QtCore/qassert.h> +#include <QtCore/qcompare.h> +#include <QtCore/qcontainertools_impl.h> +#include <QtCore/qmetatype.h> +#include <QtCore/qtypeinfo.h> + +#include <limits> + +QT_BEGIN_NAMESPACE + +class QDataStream; +class QDebug; + +class QTypeRevision; +Q_CORE_EXPORT size_t qHash(const QTypeRevision &key, size_t seed = 0); + +#ifndef QT_NO_DATASTREAM +Q_CORE_EXPORT QDataStream& operator<<(QDataStream &out, const QTypeRevision &revision); +Q_CORE_EXPORT QDataStream& operator>>(QDataStream &in, QTypeRevision &revision); +#endif + +class QTypeRevision +{ +public: + template<typename Integer> + using if_valid_segment_type = typename std::enable_if< + std::is_integral<Integer>::value, bool>::type; + + template<typename Integer> + using if_valid_value_type = typename std::enable_if< + std::is_integral<Integer>::value + && (sizeof(Integer) > sizeof(quint16) + || (sizeof(Integer) == sizeof(quint16) + && !std::is_signed<Integer>::value)), bool>::type; + + template<typename Integer, if_valid_segment_type<Integer> = true> + static constexpr bool isValidSegment(Integer segment) + { + // using extra parentheses around max to avoid expanding it if it is a macro + return segment >= Integer(0) + && ((std::numeric_limits<Integer>::max)() < Integer(SegmentUnknown) + || segment < Integer(SegmentUnknown)); + } + + template<typename Major, typename Minor, + if_valid_segment_type<Major> = true, + if_valid_segment_type<Minor> = true> + static constexpr QTypeRevision fromVersion(Major majorVersion, Minor minorVersion) + { + return Q_ASSERT(isValidSegment(majorVersion)), + Q_ASSERT(isValidSegment(minorVersion)), + QTypeRevision(quint8(majorVersion), quint8(minorVersion)); + } + + template<typename Major, if_valid_segment_type<Major> = true> + static constexpr QTypeRevision fromMajorVersion(Major majorVersion) + { + return Q_ASSERT(isValidSegment(majorVersion)), + QTypeRevision(quint8(majorVersion), SegmentUnknown); + } + + template<typename Minor, if_valid_segment_type<Minor> = true> + static constexpr QTypeRevision fromMinorVersion(Minor minorVersion) + { + return Q_ASSERT(isValidSegment(minorVersion)), + QTypeRevision(SegmentUnknown, quint8(minorVersion)); + } + + template<typename Integer, if_valid_value_type<Integer> = true> + static constexpr QTypeRevision fromEncodedVersion(Integer value) + { + return Q_ASSERT((value & ~Integer(0xffff)) == Integer(0)), + QTypeRevision((value & Integer(0xff00)) >> 8, value & Integer(0xff)); + } + + static constexpr QTypeRevision zero() { return QTypeRevision(0, 0); } + + constexpr QTypeRevision() = default; + + constexpr bool hasMajorVersion() const { return m_majorVersion != SegmentUnknown; } + constexpr quint8 majorVersion() const { return m_majorVersion; } + + constexpr bool hasMinorVersion() const { return m_minorVersion != SegmentUnknown; } + constexpr quint8 minorVersion() const { return m_minorVersion; } + + constexpr bool isValid() const { return hasMajorVersion() || hasMinorVersion(); } + + template<typename Integer, if_valid_value_type<Integer> = true> + constexpr Integer toEncodedVersion() const + { + return Integer(m_majorVersion << 8) | Integer(m_minorVersion); + } + +private: + friend constexpr bool + comparesEqual(const QTypeRevision &lhs, const QTypeRevision &rhs) noexcept + { return lhs.toEncodedVersion<quint16>() == rhs.toEncodedVersion<quint16>(); } + friend constexpr Qt::strong_ordering + compareThreeWay(const QTypeRevision &lhs, const QTypeRevision &rhs) noexcept + { + // For both major and minor the following rule applies: + // non-0 ver > unspecified ver > 0 ver + auto cmpUnspecified = [](quint8 leftVer, quint8 rightVer) { + Q_ASSERT(leftVer != rightVer + && (leftVer == QTypeRevision::SegmentUnknown + || rightVer == QTypeRevision::SegmentUnknown)); + if (leftVer != QTypeRevision::SegmentUnknown) + return leftVer > 0 ? Qt::strong_ordering::greater : Qt::strong_ordering::less; + return rightVer > 0 ? Qt::strong_ordering::less : Qt::strong_ordering::greater; + }; + + if (lhs.hasMajorVersion() != rhs.hasMajorVersion()) { + return cmpUnspecified(lhs.majorVersion(), rhs.majorVersion()); + } else { + const auto majorRes = Qt::compareThreeWay(lhs.majorVersion(), rhs.majorVersion()); + if (is_eq(majorRes)) { + if (lhs.hasMinorVersion() != rhs.hasMinorVersion()) + return cmpUnspecified(lhs.minorVersion(), rhs.minorVersion()); + return Qt::compareThreeWay(lhs.minorVersion(), rhs.minorVersion()); + } + return majorRes; + } + } + Q_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE(QTypeRevision) + + enum { SegmentUnknown = 0xff }; + +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + constexpr QTypeRevision(quint8 major, quint8 minor) + : m_minorVersion(minor), m_majorVersion(major) {} + + quint8 m_minorVersion = SegmentUnknown; + quint8 m_majorVersion = SegmentUnknown; +#else + constexpr QTypeRevision(quint8 major, quint8 minor) + : m_majorVersion(major), m_minorVersion(minor) {} + + quint8 m_majorVersion = SegmentUnknown; + quint8 m_minorVersion = SegmentUnknown; +#endif +}; + +static_assert(sizeof(QTypeRevision) == 2); +Q_DECLARE_TYPEINFO(QTypeRevision, Q_RELOCATABLE_TYPE); + +#ifndef QT_NO_DEBUG_STREAM +Q_CORE_EXPORT QDebug operator<<(QDebug, const QTypeRevision &revision); +#endif + +QT_END_NAMESPACE + +QT_DECL_METATYPE_EXTERN(QTypeRevision, Q_CORE_EXPORT) + +#endif // QTYPEREVISION_H + +#if !defined(QT_LEAN_HEADERS) || QT_LEAN_HEADERS < 2 +// make QVersionNumber available from <QTypeRevision> +#include <QtCore/qversionnumber.h> +#endif diff --git a/src/corelib/tools/quniquehandle_p.h b/src/corelib/tools/quniquehandle_p.h new file mode 100644 index 0000000000..7af1536c2e --- /dev/null +++ b/src/corelib/tools/quniquehandle_p.h @@ -0,0 +1,225 @@ +// Copyright (C) 2023 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 QUNIQUEHANDLE_P_H +#define QUNIQUEHANDLE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qtconfigmacros.h> +#include <QtCore/qassert.h> + +#include <memory> + +QT_BEGIN_NAMESPACE + +/*! \internal QUniqueHandle is a general purpose RAII wrapper intended + for interfacing with resource-allocating C-style APIs, for example + operating system APIs, database engine APIs, or any other scenario + where resources are allocated and released, and where pointer + semantics does not seem a perfect fit. + + QUniqueHandle does not support copying, because it is intended to + maintain ownership of resources that can not be copied. This makes + it safer to use than naked handle types, since ownership is + maintained by design. + + The underlying handle object is described using a client supplied + HandleTraits object that is implemented per resource type. The + traits struct must describe two properties of a handle: + + 1) What value is considered invalid + 2) How to close a resource. + + Example 1: + + struct InvalidHandleTraits { + using Type = HANDLE; + + static Type invalidValue() { + return INVALID_HANDLE_VALUE; + } + + static bool close(Type handle) { + return CloseHandle(handle) != 0; + } + } + + using FileHandle = QUniqueHandle<InvalidHandleTraits>; + + Usage: + + // Takes ownership of returned handle. + FileHandle handle{ CreateFile(...) }; + + if (!handle.isValid()) { + qDebug() << GetLastError() + return; + } + + ... + + Example 2: + + struct SqLiteTraits { + using Type = sqlite3*; + + static Type invalidValue() { + return nullptr; + } + + static bool close(Type handle) { + sqlite3_close(handle); + return true; + } + } + + using DbHandle = QUniqueHandle<SqLiteTraits>; + + Usage: + + DbHandle h; + + // Take ownership of returned handle. + int result = sqlite3_open(":memory:", &h); + + ... + + NOTE: The QUniqueHandle assumes that closing a resource is + guaranteed to succeed, and provides no support for handling failure + to close a resource. It is therefore only recommended for use cases + where failure to close a resource is either not an error, or an + unrecoverable error. +*/ + +// clang-format off + +template <typename HandleTraits> +class QUniqueHandle +{ +public: + using Type = typename HandleTraits::Type; + + QUniqueHandle() = default; + + explicit QUniqueHandle(const Type &handle) noexcept + : m_handle{ handle } + {} + + QUniqueHandle(QUniqueHandle &&other) noexcept + : m_handle{ other.release() } + {} + + ~QUniqueHandle() noexcept + { + close(); + } + + QUniqueHandle& operator=(QUniqueHandle &&rhs) noexcept + { + if (this != std::addressof(rhs)) + reset(rhs.release()); + + return *this; + } + + QUniqueHandle(const QUniqueHandle &) = delete; + QUniqueHandle &operator=(const QUniqueHandle &) = delete; + + + [[nodiscard]] bool isValid() const noexcept + { + return m_handle != HandleTraits::invalidValue(); + } + + [[nodiscard]] explicit operator bool() const noexcept + { + return isValid(); + } + + [[nodiscard]] Type get() const noexcept + { + return m_handle; + } + + void reset(const Type& handle) noexcept + { + if (handle == m_handle) + return; + + close(); + m_handle = handle; + } + + [[nodiscard]] Type release() noexcept + { + Type handle = m_handle; + m_handle = HandleTraits::invalidValue(); + return handle; + } + + [[nodiscard]] Type *operator&() noexcept // NOLINT(google-runtime-operator) + { + Q_ASSERT(!isValid()); + return &m_handle; + } + + void close() noexcept + { + if (!isValid()) + return; + + const bool success = HandleTraits::close(m_handle); + Q_ASSERT(success); + + m_handle = HandleTraits::invalidValue(); + } + + [[nodiscard]] friend bool operator==(const QUniqueHandle &lhs, const QUniqueHandle &rhs) noexcept + { + return lhs.get() == rhs.get(); + } + + [[nodiscard]] friend bool operator!=(const QUniqueHandle &lhs, const QUniqueHandle &rhs) noexcept + { + return lhs.get() != rhs.get(); + } + + [[nodiscard]] friend bool operator<(const QUniqueHandle &lhs, const QUniqueHandle &rhs) noexcept + { + return lhs.get() < rhs.get(); + } + + [[nodiscard]] friend bool operator<=(const QUniqueHandle &lhs, const QUniqueHandle &rhs) noexcept + { + return lhs.get() <= rhs.get(); + } + + [[nodiscard]] friend bool operator>(const QUniqueHandle &lhs, const QUniqueHandle &rhs) noexcept + { + return lhs.get() > rhs.get(); + } + + [[nodiscard]] friend bool operator>=(const QUniqueHandle &lhs, const QUniqueHandle &rhs) noexcept + { + return lhs.get() >= rhs.get(); + } + +private: + Type m_handle{ HandleTraits::invalidValue() }; +}; + +// clang-format on + +QT_END_NAMESPACE + +#endif diff --git a/src/corelib/tools/qvarlengtharray.h b/src/corelib/tools/qvarlengtharray.h index 1759fba415..78d5a27627 100644 --- a/src/corelib/tools/qvarlengtharray.h +++ b/src/corelib/tools/qvarlengtharray.h @@ -14,6 +14,7 @@ #include <QtCore/qalgorithms.h> #include <QtCore/qcontainertools_impl.h> #include <QtCore/qhashfunctions.h> +#include <QtCore/qttypetraits.h> #include <algorithm> #include <initializer_list> @@ -183,6 +184,12 @@ public: iterator erase(const_iterator begin, const_iterator end); iterator erase(const_iterator pos) { return erase(pos, pos + 1); } + static constexpr qsizetype max_size() noexcept + { + // -1 to deal with the pointer one-past-the-end + return (QtPrivate::MaxAllocSize / sizeof(T)) - 1; + } + size_t hash(size_t seed) const noexcept(QtPrivate::QNothrowHashable_v<T>) { return qHashRange(begin(), end(), seed); @@ -278,6 +285,8 @@ class QVarLengthArray template <typename InputIterator> using if_input_iterator = QtPrivate::IfIsInputIterator<InputIterator>; public: + static constexpr qsizetype PreallocatedSize = Prealloc; + using size_type = typename Base::size_type; using value_type = typename Base::value_type; using pointer = typename Base::pointer; @@ -395,6 +404,7 @@ public: } #ifdef Q_QDOC inline qsizetype size() const { return this->s; } + static constexpr qsizetype max_size() noexcept { return QVLABase<T>::max_size(); } #endif using Base::size; inline qsizetype count() const { return size(); } @@ -823,7 +833,7 @@ Q_OUTOFLINE_TEMPLATE void QVLABase<T>::reallocate_impl(qsizetype prealloc, void qsizetype osize = size(); const qsizetype copySize = qMin(asize, osize); - Q_ASSUME(copySize >= 0); + Q_ASSERT(copySize >= 0); if (aalloc != capacity()) { QVLABaseBase::malloced_ptr guard; @@ -947,8 +957,8 @@ Q_OUTOFLINE_TEMPLATE auto QVLABase<T>::insert_impl(qsizetype prealloc, void *arr template <class T> Q_OUTOFLINE_TEMPLATE auto QVLABase<T>::erase(const_iterator abegin, const_iterator aend) -> iterator { - Q_ASSERT_X(isValidIterator(abegin), "QVarLengthArray::insert", "The specified const_iterator argument 'abegin' is invalid"); - Q_ASSERT_X(isValidIterator(aend), "QVarLengthArray::insert", "The specified const_iterator argument 'aend' is invalid"); + Q_ASSERT_X(isValidIterator(abegin), "QVarLengthArray::erase", "The specified const_iterator argument 'abegin' is invalid"); + Q_ASSERT_X(isValidIterator(aend), "QVarLengthArray::erase", "The specified const_iterator argument 'aend' is invalid"); qsizetype f = qsizetype(abegin - cbegin()); qsizetype l = qsizetype(aend - cbegin()); @@ -959,10 +969,11 @@ Q_OUTOFLINE_TEMPLATE auto QVLABase<T>::erase(const_iterator abegin, const_iterat Q_ASSERT(n > 0); // aend must be reachable from abegin - if constexpr (QTypeInfo<T>::isComplex) { + if constexpr (!QTypeInfo<T>::isRelocatable) { std::move(begin() + l, end(), QT_MAKE_CHECKED_ARRAY_ITERATOR(begin() + f, size() - f)); std::destroy(end() - n, end()); } else { + std::destroy(abegin, aend); memmove(static_cast<void *>(data() + f), static_cast<const void *>(data() + l), (size() - l) * sizeof(T)); } this->s -= n; diff --git a/src/corelib/tools/qvarlengtharray.qdoc b/src/corelib/tools/qvarlengtharray.qdoc index 70dc41685c..4467e0c65a 100644 --- a/src/corelib/tools/qvarlengtharray.qdoc +++ b/src/corelib/tools/qvarlengtharray.qdoc @@ -98,12 +98,9 @@ \since 5.5 Constructs an array from the std::initializer_list given by \a args. - - This constructor is only enabled if the compiler supports C++11 initializer - lists. */ -/*! \fn template<class T, qsizetype Prealloc> template<typename InputIterator, if_input_iterator<InputIterator>> QVarLengthArray<T, Prealloc>::QVarLengthArray(InputIterator first, InputIterator last) +/*! \fn template<class T, qsizetype Prealloc> template<typename InputIterator, QVarLengthArray<T, Prealloc>::if_input_iterator<InputIterator>> QVarLengthArray<T, Prealloc>::QVarLengthArray(InputIterator first, InputIterator last) \since 5.14 Constructs an array with the contents in the iterator range [\a first, \a last). @@ -143,6 +140,15 @@ \sa isEmpty(), resize() */ +/*! \fn template<class T, qsizetype Prealloc> qsizetype QVarLengthArray<T, Prealloc>::max_size() + \since 6.8 + + This function is provided for STL compatibility. + It returns the maximum number of elements that the array can + theoretically hold. In practice, the number can be much smaller, + limited by the amount of memory available to the system. +*/ + /*! \fn template<class T, qsizetype Prealloc> T& QVarLengthArray<T, Prealloc>::first() Returns a reference to the first item in the array. The array must @@ -424,9 +430,6 @@ \since 5.5 Assigns the values of \a list to this array, and returns a reference to this array. - - This constructor is only enabled if the compiler supports C++11 initializer - lists. */ /*! \fn template<class T, qsizetype Prealloc> QVarLengthArray<T, Prealloc>::QVarLengthArray(const QVarLengthArray<T, Prealloc> &other) @@ -468,6 +471,15 @@ \a defaultValue. */ +/* + \var QVarLengthArray::PreallocatedSize + \since 6.8 + + The same value as the \c{Prealloc} template argument. Provided for easier + access compared to manually extracting the value from the template + argument. +*/ + /*! \typedef QVarLengthArray::size_type \since 4.7 @@ -997,7 +1009,7 @@ \sa erase() */ -/*! \fn template <class T, qsizetype Prealloc> QVarLengthArray<T, Prealloc>::assign(qsizetype n, const T &t) +/*! \fn template <class T, qsizetype Prealloc> QVarLengthArray<T, Prealloc>& QVarLengthArray<T, Prealloc>::assign(qsizetype n, const T &t) \since 6.6 Replaces the contents of this container with \a n copies of \a t. @@ -1006,7 +1018,7 @@ allocate memory if \a n exceeds the capacity of the container. */ -/*! \fn template <class T, qsizetype Prealloc> template <typename InputIterator, if_input_iterator<InputIterator>> QVarLengthArray<T, Prealloc>::assign(InputIterator first, InputIterator last) +/*! \fn template <class T, qsizetype Prealloc> template <typename InputIterator, QVarLengthArray<T, Prealloc>::if_input_iterator<InputIterator>> QVarLengthArray<T, Prealloc>& QVarLengthArray<T, Prealloc>::assign(InputIterator first, InputIterator last) \since 6.6 Replaces the contents of this container with a copy of the elements in the @@ -1023,7 +1035,7 @@ The behavior is undefined if either argument is an iterator into *this. */ -/*! \fn template <class T, qsizetype Prealloc> QVarLengthArray<T, Prealloc>::assign(std::initializer_list<T> list) +/*! \fn template <class T, qsizetype Prealloc> QVarLengthArray<T, Prealloc>& QVarLengthArray<T, Prealloc>::assign(std::initializer_list<T> list) \since 6.6 Replaces the contents of this container with a copy of the elements of \a list. diff --git a/src/corelib/tools/qversionnumber.cpp b/src/corelib/tools/qversionnumber.cpp index dcb2a3ad64..af95875b44 100644 --- a/src/corelib/tools/qversionnumber.cpp +++ b/src/corelib/tools/qversionnumber.cpp @@ -22,7 +22,6 @@ QT_BEGIN_NAMESPACE QT_IMPL_METATYPE_EXTERN(QVersionNumber) -QT_IMPL_METATYPE_EXTERN(QTypeRevision) /*! \class QVersionNumber @@ -31,6 +30,7 @@ QT_IMPL_METATYPE_EXTERN(QTypeRevision) \brief The QVersionNumber class contains a version number with an arbitrary number of segments. + \compares strong \snippet qversionnumber/main.cpp 0 */ @@ -72,25 +72,23 @@ QT_IMPL_METATYPE_EXTERN(QTypeRevision) \fn QVersionNumber::QVersionNumber(QList<int> &&seg) Move-constructs a version number from the list of numbers contained in \a seg. - - This constructor is only enabled if the compiler supports C++11 move semantics. */ /*! \fn QVersionNumber::QVersionNumber(std::initializer_list<int> args) - Construct a version number from the std::initializer_list specified by + Constructs a version number from the std::initializer_list specified by \a args. - - This constructor is only enabled if the compiler supports C++11 initializer - lists. */ /*! - \fn template <qsizetype N> QVersionNumber::QVersionNumber(const QVarLengthArray<int, N> &seg) - \since 6.4 + \fn QVersionNumber::QVersionNumber(QSpan<const int> args) + \since 6.8 - Constructs a version number from the list of numbers contained in \a seg. + Constructs a version number from the span specified by \a args. + + \note In Qt versions prior to 6.8, QVersionNumber could only be constructed + from QList, QVarLenthArray or std::initializer_list. */ /*! @@ -178,6 +176,55 @@ QList<int> QVersionNumber::segments() const */ /*! + \typedef QVersionNumber::const_iterator + \typedef QVersionNumber::const_reverse_iterator + \since 6.8 + + Typedefs for an opaque class that implements a (reverse) random-access + iterator over QVersionNumber segments. + + \note QVersionNumber does not support modifying segments in-place, so + there is no mutable iterator. +*/ + +/*! + \typedef QVersionNumber::value_type + \typedef QVersionNumber::difference_type + \typedef QVersionNumber::size_type + \typedef QVersionNumber::reference + \typedef QVersionNumber::const_reference + \typedef QVersionNumber::pointer + \typedef QVersionNumber::const_pointer + \since 6.8 + + Provided for STL-compatibility. + + \note QVersionNumber does not support modifying segments in-place, so + reference and const_reference, as well as pointer and const_pointer are + pairwise the same types. +*/ + +/*! + \fn QVersionNumber::begin() const + \fn QVersionNumber::end() const; + \fn QVersionNumber::rbegin() const + \fn QVersionNumber::rend() const; + \fn QVersionNumber::cbegin() const + \fn QVersionNumber::cend() const; + \fn QVersionNumber::crbegin() const + \fn QVersionNumber::crend() const; + \fn QVersionNumber::constBegin() const; + \fn QVersionNumber::constEnd() const; + \since 6.8 + + Returns a const_iterator or const_reverse_iterator, respectively, pointing + to the first or one past the last segment of this version number. + + \note QVersionNumber does not support modifying segments in-place, so + there is no mutable iterator. +*/ + +/*! \fn QVersionNumber QVersionNumber::normalized() const Returns an equivalent version number but with all trailing zeros removed. @@ -538,198 +585,4 @@ size_t qHash(const QVersionNumber &key, size_t seed) return seed; } -/*! - \class QTypeRevision - \inmodule QtCore - \since 6.0 - \brief The QTypeRevision class contains a lightweight representation of - a version number with two 8-bit segments, major and minor, either - of which can be unknown. - - Use this class to describe revisions of a type. Compatible revisions can be - expressed as increments of the minor version. Breaking changes can be - expressed as increments of the major version. The return values of - \l QMetaMethod::revision() and \l QMetaProperty::revision() can be passed to - \l QTypeRevision::fromEncodedVersion(). The resulting major and minor versions - specify in which Qt versions the properties and methods were added. - - \sa QMetaMethod::revision(), QMetaProperty::revision() -*/ - -/*! - \fn template<typename Integer> static bool QTypeRevision::isValidSegment(Integer segment) - - Returns true if the given number can be used as either major or minor - version in a QTypeRevision. The valid range for \a segment is \c {>= 0} and \c {< 255}. -*/ - -/*! - \fn QTypeRevision::QTypeRevision() - - Produces an invalid revision. - - \sa isValid() -*/ - -/*! - \fn template <typename Major, typename Minor> static QTypeRevision QTypeRevision::fromVersion(Major majorVersion, Minor minorVersion) - - Produces a QTypeRevision from the given \a majorVersion and \a minorVersion, - both of which need to be a valid segments. - - \sa isValidSegment() -*/ - -/*! - \fn template <typename Major> static QTypeRevision QTypeRevision::fromMajorVersion(Major majorVersion) - - Produces a QTypeRevision from the given \a majorVersion with an invalid minor - version. \a majorVersion needs to be a valid segment. - - \sa isValidSegment() -*/ - -/*! - \fn template <typename Minor> static QTypeRevision QTypeRevision::fromMinorVersion(Minor minorVersion) - - Produces a QTypeRevision from the given \a minorVersion with an invalid major - version. \a minorVersion needs to be a valid segment. - - \sa isValidSegment() -*/ - -/*! - \fn template <typename Integer> static QTypeRevision QTypeRevision::fromEncodedVersion(Integer value) - - Produces a QTypeRevision from the given \a value. \a value encodes both the - minor and major versions in the least significant and second least - significant byte, respectively. - - \a value must not have any bits outside the least significant two bytes set. - \c Integer needs to be at least 16 bits wide, and must not have a sign bit - in the least significant 16 bits. - - \sa toEncodedVersion() -*/ - -/*! - \fn static QTypeRevision QTypeRevision::zero() - - Produces a QTypeRevision with major and minor version \c{0}. -*/ - -/*! - \fn bool QTypeRevision::hasMajorVersion() const - - Returns true if the major version is known, otherwise false. - - \sa majorVersion(), hasMinorVersion() -*/ - -/*! - \fn quint8 QTypeRevision::majorVersion() const - - Returns the major version encoded in the revision. - - \sa hasMajorVersion(), minorVersion() -*/ - -/*! - \fn bool QTypeRevision::hasMinorVersion() const - - Returns true if the minor version is known, otherwise false. - - \sa minorVersion(), hasMajorVersion() -*/ - -/*! - \fn quint8 QTypeRevision::minorVersion() const - - Returns the minor version encoded in the revision. - - \sa hasMinorVersion(), majorVersion() -*/ - -/*! - \fn bool QTypeRevision::isValid() const - - Returns true if the major version or the minor version is known, - otherwise false. - - \sa hasMajorVersion(), hasMinorVersion() -*/ - -/*! - \fn template<typename Integer> Integer QTypeRevision::toEncodedVersion() const - - Transforms the revision into an integer value, encoding the minor - version into the least significant byte, and the major version into - the second least significant byte. - - \c Integer needs to be at least 16 bits wide, and must not have a sign bit - in the least significant 16 bits. - - \sa fromEncodedVersion() -*/ - -#ifndef QT_NO_DATASTREAM -/*! - \fn QDataStream& operator<<(QDataStream &out, const QTypeRevision &revision) - \relates QTypeRevision - \since 6.0 - - Writes the revision \a revision to stream \a out. - */ -QDataStream &operator<<(QDataStream &out, const QTypeRevision &revision) -{ - return out << revision.toEncodedVersion<quint16>(); -} - -/*! - \fn QDataStream& operator>>(QDataStream &in, QTypeRevision &revision) - \relates QTypeRevision - \since 6.0 - - Reads a revision from stream \a in and stores it in \a revision. - */ -QDataStream &operator>>(QDataStream &in, QTypeRevision &revision) -{ - quint16 value; - in >> value; - revision = QTypeRevision::fromEncodedVersion(value); - return in; -} -#endif - -#ifndef QT_NO_DEBUG_STREAM -QDebug operator<<(QDebug debug, const QTypeRevision &revision) -{ - QDebugStateSaver saver(debug); - if (revision.hasMajorVersion()) { - if (revision.hasMinorVersion()) - debug.nospace() << revision.majorVersion() << '.' << revision.minorVersion(); - else - debug.nospace().noquote() << revision.majorVersion() << ".x"; - } else { - if (revision.hasMinorVersion()) - debug << revision.minorVersion(); - else - debug.noquote() << "invalid"; - } - return debug; -} -#endif - -/*! - \relates QHash - \since 6.0 - - Returns the hash value for the \a key, using \a seed to seed the - calculation. -*/ -size_t qHash(const QTypeRevision &key, size_t seed) -{ - return qHash(key.toEncodedVersion<quint16>(), seed); -} - QT_END_NAMESPACE diff --git a/src/corelib/tools/qversionnumber.h b/src/corelib/tools/qversionnumber.h index a7c2b44a7e..e7ae107226 100644 --- a/src/corelib/tools/qversionnumber.h +++ b/src/corelib/tools/qversionnumber.h @@ -6,12 +6,17 @@ #ifndef QVERSIONNUMBER_H #define QVERSIONNUMBER_H +#include <QtCore/qcompare.h> +#include <QtCore/qcontainertools_impl.h> #include <QtCore/qlist.h> #include <QtCore/qmetatype.h> #include <QtCore/qnamespace.h> +#include <QtCore/qspan.h> #include <QtCore/qstring.h> #include <QtCore/qtypeinfo.h> -#include <limits> +#if !defined(QT_LEAN_HEADERS) || QT_LEAN_HEADERS < 2 +#include <QtCore/qtyperevision.h> +#endif // lean headers level 2 QT_BEGIN_NAMESPACE @@ -110,7 +115,7 @@ class QVersionNumber Q_CORE_EXPORT void setListData(QList<int> &&seg); - explicit SegmentStorage(std::initializer_list<int> args) + explicit SegmentStorage(QSpan<const int> args) : SegmentStorage(args.begin(), args.end()) {} explicit SegmentStorage(const int *first, const int *last) @@ -188,23 +193,85 @@ class QVersionNumber Q_CORE_EXPORT void setVector(int len, int maj, int min, int mic); } m_segments; + class It + { + const QVersionNumber *v; + qsizetype i; + + friend class QVersionNumber; + explicit constexpr It(const QVersionNumber *vn, qsizetype idx) noexcept : v(vn), i(idx) {} + + friend constexpr bool comparesEqual(const It &lhs, const It &rhs) + { Q_ASSERT(lhs.v == rhs.v); return lhs.i == rhs.i; } + friend constexpr Qt::strong_ordering compareThreeWay(const It &lhs, const It &rhs) + { Q_ASSERT(lhs.v == rhs.v); return Qt::compareThreeWay(lhs.i, rhs.i); } + Q_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE(It) + + public: + // Rule Of Zero applies + It() = default; + + using iterator_category = std::random_access_iterator_tag; + using value_type = int; +#ifdef QT_COMPILER_HAS_LWG3346 + using element_type = const int; +#endif + using difference_type = qptrdiff; // difference to container requirements + using size_type = qsizetype; // difference to container requirements + using reference = value_type; // difference to container requirements + using pointer = QtPrivate::ArrowProxy<reference>; + + reference operator*() const { return v->segmentAt(i); } + pointer operator->() const { return {**this}; } + + It &operator++() { ++i; return *this; } + It operator++(int) { auto copy = *this; ++*this; return copy; } + + It &operator--() { --i; return *this; } + It operator--(int) { auto copy = *this; --*this; return copy; } + + It &operator+=(difference_type n) { i += n; return *this; } + friend It operator+(It it, difference_type n) { it += n; return it; } + friend It operator+(difference_type n, It it) { return it + n; } + + It &operator-=(difference_type n) { i -= n; return *this; } + friend It operator-(It it, difference_type n) { it -= n; return it; } + + friend difference_type operator-(It lhs, It rhs) + { Q_ASSERT(lhs.v == rhs.v); return lhs.i - rhs.i; } + + reference operator[](difference_type n) const { return *(*this + n); } + }; + public: + using const_iterator = It; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + + using value_type = It::value_type; + using difference_type = It::difference_type; + using size_type = It::size_type; + using reference = It::reference; + using const_reference = reference; + using pointer = It::pointer; + using const_pointer = pointer; + inline QVersionNumber() noexcept : m_segments() {} + Q_WEAK_OVERLOAD inline explicit QVersionNumber(const QList<int> &seg) : m_segments(seg) { } // compiler-generated copy/move ctor/assignment operators and the destructor are ok + Q_WEAK_OVERLOAD explicit QVersionNumber(QList<int> &&seg) : m_segments(std::move(seg)) { } inline QVersionNumber(std::initializer_list<int> args) - : m_segments(args) + : m_segments(QSpan{args}) {} - template <qsizetype N> - explicit QVersionNumber(const QVarLengthArray<int, N> &sec) - : m_segments(sec.begin(), sec.end()) + explicit QVersionNumber(QSpan<const int> args) + : m_segments(args) {} inline explicit QVersionNumber(int maj) @@ -241,6 +308,19 @@ public: [[nodiscard]] inline qsizetype segmentCount() const noexcept { return m_segments.size(); } + [[nodiscard]] const_iterator begin() const noexcept { return const_iterator{this, 0}; } + [[nodiscard]] const_iterator end() const noexcept { return begin() + segmentCount(); } + [[nodiscard]] const_iterator cbegin() const noexcept { return begin(); } + [[nodiscard]] const_iterator cend() const noexcept { return end(); } + + [[nodiscard]] const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{end()}; } + [[nodiscard]] const_reverse_iterator rend() const noexcept { return const_reverse_iterator{begin()}; } + [[nodiscard]] const_reverse_iterator crbegin() const noexcept { return rbegin(); } + [[nodiscard]] const_reverse_iterator crend() const noexcept { return rend(); } + + [[nodiscard]] const_iterator constBegin() const noexcept { return begin(); } + [[nodiscard]] const_iterator constEnd() const noexcept { return end(); } + [[nodiscard]] Q_CORE_EXPORT bool isPrefixOf(const QVersionNumber &other) const noexcept; [[nodiscard]] Q_CORE_EXPORT static int compare(const QVersionNumber &v1, const QVersionNumber &v2) noexcept; @@ -276,191 +356,34 @@ public: [[nodiscard]] Q_CORE_EXPORT static QVersionNumber fromString(QStringView string, int *suffixIndex); #endif - [[nodiscard]] friend bool operator> (const QVersionNumber &lhs, const QVersionNumber &rhs) noexcept - { return compare(lhs, rhs) > 0; } - - [[nodiscard]] friend bool operator>=(const QVersionNumber &lhs, const QVersionNumber &rhs) noexcept - { return compare(lhs, rhs) >= 0; } - - [[nodiscard]] friend bool operator< (const QVersionNumber &lhs, const QVersionNumber &rhs) noexcept - { return compare(lhs, rhs) < 0; } - - [[nodiscard]] friend bool operator<=(const QVersionNumber &lhs, const QVersionNumber &rhs) noexcept - { return compare(lhs, rhs) <= 0; } - - [[nodiscard]] friend bool operator==(const QVersionNumber &lhs, const QVersionNumber &rhs) noexcept - { return compare(lhs, rhs) == 0; } - - [[nodiscard]] friend bool operator!=(const QVersionNumber &lhs, const QVersionNumber &rhs) noexcept - { return compare(lhs, rhs) != 0; } - private: -#ifndef QT_NO_DATASTREAM - friend Q_CORE_EXPORT QDataStream& operator>>(QDataStream &in, QVersionNumber &version); -#endif - friend Q_CORE_EXPORT size_t qHash(const QVersionNumber &key, size_t seed); -}; - -Q_DECLARE_TYPEINFO(QVersionNumber, Q_RELOCATABLE_TYPE); - -#ifndef QT_NO_DEBUG_STREAM -Q_CORE_EXPORT QDebug operator<<(QDebug, const QVersionNumber &version); -#endif - -class QTypeRevision; -Q_CORE_EXPORT size_t qHash(const QTypeRevision &key, size_t seed = 0); - -#ifndef QT_NO_DATASTREAM -Q_CORE_EXPORT QDataStream& operator<<(QDataStream &out, const QTypeRevision &revision); -Q_CORE_EXPORT QDataStream& operator>>(QDataStream &in, QTypeRevision &revision); -#endif - -class QTypeRevision -{ -public: - template<typename Integer> - using if_valid_segment_type = typename std::enable_if< - std::is_integral<Integer>::value, bool>::type; - - template<typename Integer> - using if_valid_value_type = typename std::enable_if< - std::is_integral<Integer>::value - && (sizeof(Integer) > sizeof(quint16) - || (sizeof(Integer) == sizeof(quint16) - && !std::is_signed<Integer>::value)), bool>::type; - - template<typename Integer, if_valid_segment_type<Integer> = true> - static constexpr bool isValidSegment(Integer segment) + [[nodiscard]] friend bool comparesEqual(const QVersionNumber &lhs, + const QVersionNumber &rhs) noexcept { - // using extra parentheses around max to avoid expanding it if it is a macro - return segment >= Integer(0) - && ((std::numeric_limits<Integer>::max)() < Integer(SegmentUnknown) - || segment < Integer(SegmentUnknown)); + return compare(lhs, rhs) == 0; } - - template<typename Major, typename Minor, - if_valid_segment_type<Major> = true, - if_valid_segment_type<Minor> = true> - static constexpr QTypeRevision fromVersion(Major majorVersion, Minor minorVersion) + [[nodiscard]] friend Qt::strong_ordering compareThreeWay(const QVersionNumber &lhs, + const QVersionNumber &rhs) noexcept { - return Q_ASSERT(isValidSegment(majorVersion)), - Q_ASSERT(isValidSegment(minorVersion)), - QTypeRevision(quint8(majorVersion), quint8(minorVersion)); + int c = compare(lhs, rhs); + return Qt::compareThreeWay(c, 0); } + Q_DECLARE_STRONGLY_ORDERED(QVersionNumber) - template<typename Major, if_valid_segment_type<Major> = true> - static constexpr QTypeRevision fromMajorVersion(Major majorVersion) - { - return Q_ASSERT(isValidSegment(majorVersion)), - QTypeRevision(quint8(majorVersion), SegmentUnknown); - } - - template<typename Minor, if_valid_segment_type<Minor> = true> - static constexpr QTypeRevision fromMinorVersion(Minor minorVersion) - { - return Q_ASSERT(isValidSegment(minorVersion)), - QTypeRevision(SegmentUnknown, quint8(minorVersion)); - } - - template<typename Integer, if_valid_value_type<Integer> = true> - static constexpr QTypeRevision fromEncodedVersion(Integer value) - { - return Q_ASSERT((value & ~Integer(0xffff)) == Integer(0)), - QTypeRevision((value & Integer(0xff00)) >> 8, value & Integer(0xff)); - } - - static constexpr QTypeRevision zero() { return QTypeRevision(0, 0); } - - constexpr QTypeRevision() = default; - - constexpr bool hasMajorVersion() const { return m_majorVersion != SegmentUnknown; } - constexpr quint8 majorVersion() const { return m_majorVersion; } - - constexpr bool hasMinorVersion() const { return m_minorVersion != SegmentUnknown; } - constexpr quint8 minorVersion() const { return m_minorVersion; } - - constexpr bool isValid() const { return hasMajorVersion() || hasMinorVersion(); } - - template<typename Integer, if_valid_value_type<Integer> = true> - constexpr Integer toEncodedVersion() const - { - return Integer(m_majorVersion << 8) | Integer(m_minorVersion); - } - - [[nodiscard]] friend constexpr bool operator==(QTypeRevision lhs, QTypeRevision rhs) - { - return lhs.toEncodedVersion<quint16>() == rhs.toEncodedVersion<quint16>(); - } - - [[nodiscard]] friend constexpr bool operator!=(QTypeRevision lhs, QTypeRevision rhs) - { - return lhs.toEncodedVersion<quint16>() != rhs.toEncodedVersion<quint16>(); - } - - [[nodiscard]] friend constexpr bool operator<(QTypeRevision lhs, QTypeRevision rhs) - { - return (!lhs.hasMajorVersion() && rhs.hasMajorVersion()) - // non-0 major > unspecified major > major 0 - ? rhs.majorVersion() != 0 - : ((lhs.hasMajorVersion() && !rhs.hasMajorVersion()) - // major 0 < unspecified major < non-0 major - ? lhs.majorVersion() == 0 - : (lhs.majorVersion() != rhs.majorVersion() - // both majors specified and non-0 - ? lhs.majorVersion() < rhs.majorVersion() - : ((!lhs.hasMinorVersion() && rhs.hasMinorVersion()) - // non-0 minor > unspecified minor > minor 0 - ? rhs.minorVersion() != 0 - : ((lhs.hasMinorVersion() && !rhs.hasMinorVersion()) - // minor 0 < unspecified minor < non-0 minor - ? lhs.minorVersion() == 0 - // both minors specified and non-0 - : lhs.minorVersion() < rhs.minorVersion())))); - } - - [[nodiscard]] friend constexpr bool operator>(QTypeRevision lhs, QTypeRevision rhs) - { - return lhs != rhs && !(lhs < rhs); - } - - [[nodiscard]] friend constexpr bool operator<=(QTypeRevision lhs, QTypeRevision rhs) - { - return lhs == rhs || lhs < rhs; - } - - [[nodiscard]] friend constexpr bool operator>=(QTypeRevision lhs, QTypeRevision rhs) - { - return lhs == rhs || !(lhs < rhs); - } - -private: - enum { SegmentUnknown = 0xff }; - -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - constexpr QTypeRevision(quint8 major, quint8 minor) - : m_minorVersion(minor), m_majorVersion(major) {} - - quint8 m_minorVersion = SegmentUnknown; - quint8 m_majorVersion = SegmentUnknown; -#else - constexpr QTypeRevision(quint8 major, quint8 minor) - : m_majorVersion(major), m_minorVersion(minor) {} - - quint8 m_majorVersion = SegmentUnknown; - quint8 m_minorVersion = SegmentUnknown; +#ifndef QT_NO_DATASTREAM + friend Q_CORE_EXPORT QDataStream& operator>>(QDataStream &in, QVersionNumber &version); #endif + friend Q_CORE_EXPORT size_t qHash(const QVersionNumber &key, size_t seed); }; -static_assert(sizeof(QTypeRevision) == 2); -Q_DECLARE_TYPEINFO(QTypeRevision, Q_RELOCATABLE_TYPE); +Q_DECLARE_TYPEINFO(QVersionNumber, Q_RELOCATABLE_TYPE); #ifndef QT_NO_DEBUG_STREAM -Q_CORE_EXPORT QDebug operator<<(QDebug, const QTypeRevision &revision); +Q_CORE_EXPORT QDebug operator<<(QDebug, const QVersionNumber &version); #endif QT_END_NAMESPACE QT_DECL_METATYPE_EXTERN(QVersionNumber, Q_CORE_EXPORT) -QT_DECL_METATYPE_EXTERN(QTypeRevision, Q_CORE_EXPORT) #endif // QVERSIONNUMBER_H |