diff options
Diffstat (limited to 'src/corelib/tools/qhash.h')
-rw-r--r-- | src/corelib/tools/qhash.h | 3059 |
1 files changed, 2186 insertions, 873 deletions
diff --git a/src/corelib/tools/qhash.h b/src/corelib/tools/qhash.h index 9fd96686f5..e7cd4123fb 100644 --- a/src/corelib/tools/qhash.h +++ b/src/corelib/tools/qhash.h @@ -1,258 +1,880 @@ -/**************************************************************************** -** -** 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> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QHASH_H #define QHASH_H -#include <QtCore/qchar.h> +#include <QtCore/qalgorithms.h> +#include <QtCore/qcontainertools_impl.h> +#include <QtCore/qhashfunctions.h> #include <QtCore/qiterator.h> #include <QtCore/qlist.h> #include <QtCore/qrefcount.h> -#include <QtCore/qhashfunctions.h> -#include <QtCore/qcontainertools_impl.h> -#include <algorithm> #include <initializer_list> +#include <functional> // for std::hash -#if defined(Q_CC_MSVC) -#pragma warning( push ) -#pragma warning( disable : 4311 ) // disable pointer truncation warning -#pragma warning( disable : 4127 ) // conditional expression is constant -#endif +class tst_QHash; // for befriending QT_BEGIN_NAMESPACE -struct Q_CORE_EXPORT QHashData +struct QHashDummyValue { - struct Node { - Node *next; - uint h; - }; + bool operator==(const QHashDummyValue &) const noexcept { return true; } +}; - Node *fakeNext; - Node **buckets; - QtPrivate::RefCount ref; - int size; - int nodeSize; - short userNumBits; - short numBits; - int numBuckets; - uint seed; - uint sharable : 1; - uint strictAlignment : 1; - uint reserved : 30; - - void *allocateNode(int nodeAlign); - void freeNode(void *node); - QHashData *detach_helper(void (*node_duplicate)(Node *, void *), void (*node_delete)(Node *), - int nodeSize, int nodeAlign); - bool willGrow(); - void hasShrunk(); - void rehash(int hint); - void free_helper(void (*node_delete)(Node *)); - Node *firstNode(); -#ifdef QT_QHASH_DEBUG - void dump(); - void checkSanity(); -#endif - static Node *nextNode(Node *node); - static Node *previousNode(Node *node); +namespace QHashPrivate { - static const QHashData shared_null; -}; +template <typename T, typename = void> +constexpr inline bool HasQHashOverload = false; + +template <typename T> +constexpr inline bool HasQHashOverload<T, std::enable_if_t< + std::is_convertible_v<decltype(qHash(std::declval<const T &>(), std::declval<size_t>())), size_t> +>> = true; + +template <typename T, typename = void> +constexpr inline bool HasStdHashSpecializationWithSeed = false; + +template <typename T> +constexpr inline bool HasStdHashSpecializationWithSeed<T, std::enable_if_t< + std::is_convertible_v<decltype(std::hash<T>()(std::declval<const T &>(), std::declval<size_t>())), size_t> +>> = true; + +template <typename T, typename = void> +constexpr inline bool HasStdHashSpecializationWithoutSeed = false; -inline bool QHashData::willGrow() +template <typename T> +constexpr inline bool HasStdHashSpecializationWithoutSeed<T, std::enable_if_t< + std::is_convertible_v<decltype(std::hash<T>()(std::declval<const T &>())), size_t> +>> = true; + +template <typename T> +size_t calculateHash(const T &t, size_t seed = 0) { - if (size >= numBuckets) { - rehash(numBits + 1); - return true; + if constexpr (HasQHashOverload<T>) { + return qHash(t, seed); + } else if constexpr (HasStdHashSpecializationWithSeed<T>) { + return std::hash<T>()(t, seed); + } else if constexpr (HasStdHashSpecializationWithoutSeed<T>) { + Q_UNUSED(seed); + return std::hash<T>()(t); } else { - return false; + static_assert(sizeof(T) == 0, "The key type must have a qHash overload or a std::hash specialization"); + return 0; } } -inline void QHashData::hasShrunk() +template <typename Key, typename T> +struct Node { - if (size <= (numBuckets >> 3) && numBits > userNumBits) { - QT_TRY { - rehash(qMax(int(numBits) - 2, int(userNumBits))); - } QT_CATCH(const std::bad_alloc &) { - // ignore bad allocs - shrinking shouldn't throw. rehash is exception safe. - } + using KeyType = Key; + using ValueType = T; + + Key key; + T value; + template<typename ...Args> + static void createInPlace(Node *n, Key &&k, Args &&... args) + { new (n) Node{ std::move(k), T(std::forward<Args>(args)...) }; } + template<typename ...Args> + static void createInPlace(Node *n, const Key &k, Args &&... args) + { new (n) Node{ Key(k), T(std::forward<Args>(args)...) }; } + template<typename ...Args> + void emplaceValue(Args &&... args) + { + value = T(std::forward<Args>(args)...); } -} + T &&takeValue() noexcept(std::is_nothrow_move_assignable_v<T>) + { + return std::move(value); + } + bool valuesEqual(const Node *other) const { return value == other->value; } +}; -inline QHashData::Node *QHashData::firstNode() -{ - Node *e = reinterpret_cast<Node *>(this); - Node **bucket = buckets; - int n = numBuckets; - while (n--) { - if (*bucket != e) - return *bucket; - ++bucket; +template <typename Key> +struct Node<Key, QHashDummyValue> { + using KeyType = Key; + using ValueType = QHashDummyValue; + + Key key; + template<typename ...Args> + static void createInPlace(Node *n, Key &&k, Args &&...) + { new (n) Node{ std::move(k) }; } + template<typename ...Args> + static void createInPlace(Node *n, const Key &k, Args &&...) + { new (n) Node{ k }; } + template<typename ...Args> + void emplaceValue(Args &&...) + { } - return e; -} + ValueType takeValue() { return QHashDummyValue(); } + bool valuesEqual(const Node *) const { return true; } +}; -struct QHashDummyValue +template <typename T> +struct MultiNodeChain { + T value; + MultiNodeChain *next = nullptr; + ~MultiNodeChain() + { + } + qsizetype free() noexcept(std::is_nothrow_destructible_v<T>) + { + qsizetype nEntries = 0; + MultiNodeChain *e = this; + while (e) { + MultiNodeChain *n = e->next; + ++nEntries; + delete e; + e = n; + } + return nEntries; + } + bool contains(const T &val) const noexcept + { + const MultiNodeChain *e = this; + while (e) { + if (e->value == val) + return true; + e = e->next; + } + return false; + } }; -constexpr bool operator==(const QHashDummyValue &, const QHashDummyValue &) noexcept +template <typename Key, typename T> +struct MultiNode { - return true; -} + using KeyType = Key; + using ValueType = T; + using Chain = MultiNodeChain<T>; + + Key key; + Chain *value; + + template<typename ...Args> + static void createInPlace(MultiNode *n, Key &&k, Args &&... args) + { new (n) MultiNode(std::move(k), new Chain{ T(std::forward<Args>(args)...), nullptr }); } + template<typename ...Args> + static void createInPlace(MultiNode *n, const Key &k, Args &&... args) + { new (n) MultiNode(k, new Chain{ T(std::forward<Args>(args)...), nullptr }); } + + MultiNode(const Key &k, Chain *c) + : key(k), + value(c) + {} + MultiNode(Key &&k, Chain *c) noexcept(std::is_nothrow_move_assignable_v<Key>) + : key(std::move(k)), + value(c) + {} + + MultiNode(MultiNode &&other) + : key(other.key), + value(std::exchange(other.value, nullptr)) + { + } -Q_DECLARE_TYPEINFO(QHashDummyValue, Q_MOVABLE_TYPE | Q_DUMMY_TYPE); + MultiNode(const MultiNode &other) + : key(other.key) + { + Chain *c = other.value; + Chain **e = &value; + while (c) { + Chain *chain = new Chain{ c->value, nullptr }; + *e = chain; + e = &chain->next; + c = c->next; + } + } + ~MultiNode() + { + if (value) + value->free(); + } + static qsizetype freeChain(MultiNode *n) noexcept(std::is_nothrow_destructible_v<T>) + { + qsizetype size = n->value->free(); + n->value = nullptr; + return size; + } + template<typename ...Args> + void insertMulti(Args &&... args) + { + Chain *e = new Chain{ T(std::forward<Args>(args)...), nullptr }; + e->next = std::exchange(value, e); + } + template<typename ...Args> + void emplaceValue(Args &&... args) + { + value->value = T(std::forward<Args>(args)...); + } +}; -template <class Key, class T> -struct QHashNode +template<typename Node> +constexpr bool isRelocatable() { - QHashNode *next; - const uint h; - const Key key; - T value; + return QTypeInfo<typename Node::KeyType>::isRelocatable && QTypeInfo<typename Node::ValueType>::isRelocatable; +} - inline QHashNode(const Key &key0, const T &value0, uint hash, QHashNode *n) - : next(n), h(hash), key(key0), value(value0) {} - inline bool same_key(uint h0, const Key &key0) const { return h0 == h && key0 == key; } +struct SpanConstants { + static constexpr size_t SpanShift = 7; + static constexpr size_t NEntries = (1 << SpanShift); + static constexpr size_t LocalBucketMask = (NEntries - 1); + static constexpr size_t UnusedEntry = 0xff; -private: - Q_DISABLE_COPY(QHashNode) + static_assert ((NEntries & LocalBucketMask) == 0, "NEntries must be a power of two."); }; -// Specialize for QHashDummyValue in order to save some memory -template <class Key> -struct QHashNode<Key, QHashDummyValue> -{ - union { - QHashNode *next; - QHashDummyValue value; +// Regular hash tables consist of a list of buckets that can store Nodes. But simply allocating one large array of buckets +// would waste a lot of memory. To avoid this, we split the vector of buckets up into a vector of Spans. Each Span represents +// NEntries buckets. To quickly find the correct Span that holds a bucket, NEntries must be a power of two. +// +// Inside each Span, there is an offset array that represents the actual buckets. offsets contains either an index into the +// actual storage space for the Nodes (the 'entries' member) or 0xff (UnusedEntry) to flag that the bucket is empty. +// As we have only 128 entries per Span, the offset array can be represented using an unsigned char. This trick makes the hash +// table have a very small memory overhead compared to many other implementations. +template<typename Node> +struct Span { + // Entry is a slot available for storing a Node. The Span holds a pointer to + // an array of Entries. Upon construction of the array, those entries are + // unused, and nextFree() is being used to set up a singly linked list + // of free entries. + // When a node gets inserted, the first free entry is being picked, removed + // from the singly linked list and the Node gets constructed in place. + struct Entry { + struct { alignas(Node) unsigned char data[sizeof(Node)]; } storage; + + unsigned char &nextFree() { return *reinterpret_cast<unsigned char *>(&storage); } + Node &node() { return *reinterpret_cast<Node *>(&storage); } }; - const uint h; - const Key key; - inline QHashNode(const Key &key0, const QHashDummyValue &, uint hash, QHashNode *n) - : next(n), h(hash), key(key0) {} - inline bool same_key(uint h0, const Key &key0) const { return h0 == h && key0 == key; } + unsigned char offsets[SpanConstants::NEntries]; + Entry *entries = nullptr; + unsigned char allocated = 0; + unsigned char nextFree = 0; + Span() noexcept + { + memset(offsets, SpanConstants::UnusedEntry, sizeof(offsets)); + } + ~Span() + { + freeData(); + } + void freeData() noexcept(std::is_nothrow_destructible<Node>::value) + { + if (entries) { + if constexpr (!std::is_trivially_destructible<Node>::value) { + for (auto o : offsets) { + if (o != SpanConstants::UnusedEntry) + entries[o].node().~Node(); + } + } + delete[] entries; + entries = nullptr; + } + } + Node *insert(size_t i) + { + Q_ASSERT(i < SpanConstants::NEntries); + Q_ASSERT(offsets[i] == SpanConstants::UnusedEntry); + if (nextFree == allocated) + addStorage(); + unsigned char entry = nextFree; + Q_ASSERT(entry < allocated); + nextFree = entries[entry].nextFree(); + offsets[i] = entry; + return &entries[entry].node(); + } + void erase(size_t bucket) noexcept(std::is_nothrow_destructible<Node>::value) + { + Q_ASSERT(bucket < SpanConstants::NEntries); + Q_ASSERT(offsets[bucket] != SpanConstants::UnusedEntry); + + unsigned char entry = offsets[bucket]; + offsets[bucket] = SpanConstants::UnusedEntry; -private: - Q_DISABLE_COPY(QHashNode) + entries[entry].node().~Node(); + entries[entry].nextFree() = nextFree; + nextFree = entry; + } + size_t offset(size_t i) const noexcept + { + return offsets[i]; + } + bool hasNode(size_t i) const noexcept + { + return (offsets[i] != SpanConstants::UnusedEntry); + } + Node &at(size_t i) noexcept + { + Q_ASSERT(i < SpanConstants::NEntries); + Q_ASSERT(offsets[i] != SpanConstants::UnusedEntry); + + return entries[offsets[i]].node(); + } + const Node &at(size_t i) const noexcept + { + Q_ASSERT(i < SpanConstants::NEntries); + Q_ASSERT(offsets[i] != SpanConstants::UnusedEntry); + + return entries[offsets[i]].node(); + } + Node &atOffset(size_t o) noexcept + { + Q_ASSERT(o < allocated); + + return entries[o].node(); + } + const Node &atOffset(size_t o) const noexcept + { + Q_ASSERT(o < allocated); + + return entries[o].node(); + } + void moveLocal(size_t from, size_t to) noexcept + { + Q_ASSERT(offsets[from] != SpanConstants::UnusedEntry); + Q_ASSERT(offsets[to] == SpanConstants::UnusedEntry); + offsets[to] = offsets[from]; + offsets[from] = SpanConstants::UnusedEntry; + } + void moveFromSpan(Span &fromSpan, size_t fromIndex, size_t to) noexcept(std::is_nothrow_move_constructible_v<Node>) + { + Q_ASSERT(to < SpanConstants::NEntries); + Q_ASSERT(offsets[to] == SpanConstants::UnusedEntry); + Q_ASSERT(fromIndex < SpanConstants::NEntries); + Q_ASSERT(fromSpan.offsets[fromIndex] != SpanConstants::UnusedEntry); + if (nextFree == allocated) + addStorage(); + Q_ASSERT(nextFree < allocated); + offsets[to] = nextFree; + Entry &toEntry = entries[nextFree]; + nextFree = toEntry.nextFree(); + + size_t fromOffset = fromSpan.offsets[fromIndex]; + fromSpan.offsets[fromIndex] = SpanConstants::UnusedEntry; + Entry &fromEntry = fromSpan.entries[fromOffset]; + + if constexpr (isRelocatable<Node>()) { + memcpy(&toEntry, &fromEntry, sizeof(Entry)); + } else { + new (&toEntry.node()) Node(std::move(fromEntry.node())); + fromEntry.node().~Node(); + } + fromEntry.nextFree() = fromSpan.nextFree; + fromSpan.nextFree = static_cast<unsigned char>(fromOffset); + } + + void addStorage() + { + Q_ASSERT(allocated < SpanConstants::NEntries); + Q_ASSERT(nextFree == allocated); + // the hash table should always be between 25 and 50% full + // this implies that we on average have between 32 and 64 entries + // in here. More exactly, we have a binominal distribution of the amount of + // occupied entries. + // For a 25% filled table, the average is 32 entries, with a 95% chance that we have between + // 23 and 41 entries. + // For a 50% filled table, the average is 64 entries, with a 95% chance that we have between + // 53 and 75 entries. + // Since we only resize the table once it's 50% filled and we want to avoid copies of + // data where possible, we initially allocate 48 entries, then resize to 80 entries, after that + // resize by increments of 16. That way, we usually only get one resize of the table + // while filling it. + size_t alloc; + static_assert(SpanConstants::NEntries % 8 == 0); + if (!allocated) + alloc = SpanConstants::NEntries / 8 * 3; + else if (allocated == SpanConstants::NEntries / 8 * 3) + alloc = SpanConstants::NEntries / 8 * 5; + else + alloc = allocated + SpanConstants::NEntries/8; + Entry *newEntries = new Entry[alloc]; + // we only add storage if the previous storage was fully filled, so + // simply copy the old data over + if constexpr (isRelocatable<Node>()) { + if (allocated) + memcpy(newEntries, entries, allocated * sizeof(Entry)); + } else { + for (size_t i = 0; i < allocated; ++i) { + new (&newEntries[i].node()) Node(std::move(entries[i].node())); + entries[i].node().~Node(); + } + } + for (size_t i = allocated; i < alloc; ++i) { + newEntries[i].nextFree() = uchar(i + 1); + } + delete[] entries; + entries = newEntries; + allocated = uchar(alloc); + } }; +// QHash uses a power of two growth policy. +namespace GrowthPolicy { +inline constexpr size_t bucketsForCapacity(size_t requestedCapacity) noexcept +{ + constexpr int SizeDigits = std::numeric_limits<size_t>::digits; + + // We want to use at minimum a full span (128 entries), so we hardcode it for any requested + // capacity <= 64. Any capacity above that gets rounded to a later power of two. + if (requestedCapacity <= 64) + return SpanConstants::NEntries; + + // Same as + // qNextPowerOfTwo(2 * requestedCapacity); + // + // but ensuring neither our multiplication nor the function overflow. + // Additionally, the maximum memory allocation is 2^31-1 or 2^63-1 bytes + // (limited by qsizetype and ptrdiff_t). + int count = qCountLeadingZeroBits(requestedCapacity); + if (count < 2) + return (std::numeric_limits<size_t>::max)(); // will cause std::bad_alloc + return size_t(1) << (SizeDigits - count + 1); +} +inline constexpr size_t bucketForHash(size_t nBuckets, size_t hash) noexcept +{ + return hash & (nBuckets - 1); +} +} // namespace GrowthPolicy -#if 0 -// ### -// The introduction of the QHash random seed breaks this optimization, as it -// relies on qHash(int i) = i. If the hash value is salted, then the hash -// table becomes corrupted. -// -// A bit more research about whether it makes sense or not to salt integer -// keys (and in general keys whose hash value is easy to invert) -// is needed, or about how keep this optimization and the seed (f.i. by -// specializing QHash for integer values, and re-apply the seed during lookup). -// -// Be aware that such changes can easily be binary incompatible, and therefore -// cannot be made during the Qt 5 lifetime. -#define Q_HASH_DECLARE_INT_NODES(key_type) \ - template <class T> \ - struct QHashDummyNode<key_type, T> { \ - QHashDummyNode *next; \ - union { uint h; key_type key; }; \ -\ - inline QHashDummyNode(key_type /* key0 */) {} \ - }; \ -\ - template <class T> \ - struct QHashNode<key_type, T> { \ - QHashNode *next; \ - union { uint h; key_type key; }; \ - T value; \ -\ - inline QHashNode(key_type /* key0 */) {} \ - inline QHashNode(key_type /* key0 */, const T &value0) : value(value0) {} \ - inline bool same_key(uint h0, key_type) const { return h0 == h; } \ - } - -#if defined(Q_BYTE_ORDER) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN -Q_HASH_DECLARE_INT_NODES(short); -Q_HASH_DECLARE_INT_NODES(ushort); -#endif -Q_HASH_DECLARE_INT_NODES(int); -Q_HASH_DECLARE_INT_NODES(uint); -#undef Q_HASH_DECLARE_INT_NODES -#endif // #if 0 +template <typename Node> +struct iterator; -template <class Key, class T> -class QHash +template <typename Node> +struct Data { - typedef QHashNode<Key, T> Node; + using Key = typename Node::KeyType; + using T = typename Node::ValueType; + using Span = QHashPrivate::Span<Node>; + using iterator = QHashPrivate::iterator<Node>; + + QtPrivate::RefCount ref = {{1}}; + size_t size = 0; + size_t numBuckets = 0; + size_t seed = 0; + Span *spans = nullptr; + + static constexpr size_t maxNumBuckets() noexcept + { + return (std::numeric_limits<ptrdiff_t>::max)() / sizeof(Span); + } + + struct Bucket { + Span *span; + size_t index; + + Bucket(Span *s, size_t i) noexcept + : span(s), index(i) + {} + Bucket(const Data *d, size_t bucket) noexcept + : span(d->spans + (bucket >> SpanConstants::SpanShift)), + index(bucket & SpanConstants::LocalBucketMask) + {} + Bucket(iterator it) noexcept + : Bucket(it.d, it.bucket) + {} + + size_t toBucketIndex(const Data *d) const noexcept + { + return ((span - d->spans) << SpanConstants::SpanShift) | index; + } + iterator toIterator(const Data *d) const noexcept { return iterator{d, toBucketIndex(d)}; } + void advanceWrapped(const Data *d) noexcept + { + advance_impl(d, d->spans); + } + void advance(const Data *d) noexcept + { + advance_impl(d, nullptr); + } + bool isUnused() const noexcept + { + return !span->hasNode(index); + } + size_t offset() const noexcept + { + return span->offset(index); + } + Node &nodeAtOffset(size_t offset) + { + return span->atOffset(offset); + } + Node *node() + { + return &span->at(index); + } + Node *insert() const + { + return span->insert(index); + } - union { - QHashData *d; - QHashNode<Key, T> *e; + private: + friend bool operator==(Bucket lhs, Bucket rhs) noexcept + { + return lhs.span == rhs.span && lhs.index == rhs.index; + } + friend bool operator!=(Bucket lhs, Bucket rhs) noexcept { return !(lhs == rhs); } + + void advance_impl(const Data *d, Span *whenAtEnd) noexcept + { + Q_ASSERT(span); + ++index; + if (Q_UNLIKELY(index == SpanConstants::NEntries)) { + index = 0; + ++span; + if (span - d->spans == ptrdiff_t(d->numBuckets >> SpanConstants::SpanShift)) + span = whenAtEnd; + } + } }; - static inline Node *concrete(QHashData::Node *node) { - return reinterpret_cast<Node *>(node); + static auto allocateSpans(size_t numBuckets) + { + struct R { + Span *spans; + size_t nSpans; + }; + + constexpr qptrdiff MaxSpanCount = (std::numeric_limits<qptrdiff>::max)() / sizeof(Span); + constexpr size_t MaxBucketCount = MaxSpanCount << SpanConstants::SpanShift; + + if (numBuckets > MaxBucketCount) { + Q_CHECK_PTR(false); + Q_UNREACHABLE(); // no exceptions and no assertions -> no error reporting + } + + size_t nSpans = numBuckets >> SpanConstants::SpanShift; + return R{ new Span[nSpans], nSpans }; + } + + Data(size_t reserve = 0) + { + numBuckets = GrowthPolicy::bucketsForCapacity(reserve); + spans = allocateSpans(numBuckets).spans; + seed = QHashSeed::globalSeed(); + } + + void reallocationHelper(const Data &other, size_t nSpans, bool resized) + { + for (size_t s = 0; s < nSpans; ++s) { + const Span &span = other.spans[s]; + for (size_t index = 0; index < SpanConstants::NEntries; ++index) { + if (!span.hasNode(index)) + continue; + const Node &n = span.at(index); + auto it = resized ? findBucket(n.key) : Bucket { spans + s, index }; + Q_ASSERT(it.isUnused()); + Node *newNode = it.insert(); + new (newNode) Node(n); + } + } + } + + Data(const Data &other) : size(other.size), numBuckets(other.numBuckets), seed(other.seed) + { + auto r = allocateSpans(numBuckets); + spans = r.spans; + reallocationHelper(other, r.nSpans, false); + } + Data(const Data &other, size_t reserved) : size(other.size), seed(other.seed) + { + numBuckets = GrowthPolicy::bucketsForCapacity(qMax(size, reserved)); + spans = allocateSpans(numBuckets).spans; + size_t otherNSpans = other.numBuckets >> SpanConstants::SpanShift; + reallocationHelper(other, otherNSpans, numBuckets != other.numBuckets); + } + + static Data *detached(Data *d) + { + if (!d) + return new Data; + Data *dd = new Data(*d); + if (!d->ref.deref()) + delete d; + return dd; + } + static Data *detached(Data *d, size_t size) + { + if (!d) + return new Data(size); + Data *dd = new Data(*d, size); + if (!d->ref.deref()) + delete d; + return dd; + } + + void clear() + { + delete[] spans; + spans = nullptr; + size = 0; + numBuckets = 0; + } + + iterator detachedIterator(iterator other) const noexcept + { + return iterator{this, other.bucket}; + } + + iterator begin() const noexcept + { + iterator it{ this, 0 }; + if (it.isUnused()) + ++it; + return it; + } + + constexpr iterator end() const noexcept + { + return iterator(); } - static inline int alignOfNode() { return qMax<int>(sizeof(void*), alignof(Node)); } + void rehash(size_t sizeHint = 0) + { + if (sizeHint == 0) + sizeHint = size; + size_t newBucketCount = GrowthPolicy::bucketsForCapacity(sizeHint); + + Span *oldSpans = spans; + size_t oldBucketCount = numBuckets; + spans = allocateSpans(newBucketCount).spans; + numBuckets = newBucketCount; + size_t oldNSpans = oldBucketCount >> SpanConstants::SpanShift; + + for (size_t s = 0; s < oldNSpans; ++s) { + Span &span = oldSpans[s]; + for (size_t index = 0; index < SpanConstants::NEntries; ++index) { + if (!span.hasNode(index)) + continue; + Node &n = span.at(index); + auto it = findBucket(n.key); + Q_ASSERT(it.isUnused()); + Node *newNode = it.insert(); + new (newNode) Node(std::move(n)); + } + span.freeData(); + } + delete[] oldSpans; + } + + size_t nextBucket(size_t bucket) const noexcept + { + ++bucket; + if (bucket == numBuckets) + bucket = 0; + return bucket; + } + + float loadFactor() const noexcept + { + return float(size)/numBuckets; + } + bool shouldGrow() const noexcept + { + return size >= (numBuckets >> 1); + } + + 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)); + // 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 bucket; + } else { + Node &n = bucket.nodeAtOffset(offset); + if (qHashEquals(n.key, key)) + return bucket; + } + bucket.advanceWrapped(this); + } + } + + template <typename K> Node *findNode(const K &key) const noexcept + { + auto bucket = findBucket(key); + if (bucket.isUnused()) + return nullptr; + return bucket.node(); + } + + struct InsertionResult + { + iterator it; + bool initialized; + }; + + template <typename K> InsertionResult findOrInsert(const K &key) noexcept + { + Bucket it(static_cast<Span *>(nullptr), 0); + if (numBuckets > 0) { + it = findBucket(key); + if (!it.isUnused()) + return { it.toIterator(this), true }; + } + if (shouldGrow()) { + rehash(size + 1); + it = findBucket(key); // need to get a new iterator after rehashing + } + Q_ASSERT(it.span != nullptr); + Q_ASSERT(it.isUnused()); + it.insert(); + ++size; + return { it.toIterator(this), false }; + } + + void erase(Bucket bucket) noexcept(std::is_nothrow_destructible<Node>::value) + { + Q_ASSERT(bucket.span->hasNode(bucket.index)); + bucket.span->erase(bucket.index); + --size; + + // re-insert the following entries to avoid holes + Bucket next = bucket; + while (true) { + next.advanceWrapped(this); + size_t offset = next.offset(); + if (offset == SpanConstants::UnusedEntry) + return; + size_t hash = QHashPrivate::calculateHash(next.nodeAtOffset(offset).key, seed); + Bucket newBucket(this, GrowthPolicy::bucketForHash(numBuckets, hash)); + while (true) { + if (newBucket == next) { + // nothing to do, item is at the right plae + break; + } else if (newBucket == bucket) { + // move into the hole we created earlier + if (next.span == bucket.span) { + bucket.span->moveLocal(next.index, bucket.index); + } else { + // move between spans, more expensive + bucket.span->moveFromSpan(*next.span, next.index, bucket.index); + } + bucket = next; + break; + } + newBucket.advanceWrapped(this); + } + } + } + + ~Data() + { + delete [] spans; + } +}; + +template <typename Node> +struct iterator { + using Span = QHashPrivate::Span<Node>; + + const Data<Node> *d = nullptr; + size_t bucket = 0; + + size_t span() const noexcept { return bucket >> SpanConstants::SpanShift; } + size_t index() const noexcept { return bucket & SpanConstants::LocalBucketMask; } + inline bool isUnused() const noexcept { return !d->spans[span()].hasNode(index()); } + + inline Node *node() const noexcept + { + Q_ASSERT(!isUnused()); + return &d->spans[span()].at(index()); + } + bool atEnd() const noexcept { return !d; } + + iterator operator++() noexcept + { + while (true) { + ++bucket; + if (bucket == d->numBuckets) { + d = nullptr; + bucket = 0; + break; + } + if (!isUnused()) + break; + } + return *this; + } + bool operator==(iterator other) const noexcept + { return d == other.d && bucket == other.bucket; } + bool operator!=(iterator other) const noexcept + { return !(*this == other); } +}; + + + +} // namespace QHashPrivate + +template <typename Key, typename T> +class QHash +{ + using Node = QHashPrivate::Node<Key, T>; + using Data = QHashPrivate::Data<Node>; + friend class QSet<Key>; + friend class QMultiHash<Key, T>; + friend tst_QHash; + + Data *d = nullptr; public: - inline QHash() noexcept : d(const_cast<QHashData *>(&QHashData::shared_null)) { } + using key_type = Key; + using mapped_type = T; + using value_type = T; + using size_type = qsizetype; + using difference_type = qsizetype; + using reference = T &; + using const_reference = const T &; + + inline QHash() noexcept = default; inline QHash(std::initializer_list<std::pair<Key,T> > list) - : d(const_cast<QHashData *>(&QHashData::shared_null)) + : d(new Data(list.size())) { - reserve(int(list.size())); for (typename std::initializer_list<std::pair<Key,T> >::const_iterator it = list.begin(); it != list.end(); ++it) insert(it->first, it->second); } - QHash(const QHash &other) : d(other.d) { d->ref.ref(); if (!d->sharable) detach(); } - ~QHash() { if (!d->ref.deref()) freeData(d); } + QHash(const QHash &other) noexcept + : d(other.d) + { + if (d) + d->ref.ref(); + } + ~QHash() + { + static_assert(std::is_nothrow_destructible_v<Key>, "Types with throwing destructors are not supported in Qt containers."); + static_assert(std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers."); + + if (d && !d->ref.deref()) + delete d; + } - QHash &operator=(const QHash &other); - QHash(QHash &&other) noexcept : d(other.d) { other.d = const_cast<QHashData *>(&QHashData::shared_null); } - QHash &operator=(QHash &&other) noexcept - { QHash moved(std::move(other)); swap(moved); return *this; } + QHash &operator=(const QHash &other) noexcept(std::is_nothrow_destructible<Node>::value) + { + if (d != other.d) { + Data *o = other.d; + if (o) + o->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = o; + } + return *this; + } + + QHash(QHash &&other) noexcept + : d(std::exchange(other.d, nullptr)) + { + } + QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QHash) #ifdef Q_QDOC template <typename InputIterator> QHash(InputIterator f, InputIterator l); @@ -275,150 +897,287 @@ public: insert(f->first, f->second); } #endif - void swap(QHash &other) noexcept { qSwap(d, other.d); } + void swap(QHash &other) noexcept { qt_ptr_swap(d, other.d); } + +#ifndef Q_QDOC + template <typename AKey = Key, typename AT = T> + QTypeTraits::compare_eq_result_container<QHash, AKey, AT> operator==(const QHash &other) const noexcept + { + if (d == other.d) + return true; + if (size() != other.size()) + return false; + for (const_iterator it = other.begin(); it != other.end(); ++it) { + const_iterator i = find(it.key()); + if (i == end() || !i.i.node()->valuesEqual(it.i.node())) + return false; + } + // all values must be the same as size is the same + return true; + } + template <typename AKey = Key, typename AT = T> + QTypeTraits::compare_eq_result_container<QHash, AKey, AT> operator!=(const QHash &other) const noexcept + { return !(*this == other); } +#else bool operator==(const QHash &other) const; - bool operator!=(const QHash &other) const { return !(*this == other); } + bool operator!=(const QHash &other) const; +#endif // Q_QDOC + + inline qsizetype size() const noexcept { return d ? qsizetype(d->size) : 0; } + inline bool isEmpty() const noexcept { return !d || d->size == 0; } - inline int size() const { return d->size; } + inline qsizetype capacity() const noexcept { return d ? qsizetype(d->numBuckets >> 1) : 0; } + void reserve(qsizetype size) + { + // reserve(0) is used in squeeze() + if (size && (this->capacity() >= size)) + return; + if (isDetached()) + d->rehash(size); + else + d = Data::detached(d, size_t(size)); + } + inline void squeeze() + { + if (capacity()) + reserve(0); + } + + inline void detach() { if (!d || d->ref.isShared()) d = Data::detached(d); } + inline bool isDetached() const noexcept { return d && !d->ref.isShared(); } + bool isSharedWith(const QHash &other) const noexcept { return d == other.d; } + + void clear() noexcept(std::is_nothrow_destructible<Node>::value) + { + if (d && !d->ref.deref()) + delete d; + d = nullptr; + } + + 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); + size_t bucket = it.toBucketIndex(d); + detach(); + it = typename Data::Bucket(d, bucket); // reattach in case of detach - inline bool isEmpty() const { return d->size == 0; } + if (it.isUnused()) + return false; + d->erase(it); + return true; + } - inline int capacity() const { return d->numBuckets; } - void reserve(int size); - inline void squeeze() { reserve(1); } +public: + template <typename Predicate> + qsizetype removeIf(Predicate pred) + { + return QtPrivate::associative_erase_if(*this, pred); + } - inline void detach() { if (d->ref.isShared()) detach_helper(); } - inline bool isDetached() const { return !d->ref.isShared(); } - bool isSharedWith(const QHash &other) const { return d == other.d; } + 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); + size_t bucket = it.toBucketIndex(d); + detach(); + it = typename Data::Bucket(d, bucket); // reattach in case of detach - void clear(); + if (it.isUnused()) + return T(); + T value = it.node()->takeValue(); + d->erase(it); + return value; + } - int remove(const Key &key); - T take(const Key &key); +public: + bool contains(const Key &key) const noexcept + { + if (!d) + return false; + return d->findNode(key) != nullptr; + } + qsizetype count(const Key &key) const noexcept + { + return contains(key) ? 1 : 0; + } - bool contains(const Key &key) const; - const Key key(const T &value) const; - const Key key(const T &value, const Key &defaultKey) const; - const T value(const Key &key) const; - const T value(const Key &key, const T &defaultValue) const; - T &operator[](const Key &key); - const T operator[](const Key &key) const; +private: + 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(); + ++i; + } + } - QList<Key> uniqueKeys() const; - QList<Key> keys() const; - QList<Key> keys(const T &value) const; - QList<T> values() const; - QList<T> values(const Key &key) const; - int count(const Key &key) const; + return defaultFn(); + } + +public: + Key key(const T &value) const noexcept + { + return keyImpl(value, [] { return Key(); }); + } + Key key(const T &value, const Key &defaultKey) const noexcept + { + return keyImpl(value, [&] { return defaultKey; }); + } + +private: + 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 defaultValue(); + } +public: + T value(const Key &key) const noexcept + { + return valueImpl(key, [] { return T(); }); + } + + T value(const Key &key, const T &defaultValue) const noexcept + { + 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(key), T()); + return result.it.node()->value; + } + +public: + const T operator[](const Key &key) const noexcept + { + return value(key); + } + + QList<Key> keys() const { return QList<Key>(keyBegin(), keyEnd()); } + QList<Key> keys(const T &value) const + { + QList<Key> res; + const_iterator i = begin(); + while (i != end()) { + if (i.value() == value) + res.append(i.key()); + ++i; + } + return res; + } + QList<T> values() const { return QList<T>(begin(), end()); } class const_iterator; class iterator { + using piter = typename QHashPrivate::iterator<Node>; friend class const_iterator; friend class QHash<Key, T>; friend class QSet<Key>; - QHashData::Node *i; + piter i; + explicit inline iterator(piter it) noexcept : i(it) { } public: - typedef std::bidirectional_iterator_tag iterator_category; + typedef std::forward_iterator_tag iterator_category; typedef qptrdiff difference_type; typedef T value_type; typedef T *pointer; typedef T &reference; - inline iterator() : i(nullptr) { } - explicit inline iterator(void *node) : i(reinterpret_cast<QHashData::Node *>(node)) { } + constexpr iterator() noexcept = default; - inline const Key &key() const { return concrete(i)->key; } - inline T &value() const { return concrete(i)->value; } - inline T &operator*() const { return concrete(i)->value; } - inline T *operator->() const { return &concrete(i)->value; } - inline bool operator==(const iterator &o) const { return i == o.i; } - inline bool operator!=(const iterator &o) const { return i != o.i; } + inline const Key &key() const noexcept { return i.node()->key; } + inline T &value() const noexcept { return i.node()->value; } + inline T &operator*() const noexcept { return i.node()->value; } + inline T *operator->() const noexcept { return &i.node()->value; } + inline bool operator==(const iterator &o) const noexcept { return i == o.i; } + inline bool operator!=(const iterator &o) const noexcept { return i != o.i; } - inline iterator &operator++() { - i = QHashData::nextNode(i); - return *this; - } - inline iterator operator++(int) { - iterator r = *this; - i = QHashData::nextNode(i); - return r; - } - inline iterator &operator--() { - i = QHashData::previousNode(i); + inline iterator &operator++() noexcept + { + ++i; return *this; } - inline iterator operator--(int) { + inline iterator operator++(int) noexcept + { iterator r = *this; - i = QHashData::previousNode(i); + ++i; return r; } - inline iterator operator+(int j) const - { iterator r = *this; if (j > 0) while (j--) ++r; else while (j++) --r; return r; } - inline iterator operator-(int j) const { return operator+(-j); } - inline iterator &operator+=(int j) { return *this = *this + j; } - inline iterator &operator-=(int j) { return *this = *this - j; } - friend inline iterator operator+(int j, iterator k) { return k + j; } - - inline bool operator==(const const_iterator &o) const { return i == o.i; } - inline bool operator!=(const const_iterator &o) const { return i != o.i; } + + inline bool operator==(const const_iterator &o) const noexcept { return i == o.i; } + inline bool operator!=(const const_iterator &o) const noexcept { return i != o.i; } }; friend class iterator; class const_iterator { + using piter = typename QHashPrivate::iterator<Node>; friend class iterator; friend class QHash<Key, T>; friend class QSet<Key>; - QHashData::Node *i; + piter i; + explicit inline const_iterator(piter it) : i(it) { } public: - typedef std::bidirectional_iterator_tag iterator_category; + typedef std::forward_iterator_tag iterator_category; typedef qptrdiff difference_type; typedef T value_type; typedef const T *pointer; typedef const T &reference; - Q_DECL_CONSTEXPR inline const_iterator() : i(nullptr) { } - explicit inline const_iterator(void *node) - : i(reinterpret_cast<QHashData::Node *>(node)) { } - inline const_iterator(const iterator &o) - { i = o.i; } - - inline const Key &key() const { return concrete(i)->key; } - inline const T &value() const { return concrete(i)->value; } - inline const T &operator*() const { return concrete(i)->value; } - inline const T *operator->() const { return &concrete(i)->value; } - Q_DECL_CONSTEXPR inline bool operator==(const const_iterator &o) const { return i == o.i; } - Q_DECL_CONSTEXPR inline bool operator!=(const const_iterator &o) const { return i != o.i; } - - inline const_iterator &operator++() { - i = QHashData::nextNode(i); - return *this; - } - inline const_iterator operator++(int) { - const_iterator r = *this; - i = QHashData::nextNode(i); - return r; - } - inline const_iterator &operator--() { - i = QHashData::previousNode(i); + constexpr const_iterator() noexcept = default; + inline const_iterator(const iterator &o) noexcept : i(o.i) { } + + inline const Key &key() const noexcept { return i.node()->key; } + inline const T &value() const noexcept { return i.node()->value; } + inline const T &operator*() const noexcept { return i.node()->value; } + inline const T *operator->() const noexcept { return &i.node()->value; } + inline bool operator==(const const_iterator &o) const noexcept { return i == o.i; } + inline bool operator!=(const const_iterator &o) const noexcept { return i != o.i; } + + inline const_iterator &operator++() noexcept + { + ++i; return *this; } - inline const_iterator operator--(int) { + inline const_iterator operator++(int) noexcept + { const_iterator r = *this; - i = QHashData::previousNode(i); + ++i; return r; } - inline const_iterator operator+(int j) const - { const_iterator r = *this; if (j > 0) while (j--) ++r; else while (j++) --r; return r; } - inline const_iterator operator-(int j) const { return operator+(-j); } - inline const_iterator &operator+=(int j) { return *this = *this + j; } - inline const_iterator &operator-=(int j) { return *this = *this - j; } - friend inline const_iterator operator+(int j, const_iterator k) { return k + j; } }; friend class const_iterator; @@ -428,653 +1187,1024 @@ public: public: typedef typename const_iterator::iterator_category iterator_category; - typedef typename const_iterator::difference_type difference_type; + typedef qptrdiff difference_type; typedef Key value_type; typedef const Key *pointer; typedef const Key &reference; - key_iterator() = default; - explicit key_iterator(const_iterator o) : i(o) { } + key_iterator() noexcept = default; + explicit key_iterator(const_iterator o) noexcept : i(o) { } - const Key &operator*() const { return i.key(); } - const Key *operator->() const { return &i.key(); } - bool operator==(key_iterator o) const { return i == o.i; } - bool operator!=(key_iterator o) const { return i != o.i; } + const Key &operator*() const noexcept { return i.key(); } + const Key *operator->() const noexcept { return &i.key(); } + bool operator==(key_iterator o) const noexcept { return i == o.i; } + bool operator!=(key_iterator o) const noexcept { return i != o.i; } - inline key_iterator &operator++() { ++i; return *this; } - inline key_iterator operator++(int) { return key_iterator(i++);} - inline key_iterator &operator--() { --i; return *this; } - inline key_iterator operator--(int) { return key_iterator(i--); } - const_iterator base() const { return i; } + inline key_iterator &operator++() noexcept { ++i; return *this; } + inline key_iterator operator++(int) noexcept { return key_iterator(i++);} + const_iterator base() const noexcept { return i; } }; typedef QKeyValueIterator<const Key&, const T&, const_iterator> const_key_value_iterator; typedef QKeyValueIterator<const Key&, T&, iterator> key_value_iterator; // STL style - inline iterator begin() { detach(); return iterator(d->firstNode()); } - inline const_iterator begin() const { return const_iterator(d->firstNode()); } - inline const_iterator cbegin() const { return const_iterator(d->firstNode()); } - inline const_iterator constBegin() const { return const_iterator(d->firstNode()); } - inline iterator end() { detach(); return iterator(e); } - inline const_iterator end() const { return const_iterator(e); } - inline const_iterator cend() const { return const_iterator(e); } - inline const_iterator constEnd() const { return const_iterator(e); } - inline key_iterator keyBegin() const { return key_iterator(begin()); } - inline key_iterator keyEnd() const { return key_iterator(end()); } + inline iterator begin() { detach(); return iterator(d->begin()); } + inline const_iterator begin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator cbegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator constBegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline iterator end() noexcept { return iterator(); } + inline const_iterator end() const noexcept { return const_iterator(); } + inline const_iterator cend() const noexcept { return const_iterator(); } + inline const_iterator constEnd() const noexcept { return const_iterator(); } + inline key_iterator keyBegin() const noexcept { return key_iterator(begin()); } + inline key_iterator keyEnd() const noexcept { return key_iterator(end()); } inline key_value_iterator keyValueBegin() { return key_value_iterator(begin()); } inline key_value_iterator keyValueEnd() { return key_value_iterator(end()); } - inline const_key_value_iterator keyValueBegin() const { return const_key_value_iterator(begin()); } - inline const_key_value_iterator constKeyValueBegin() const { return const_key_value_iterator(begin()); } - inline const_key_value_iterator keyValueEnd() const { return const_key_value_iterator(end()); } - inline const_key_value_iterator constKeyValueEnd() const { return const_key_value_iterator(end()); } + inline const_key_value_iterator keyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator constKeyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator keyValueEnd() const noexcept { return const_key_value_iterator(end()); } + inline const_key_value_iterator constKeyValueEnd() const noexcept { return const_key_value_iterator(end()); } + auto asKeyValueRange() & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() const & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() && { return QtPrivate::QKeyValueRange(std::move(*this)); } + auto asKeyValueRange() const && { return QtPrivate::QKeyValueRange(std::move(*this)); } + + iterator erase(const_iterator it) + { + Q_ASSERT(it != constEnd()); + detach(); + // ensure a valid iterator across the detach: + iterator i = iterator{d->detachedIterator(it.i)}; + typename Data::Bucket bucket(i.i); - QPair<iterator, iterator> equal_range(const Key &key); - QPair<const_iterator, const_iterator> equal_range(const Key &key) const noexcept; - iterator erase(iterator it) { return erase(const_iterator(it.i)); } - iterator erase(const_iterator it); + d->erase(bucket); + if (bucket.toBucketIndex(d) == d->numBuckets - 1 || bucket.isUnused()) + ++i; + return i; + } - // more Qt + std::pair<iterator, iterator> equal_range(const Key &key) + { + return equal_range_impl(*this, key); + } + std::pair<const_iterator, const_iterator> equal_range(const Key &key) const noexcept + { + 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 != decltype(first){}) + ++second; + return std::make_pair(first, second); + } + + template <typename K> iterator findImpl(const K &key) + { + if (isEmpty()) // prevents detaching shared null + return end(); + auto it = d->findBucket(key); + size_t bucket = it.toBucketIndex(d); + detach(); + it = typename Data::Bucket(d, bucket); // reattach in case of detach + if (it.isUnused()) + return end(); + return iterator(it.toIterator(d)); + } + template <typename K> const_iterator constFindImpl(const K &key) const noexcept + { + if (isEmpty()) + return end(); + auto it = d->findBucket(key); + if (it.isUnused()) + return end(); + return const_iterator({d, it.toBucketIndex(d)}); + } + +public: typedef iterator Iterator; typedef const_iterator ConstIterator; - inline int count() const { return d->size; } - iterator find(const Key &key); - const_iterator find(const Key &key) const; - const_iterator constFind(const Key &key) const; - iterator insert(const Key &key, const T &value); - iterator insertMulti(const Key &key, const T &value); - QHash &unite(const QHash &other); - - // STL compatibility - typedef T mapped_type; - typedef Key key_type; - typedef qptrdiff difference_type; - typedef int size_type; - - inline bool empty() const { return isEmpty(); } - -#ifdef QT_QHASH_DEBUG - inline void dump() const { d->dump(); } - inline void checkSanity() const { d->checkSanity(); } -#endif + 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); + } + iterator insert(const Key &key, const T &value) + { + return emplace(key, value); + } -private: - void detach_helper(); - void freeData(QHashData *d); - Node **findNode(const Key &key, uint *hp = nullptr) const; - Node **findNode(const Key &key, uint h) const; - Node *createNode(uint h, const Key &key, const T &value, Node **nextNode); - void deleteNode(Node *node); - static void deleteNode2(QHashData::Node *node); - - static void duplicateNode(QHashData::Node *originalNode, void *newNode); - - bool isValidIterator(const iterator &it) const noexcept - { return isValidNode(it.i); } - bool isValidIterator(const const_iterator &it) const noexcept - { return isValidNode(it.i); } - bool isValidNode(QHashData::Node *node) const noexcept - { -#if defined(QT_DEBUG) && !defined(Q_HASH_NO_ITERATOR_DEBUG) - while (node->next) - node = node->next; - return (static_cast<void *>(node) == d); -#else - Q_UNUSED(node); - return true; -#endif + void insert(const QHash &hash) + { + if (d == hash.d || !hash.d) + return; + if (!d) { + *this = hash; + return; + } + + detach(); + + for (auto it = hash.begin(); it != hash.end(); ++it) + emplace(it.key(), it.value()); } - friend class QSet<Key>; -}; + template <typename ...Args> + iterator emplace(const Key &key, Args &&... args) + { + Key copy = key; // Needs to be explicit for MSVC 2019 + return emplace(std::move(copy), std::forward<Args>(args)...); + } -template <class Key, class T> -Q_INLINE_TEMPLATE void QHash<Key, T>::deleteNode(Node *node) -{ - deleteNode2(reinterpret_cast<QHashData::Node*>(node)); - d->freeNode(node); -} + template <typename ...Args> + iterator emplace(Key &&key, Args &&... args) + { + if (isDetached()) { + if (d->shouldGrow()) // Construct the value now so that no dangling references are used + return emplace_helper(std::move(key), T(std::forward<Args>(args)...)); + return emplace_helper(std::move(key), std::forward<Args>(args)...); + } + // else: we must detach + const auto copy = *this; // keep 'args' alive across the detach/growth + detach(); + return emplace_helper(std::move(key), std::forward<Args>(args)...); + } -template <class Key, class T> -Q_INLINE_TEMPLATE void QHash<Key, T>::deleteNode2(QHashData::Node *node) -{ -#ifdef Q_CC_BOR - concrete(node)->~QHashNode<Key, T>(); -#else - concrete(node)->~Node(); -#endif -} + float load_factor() const noexcept { return d ? d->loadFactor() : 0; } + static float max_load_factor() noexcept { return 0.5; } + size_t bucket_count() const noexcept { return d ? d->numBuckets : 0; } + static size_t max_bucket_count() noexcept { return Data::maxNumBuckets(); } -template <class Key, class T> -Q_INLINE_TEMPLATE void QHash<Key, T>::duplicateNode(QHashData::Node *node, void *newNode) -{ - Node *concreteNode = concrete(node); - new (newNode) Node(concreteNode->key, concreteNode->value, concreteNode->h, nullptr); -} + inline bool empty() const noexcept { return isEmpty(); } -template <class Key, class T> -Q_INLINE_TEMPLATE typename QHash<Key, T>::Node * -QHash<Key, T>::createNode(uint ah, const Key &akey, const T &avalue, Node **anextNode) -{ - Node *node = new (d->allocateNode(alignOfNode())) Node(akey, avalue, ah, *anextNode); - *anextNode = node; - ++d->size; - return node; -} +private: + template <typename ...Args> + iterator emplace_helper(Key &&key, Args &&... args) + { + auto result = d->findOrInsert(key); + if (!result.initialized) + Node::createInPlace(result.it.node(), std::move(key), std::forward<Args>(args)...); + else + result.it.node()->emplaceValue(std::forward<Args>(args)...); + return iterator(result.it); + } -template <class Key, class T> -Q_INLINE_TEMPLATE QHash<Key, T> &QHash<Key, T>::unite(const QHash &other) -{ - if (d == &QHashData::shared_null) { - *this = other; - } else { - QHash copy(other); - const_iterator it = copy.constEnd(); - while (it != copy.constBegin()) { - --it; - insertMulti(it.key(), it.value()); - } +public: +#ifdef __cpp_concepts + bool remove(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return removeImpl(key); } - return *this; -} + 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 <class Key, class T> -Q_OUTOFLINE_TEMPLATE void QHash<Key, T>::freeData(QHashData *x) -{ - x->free_helper(deleteNode2); -} -template <class Key, class T> -Q_INLINE_TEMPLATE void QHash<Key, T>::clear() +template <typename Key, typename T> +class QMultiHash { - *this = QHash(); -} + using Node = QHashPrivate::MultiNode<Key, T>; + using Data = QHashPrivate::Data<Node>; + using Chain = QHashPrivate::MultiNodeChain<T>; -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE void QHash<Key, T>::detach_helper() -{ - QHashData *x = d->detach_helper(duplicateNode, deleteNode2, sizeof(Node), alignOfNode()); - if (!d->ref.deref()) - freeData(d); - d = x; -} + Data *d = nullptr; + qsizetype m_size = 0; -template <class Key, class T> -Q_INLINE_TEMPLATE QHash<Key, T> &QHash<Key, T>::operator=(const QHash &other) -{ - if (d != other.d) { - QHashData *o = other.d; - o->ref.ref(); - if (!d->ref.deref()) - freeData(d); - d = o; - if (!d->sharable) - detach_helper(); +public: + using key_type = Key; + using mapped_type = T; + using value_type = T; + using size_type = qsizetype; + using difference_type = qsizetype; + using reference = T &; + using const_reference = const T &; + + QMultiHash() noexcept = default; + inline QMultiHash(std::initializer_list<std::pair<Key,T> > list) + : d(new Data(list.size())) + { + for (typename std::initializer_list<std::pair<Key,T> >::const_iterator it = list.begin(); it != list.end(); ++it) + insert(it->first, it->second); + } +#ifdef Q_QDOC + template <typename InputIterator> + QMultiHash(InputIterator f, InputIterator l); +#else + template <typename InputIterator, QtPrivate::IfAssociativeIteratorHasKeyAndValue<InputIterator> = true> + QMultiHash(InputIterator f, InputIterator l) + { + QtPrivate::reserveIfForwardIterator(this, f, l); + for (; f != l; ++f) + insert(f.key(), f.value()); } - return *this; -} -template <class Key, class T> -Q_INLINE_TEMPLATE const T QHash<Key, T>::value(const Key &akey) const -{ - Node *node; - if (d->size == 0 || (node = *findNode(akey)) == e) { - return T(); - } else { - return node->value; + template <typename InputIterator, QtPrivate::IfAssociativeIteratorHasFirstAndSecond<InputIterator> = true> + QMultiHash(InputIterator f, InputIterator l) + { + QtPrivate::reserveIfForwardIterator(this, f, l); + for (; f != l; ++f) + insert(f->first, f->second); } -} +#endif + QMultiHash(const QMultiHash &other) noexcept + : d(other.d), m_size(other.m_size) + { + if (d) + d->ref.ref(); + } + ~QMultiHash() + { + static_assert(std::is_nothrow_destructible_v<Key>, "Types with throwing destructors are not supported in Qt containers."); + static_assert(std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers."); -template <class Key, class T> -Q_INLINE_TEMPLATE const T QHash<Key, T>::value(const Key &akey, const T &adefaultValue) const -{ - Node *node; - if (d->size == 0 || (node = *findNode(akey)) == e) { - return adefaultValue; - } else { - return node->value; + if (d && !d->ref.deref()) + delete d; } -} -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE QList<Key> QHash<Key, T>::uniqueKeys() const -{ - QList<Key> res; - res.reserve(size()); // May be too much, but assume short lifetime - const_iterator i = begin(); - if (i != end()) { - for (;;) { - const Key &aKey = i.key(); - res.append(aKey); - do { - if (++i == end()) - goto break_out_of_outer_loop; - } while (aKey == i.key()); + QMultiHash &operator=(const QMultiHash &other) noexcept(std::is_nothrow_destructible<Node>::value) + { + if (d != other.d) { + Data *o = other.d; + if (o) + o->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = o; + m_size = other.m_size; } + return *this; + } + QMultiHash(QMultiHash &&other) noexcept + : d(std::exchange(other.d, nullptr)), + m_size(std::exchange(other.m_size, 0)) + { + } + QMultiHash &operator=(QMultiHash &&other) noexcept(std::is_nothrow_destructible<Node>::value) + { + QMultiHash moved(std::move(other)); + swap(moved); + return *this; } -break_out_of_outer_loop: - return res; -} -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE QList<Key> QHash<Key, T>::keys() const -{ - QList<Key> res; - res.reserve(size()); - const_iterator i = begin(); - while (i != end()) { - res.append(i.key()); - ++i; - } - return res; -} + explicit QMultiHash(const QHash<Key, T> &other) + : QMultiHash(other.begin(), other.end()) + {} -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE QList<Key> QHash<Key, T>::keys(const T &avalue) const -{ - QList<Key> res; - const_iterator i = begin(); - while (i != end()) { - if (i.value() == avalue) - res.append(i.key()); - ++i; - } - return res; -} + explicit QMultiHash(QHash<Key, T> &&other) + { + unite(std::move(other)); + } -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE const Key QHash<Key, T>::key(const T &avalue) const -{ - return key(avalue, Key()); -} + void swap(QMultiHash &other) noexcept + { + qt_ptr_swap(d, other.d); + std::swap(m_size, other.m_size); + } -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE const Key QHash<Key, T>::key(const T &avalue, const Key &defaultValue) const -{ - const_iterator i = begin(); - while (i != end()) { - if (i.value() == avalue) - return i.key(); - ++i; +#ifndef Q_QDOC + template <typename AKey = Key, typename AT = T> + QTypeTraits::compare_eq_result_container<QMultiHash, AKey, AT> operator==(const QMultiHash &other) const noexcept + { + if (d == other.d) + return true; + if (m_size != other.m_size) + return false; + if (m_size == 0) + return true; + // equal size, and both non-zero size => d pointers allocated for both + Q_ASSERT(d); + Q_ASSERT(other.d); + if (d->size != other.d->size) + return false; + for (auto it = other.d->begin(); it != other.d->end(); ++it) { + auto *n = d->findNode(it.node()->key); + if (!n) + return false; + Chain *e = it.node()->value; + while (e) { + Chain *oe = n->value; + while (oe) { + if (oe->value == e->value) + break; + oe = oe->next; + } + if (!oe) + return false; + e = e->next; + } + } + // all values must be the same as size is the same + return true; } + template <typename AKey = Key, typename AT = T> + QTypeTraits::compare_eq_result_container<QMultiHash, AKey, AT> operator!=(const QMultiHash &other) const noexcept + { return !(*this == other); } +#else + bool operator==(const QMultiHash &other) const; + bool operator!=(const QMultiHash &other) const; +#endif // Q_QDOC - return defaultValue; -} + inline qsizetype size() const noexcept { return m_size; } -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE QList<T> QHash<Key, T>::values() const -{ - QList<T> res; - res.reserve(size()); - const_iterator i = begin(); - while (i != end()) { - res.append(i.value()); - ++i; - } - return res; -} + inline bool isEmpty() const noexcept { return !m_size; } -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE QList<T> QHash<Key, T>::values(const Key &akey) const -{ - QList<T> res; - Node *node = *findNode(akey); - if (node != e) { - do { - res.append(node->value); - } while ((node = node->next) != e && node->key == akey); - } - return res; -} + inline qsizetype capacity() const noexcept { return d ? qsizetype(d->numBuckets >> 1) : 0; } + void reserve(qsizetype size) + { + // reserve(0) is used in squeeze() + if (size && (this->capacity() >= size)) + return; + if (isDetached()) + d->rehash(size); + else + d = Data::detached(d, size_t(size)); + } + inline void squeeze() { reserve(0); } -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE int QHash<Key, T>::count(const Key &akey) const -{ - int cnt = 0; - Node *node = *findNode(akey); - if (node != e) { - do { - ++cnt; - } while ((node = node->next) != e && node->key == akey); - } - return cnt; -} + inline void detach() { if (!d || d->ref.isShared()) d = Data::detached(d); } + inline bool isDetached() const noexcept { return d && !d->ref.isShared(); } + bool isSharedWith(const QMultiHash &other) const noexcept { return d == other.d; } -template <class Key, class T> -Q_INLINE_TEMPLATE const T QHash<Key, T>::operator[](const Key &akey) const -{ - return value(akey); -} + void clear() noexcept(std::is_nothrow_destructible<Node>::value) + { + if (d && !d->ref.deref()) + delete d; + d = nullptr; + m_size = 0; + } -template <class Key, class T> -Q_INLINE_TEMPLATE T &QHash<Key, T>::operator[](const Key &akey) -{ - detach(); + 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); + size_t bucket = it.toBucketIndex(d); + detach(); + it = typename Data::Bucket(d, bucket); // reattach in case of detach + + if (it.isUnused()) + return 0; + qsizetype n = Node::freeChain(it.node()); + m_size -= n; + Q_ASSERT(m_size >= 0); + d->erase(it); + return n; + } - uint h; - Node **node = findNode(akey, &h); - if (*node == e) { - if (d->willGrow()) - node = findNode(akey, h); - return createNode(h, akey, T(), node)->value; +public: + template <typename Predicate> + qsizetype removeIf(Predicate pred) + { + return QtPrivate::associative_erase_if(*this, pred); } - return (*node)->value; -} -template <class Key, class T> -Q_INLINE_TEMPLATE typename QHash<Key, T>::iterator QHash<Key, T>::insert(const Key &akey, - const T &avalue) -{ - detach(); + 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); + size_t bucket = it.toBucketIndex(d); + detach(); + it = typename Data::Bucket(d, bucket); // reattach in case of detach + + if (it.isUnused()) + return T(); + Chain *e = it.node()->value; + Q_ASSERT(e); + T t = std::move(e->value); + if (e->next) { + it.node()->value = e->next; + delete e; + } else { + // erase() deletes the values. + d->erase(it); + } + --m_size; + Q_ASSERT(m_size >= 0); + return t; + } - uint h; - Node **node = findNode(akey, &h); - if (*node == e) { - if (d->willGrow()) - node = findNode(akey, h); - return iterator(createNode(h, akey, avalue, node)); +public: + bool contains(const Key &key) const noexcept + { + if (!d) + return false; + return d->findNode(key) != nullptr; } - if (!std::is_same<T, QHashDummyValue>::value) - (*node)->value = avalue; - return iterator(*node); -} +private: + 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; + ++i; + } + } -template <class Key, class T> -Q_INLINE_TEMPLATE typename QHash<Key, T>::iterator QHash<Key, T>::insertMulti(const Key &akey, - const T &avalue) -{ - detach(); - d->willGrow(); + return defaultValue(); + } +public: + Key key(const T &value) const noexcept + { + return keyImpl(value, [] { return Key(); }); + } + Key key(const T &value, const Key &defaultKey) const noexcept + { + return keyImpl(value, [&] { return defaultKey; }); + } - uint h; - Node **nextNode = findNode(akey, &h); - return iterator(createNode(h, akey, avalue, nextNode)); -} +private: + 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 defaultValue(); + } +public: + T value(const Key &key) const noexcept + { + return valueImpl(key, [] { return T(); }); + } + T value(const Key &key, const T &defaultValue) const noexcept + { + return valueImpl(key, [&] { return defaultValue; }); + } -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE int QHash<Key, T>::remove(const Key &akey) -{ - if (isEmpty()) // prevents detaching shared null - return 0; - detach(); - - int oldSize = d->size; - Node **node = findNode(akey); - if (*node != e) { - bool deleteNext = true; - do { - Node *next = (*node)->next; - deleteNext = (next != e && next->key == (*node)->key); - deleteNode(*node); - *node = next; - --d->size; - } while (deleteNext); - d->hasShrunk(); - } - return oldSize - d->size; -} + 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(key), T()); + ++m_size; + } + return result.it.node()->value->value; + } -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE T QHash<Key, T>::take(const Key &akey) -{ - if (isEmpty()) // prevents detaching shared null - return T(); - detach(); - - Node **node = findNode(akey); - if (*node != e) { - T t = std::move((*node)->value); - Node *next = (*node)->next; - deleteNode(*node); - *node = next; - --d->size; - d->hasShrunk(); - return t; +public: + const T operator[](const Key &key) const noexcept + { + return value(key); } - return T(); -} -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE typename QHash<Key, T>::iterator QHash<Key, T>::erase(const_iterator it) -{ - Q_ASSERT_X(isValidIterator(it), "QHash::erase", "The specified iterator argument 'it' is invalid"); - - if (it == const_iterator(e)) - return iterator(it.i); - - if (d->ref.isShared()) { - // save 'it' across the detach: - int bucketNum = (it.i->h % d->numBuckets); - const_iterator bucketIterator(*(d->buckets + bucketNum)); - int stepsFromBucketStartToIte = 0; - while (bucketIterator != it) { - ++stepsFromBucketStartToIte; - ++bucketIterator; + QList<Key> uniqueKeys() const + { + QList<Key> res; + if (d) { + auto i = d->begin(); + while (i != d->end()) { + res.append(i.node()->key); + ++i; + } } - detach(); - it = const_iterator(*(d->buckets + bucketNum)); - while (stepsFromBucketStartToIte > 0) { - --stepsFromBucketStartToIte; - ++it; + return res; + } + + QList<Key> keys() const { return QList<Key>(keyBegin(), keyEnd()); } + QList<Key> keys(const T &value) const + { + QList<Key> res; + const_iterator i = begin(); + while (i != end()) { + if (i.value() == value) + res.append(i.key()); + ++i; } + return res; } - iterator ret(it.i); - ++ret; + 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); + if (n) { + Chain *e = n->value; + while (e) { + values.append(e->value); + e = e->next; + } + } + } + return values; + } - Node *node = concrete(it.i); - Node **node_ptr = reinterpret_cast<Node **>(&d->buckets[node->h % d->numBuckets]); - while (*node_ptr != node) - node_ptr = &(*node_ptr)->next; - *node_ptr = node->next; - deleteNode(node); - --d->size; - return ret; -} +public: + class const_iterator; -template <class Key, class T> -Q_INLINE_TEMPLATE void QHash<Key, T>::reserve(int asize) -{ - detach(); - d->rehash(-qMax(asize, 1)); -} + class iterator + { + using piter = typename QHashPrivate::iterator<Node>; + friend class const_iterator; + friend class QMultiHash<Key, T>; + piter i; + Chain **e = nullptr; + explicit inline iterator(piter it, Chain **entry = nullptr) noexcept : i(it), e(entry) + { + if (!it.atEnd() && !e) { + e = &it.node()->value; + Q_ASSERT(e && *e); + } + } -template <class Key, class T> -Q_INLINE_TEMPLATE typename QHash<Key, T>::const_iterator QHash<Key, T>::find(const Key &akey) const -{ - return const_iterator(*findNode(akey)); -} + public: + typedef std::forward_iterator_tag iterator_category; + typedef qptrdiff difference_type; + typedef T value_type; + typedef T *pointer; + typedef T &reference; -template <class Key, class T> -Q_INLINE_TEMPLATE typename QHash<Key, T>::const_iterator QHash<Key, T>::constFind(const Key &akey) const -{ - return const_iterator(*findNode(akey)); -} + constexpr iterator() noexcept = default; + + inline const Key &key() const noexcept { return i.node()->key; } + inline T &value() const noexcept { return (*e)->value; } + inline T &operator*() const noexcept { return (*e)->value; } + inline T *operator->() const noexcept { return &(*e)->value; } + inline bool operator==(const iterator &o) const noexcept { return e == o.e; } + inline bool operator!=(const iterator &o) const noexcept { return e != o.e; } + + inline iterator &operator++() noexcept { + Q_ASSERT(e && *e); + e = &(*e)->next; + Q_ASSERT(e); + if (!*e) { + ++i; + e = i.atEnd() ? nullptr : &i.node()->value; + } + return *this; + } + inline iterator operator++(int) noexcept { + iterator r = *this; + ++(*this); + return r; + } -template <class Key, class T> -Q_INLINE_TEMPLATE typename QHash<Key, T>::iterator QHash<Key, T>::find(const Key &akey) -{ - detach(); - return iterator(*findNode(akey)); -} + inline bool operator==(const const_iterator &o) const noexcept { return e == o.e; } + inline bool operator!=(const const_iterator &o) const noexcept { return e != o.e; } + }; + friend class iterator; -template <class Key, class T> -Q_INLINE_TEMPLATE bool QHash<Key, T>::contains(const Key &akey) const -{ - return *findNode(akey) != e; -} + class const_iterator + { + using piter = typename QHashPrivate::iterator<Node>; + friend class iterator; + friend class QMultiHash<Key, T>; + piter i; + Chain **e = nullptr; + explicit inline const_iterator(piter it, Chain **entry = nullptr) noexcept : i(it), e(entry) + { + if (!it.atEnd() && !e) { + e = &it.node()->value; + Q_ASSERT(e && *e); + } + } -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE typename QHash<Key, T>::Node **QHash<Key, T>::findNode(const Key &akey, uint h) const -{ - Node **node; + public: + typedef std::forward_iterator_tag iterator_category; + typedef qptrdiff difference_type; + typedef T value_type; + typedef const T *pointer; + typedef const T &reference; - if (d->numBuckets) { - node = reinterpret_cast<Node **>(&d->buckets[h % d->numBuckets]); - Q_ASSERT(*node == e || (*node)->next); - while (*node != e && !(*node)->same_key(h, akey)) - node = &(*node)->next; - } else { - node = const_cast<Node **>(reinterpret_cast<const Node * const *>(&e)); - } - return node; -} + constexpr const_iterator() noexcept = default; + inline const_iterator(const iterator &o) noexcept : i(o.i), e(o.e) { } + + inline const Key &key() const noexcept { return i.node()->key; } + inline T &value() const noexcept { return (*e)->value; } + inline T &operator*() const noexcept { return (*e)->value; } + inline T *operator->() const noexcept { return &(*e)->value; } + inline bool operator==(const const_iterator &o) const noexcept { return e == o.e; } + inline bool operator!=(const const_iterator &o) const noexcept { return e != o.e; } + + inline const_iterator &operator++() noexcept { + Q_ASSERT(e && *e); + e = &(*e)->next; + Q_ASSERT(e); + if (!*e) { + ++i; + e = i.atEnd() ? nullptr : &i.node()->value; + } + return *this; + } + inline const_iterator operator++(int) noexcept + { + const_iterator r = *this; + ++(*this); + return r; + } + }; + friend class const_iterator; -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE typename QHash<Key, T>::Node **QHash<Key, T>::findNode(const Key &akey, - uint *ahp) const -{ - uint h = 0; + class key_iterator + { + const_iterator i; - if (d->numBuckets || ahp) { - h = qHash(akey, d->seed); - if (ahp) - *ahp = h; - } - return findNode(akey, h); -} + public: + typedef typename const_iterator::iterator_category iterator_category; + typedef qptrdiff difference_type; + typedef Key value_type; + typedef const Key *pointer; + typedef const Key &reference; -template <class Key, class T> -Q_OUTOFLINE_TEMPLATE bool QHash<Key, T>::operator==(const QHash &other) const -{ - if (d == other.d) - return true; - if (size() != other.size()) - return false; + key_iterator() noexcept = default; + explicit key_iterator(const_iterator o) noexcept : i(o) { } - const_iterator it = begin(); + const Key &operator*() const noexcept { return i.key(); } + const Key *operator->() const noexcept { return &i.key(); } + bool operator==(key_iterator o) const noexcept { return i == o.i; } + bool operator!=(key_iterator o) const noexcept { return i != o.i; } - while (it != end()) { - // Build two equal ranges for i.key(); one for *this and one for other. - // For *this we can avoid a lookup via equal_range, as we know the beginning of the range. - auto thisEqualRangeStart = it; - const Key &thisEqualRangeKey = it.key(); - size_type n = 0; - do { - ++it; - ++n; - } while (it != end() && it.key() == thisEqualRangeKey); + inline key_iterator &operator++() noexcept { ++i; return *this; } + inline key_iterator operator++(int) noexcept { return key_iterator(i++);} + const_iterator base() const noexcept { return i; } + }; - const auto otherEqualRange = other.equal_range(thisEqualRangeKey); + typedef QKeyValueIterator<const Key&, const T&, const_iterator> const_key_value_iterator; + typedef QKeyValueIterator<const Key&, T&, iterator> key_value_iterator; - if (n != std::distance(otherEqualRange.first, otherEqualRange.second)) - return false; + // STL style + inline iterator begin() { detach(); return iterator(d->begin()); } + inline const_iterator begin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator cbegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator constBegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline iterator end() noexcept { return iterator(); } + inline const_iterator end() const noexcept { return const_iterator(); } + inline const_iterator cend() const noexcept { return const_iterator(); } + inline const_iterator constEnd() const noexcept { return const_iterator(); } + inline key_iterator keyBegin() const noexcept { return key_iterator(begin()); } + inline key_iterator keyEnd() const noexcept { return key_iterator(end()); } + inline key_value_iterator keyValueBegin() noexcept { return key_value_iterator(begin()); } + inline key_value_iterator keyValueEnd() noexcept { return key_value_iterator(end()); } + inline const_key_value_iterator keyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator constKeyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator keyValueEnd() const noexcept { return const_key_value_iterator(end()); } + inline const_key_value_iterator constKeyValueEnd() const noexcept { return const_key_value_iterator(end()); } + auto asKeyValueRange() & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() const & { return QtPrivate::QKeyValueRange(*this); } + auto asKeyValueRange() && { return QtPrivate::QKeyValueRange(std::move(*this)); } + auto asKeyValueRange() const && { return QtPrivate::QKeyValueRange(std::move(*this)); } + + iterator detach(const_iterator it) + { + auto i = it.i; + Chain **e = it.e; + if (d->ref.isShared()) { + // need to store iterator position before detaching + qsizetype n = 0; + Chain *entry = i.node()->value; + while (entry != *it.e) { + ++n; + entry = entry->next; + } + Q_ASSERT(entry); + detach_helper(); - // Keys in the ranges are equal by construction; this checks only the values. - if (!qt_is_permutation(thisEqualRangeStart, it, otherEqualRange.first, otherEqualRange.second)) - return false; + i = d->detachedIterator(i); + e = &i.node()->value; + while (n) { + e = &(*e)->next; + --n; + } + Q_ASSERT(e && *e); + } + return iterator(i, e); } - return true; -} - -template <class Key, class T> -QPair<typename QHash<Key, T>::iterator, typename QHash<Key, T>::iterator> QHash<Key, T>::equal_range(const Key &akey) -{ - detach(); - auto pair = qAsConst(*this).equal_range(akey); - return qMakePair(iterator(pair.first.i), iterator(pair.second.i)); -} + iterator erase(const_iterator it) + { + Q_ASSERT(d); + iterator iter = detach(it); + iterator i = iter; + Chain *e = *i.e; + Chain *next = e->next; + *i.e = next; + delete e; + if (!next) { + if (i.e == &i.i.node()->value) { + // last remaining entry, erase + typename Data::Bucket bucket(i.i); + d->erase(bucket); + if (bucket.toBucketIndex(d) == d->numBuckets - 1 || bucket.isUnused()) + i = iterator(++iter.i); + else // 'i' currently has a nullptr chain. So, we must recreate it + i = iterator(bucket.toIterator(d)); + } else { + i = iterator(++iter.i); + } + } + --m_size; + Q_ASSERT(m_size >= 0); + return i; + } -template <class Key, class T> -QPair<typename QHash<Key, T>::const_iterator, typename QHash<Key, T>::const_iterator> QHash<Key, T>::equal_range(const Key &akey) const noexcept -{ - Node *node = *findNode(akey); - const_iterator firstIt = const_iterator(node); + // more Qt + typedef iterator Iterator; + typedef const_iterator ConstIterator; + inline qsizetype count() const noexcept { return size(); } - if (node != e) { - // equal keys must hash to the same value and so they all - // end up in the same bucket. So we can use node->next, - // which only works within a bucket, instead of (out-of-line) - // QHashData::nextNode() - while (node->next != e && node->next->key == akey) - node = node->next; +private: + template <typename K> iterator findImpl(const K &key) + { + if (isEmpty()) + return end(); + auto it = d->findBucket(key); + size_t bucket = it.toBucketIndex(d); + detach(); + it = typename Data::Bucket(d, bucket); // reattach in case of detach - // 'node' may be the last node in the bucket. To produce the end iterator, we'd - // need to enter the next bucket in this case, so we need to use - // QHashData::nextNode() here, which, unlike node->next above, can move between - // buckets. - node = concrete(QHashData::nextNode(reinterpret_cast<QHashData::Node *>(node))); + if (it.isUnused()) + return end(); + return iterator(it.toIterator(d)); + } + template <typename K> const_iterator constFindImpl(const K &key) const noexcept + { + if (isEmpty()) + return end(); + auto it = d->findBucket(key); + if (it.isUnused()) + return constEnd(); + return const_iterator(it.toIterator(d)); } - - return qMakePair(firstIt, const_iterator(node)); -} - -template <class Key, class T> -class QMultiHash : public QHash<Key, T> -{ public: - QMultiHash() noexcept {} - inline QMultiHash(std::initializer_list<std::pair<Key,T> > list) + iterator find(const Key &key) { - this->reserve(int(list.size())); - for (typename std::initializer_list<std::pair<Key,T> >::const_iterator it = list.begin(); it != list.end(); ++it) - insert(it->first, it->second); + return findImpl(key); } -#ifdef Q_QDOC - template <typename InputIterator> - QMultiHash(InputIterator f, InputIterator l); -#else - template <typename InputIterator, QtPrivate::IfAssociativeIteratorHasKeyAndValue<InputIterator> = true> - QMultiHash(InputIterator f, InputIterator l) + const_iterator constFind(const Key &key) const noexcept { - QtPrivate::reserveIfForwardIterator(this, f, l); - for (; f != l; ++f) - insert(f.key(), f.value()); + return constFindImpl(key); + } + const_iterator find(const Key &key) const noexcept + { + return constFindImpl(key); } - template <typename InputIterator, QtPrivate::IfAssociativeIteratorHasFirstAndSecond<InputIterator> = true> - QMultiHash(InputIterator f, InputIterator l) + iterator insert(const Key &key, const T &value) { - QtPrivate::reserveIfForwardIterator(this, f, l); - for (; f != l; ++f) - insert(f->first, f->second); + return emplace(key, value); + } + + template <typename ...Args> + iterator emplace(const Key &key, Args &&... args) + { + return emplace(Key(key), std::forward<Args>(args)...); + } + + template <typename ...Args> + iterator emplace(Key &&key, Args &&... args) + { + if (isDetached()) { + if (d->shouldGrow()) // Construct the value now so that no dangling references are used + return emplace_helper(std::move(key), T(std::forward<Args>(args)...)); + return emplace_helper(std::move(key), std::forward<Args>(args)...); + } + // else: we must detach + const auto copy = *this; // keep 'args' alive across the detach/growth + detach(); + return emplace_helper(std::move(key), std::forward<Args>(args)...); } -#endif - // compiler-generated copy/move ctors/assignment operators are fine! - // compiler-generated destructor is fine! - QMultiHash(const QHash<Key, T> &other) : QHash<Key, T>(other) {} - QMultiHash(QHash<Key, T> &&other) noexcept : QHash<Key, T>(std::move(other)) {} - void swap(QMultiHash &other) noexcept { QHash<Key, T>::swap(other); } // prevent QMultiHash<->QHash swaps - inline typename QHash<Key, T>::iterator replace(const Key &key, const T &value) - { return QHash<Key, T>::insert(key, value); } + float load_factor() const noexcept { return d ? d->loadFactor() : 0; } + static float max_load_factor() noexcept { return 0.5; } + size_t bucket_count() const noexcept { return d ? d->numBuckets : 0; } + static size_t max_bucket_count() noexcept { return Data::maxNumBuckets(); } + + inline bool empty() const noexcept { return isEmpty(); } + + inline iterator replace(const Key &key, const T &value) + { + return emplaceReplace(key, value); + } + + template <typename ...Args> + iterator emplaceReplace(const Key &key, Args &&... args) + { + return emplaceReplace(Key(key), std::forward<Args>(args)...); + } - inline typename QHash<Key, T>::iterator insert(const Key &key, const T &value) - { return QHash<Key, T>::insertMulti(key, value); } + template <typename ...Args> + iterator emplaceReplace(Key &&key, Args &&... args) + { + if (isDetached()) { + if (d->shouldGrow()) // Construct the value now so that no dangling references are used + return emplaceReplace_helper(std::move(key), T(std::forward<Args>(args)...)); + return emplaceReplace_helper(std::move(key), std::forward<Args>(args)...); + } + // else: we must detach + const auto copy = *this; // keep 'args' alive across the detach/growth + detach(); + return emplaceReplace_helper(std::move(key), std::forward<Args>(args)...); + } inline QMultiHash &operator+=(const QMultiHash &other) { this->unite(other); return *this; } inline QMultiHash operator+(const QMultiHash &other) const { QMultiHash result = *this; result += other; return result; } - using QHash<Key, T>::contains; - using QHash<Key, T>::remove; - using QHash<Key, T>::count; - using QHash<Key, T>::find; - using QHash<Key, T>::constFind; + 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); + if (n == nullptr) + return false; + return n->value->contains(value); + } - bool contains(const Key &key, const T &value) const; +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); + size_t bucket = it.toBucketIndex(d); + detach(); + it = typename Data::Bucket(d, bucket); // reattach in case of detach + + if (it.isUnused()) + return 0; + qsizetype n = 0; + Chain **e = &it.node()->value; + while (*e) { + Chain *entry = *e; + if (entry->value == value) { + *e = entry->next; + delete entry; + ++n; + } else { + e = &entry->next; + } + } + if (!it.node()->value) + d->erase(it); + m_size -= n; + Q_ASSERT(m_size >= 0); + return n; + } - int remove(const Key &key, const T &value); +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); + if (it.isUnused()) + return 0; + qsizetype n = 0; + Chain *e = it.node()->value; + while (e) { + ++n; + e = e->next; + } - int count(const Key &key, const T &value) const; + return n; + } - typename QHash<Key, T>::iterator find(const Key &key, const T &value) { - typename QHash<Key, T>::iterator i(find(key)); - typename QHash<Key, T>::iterator end(this->end()); - while (i != end && i.key() == key) { - if (i.value() == value) - return i; - ++i; +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); + if (it.isUnused()) + return 0; + qsizetype n = 0; + Chain *e = it.node()->value; + while (e) { + if (e->value == value) + ++n; + e = e->next; } - return end; + + return n; } - typename QHash<Key, T>::const_iterator find(const Key &key, const T &value) const { - typename QHash<Key, T>::const_iterator i(constFind(key)); - typename QHash<Key, T>::const_iterator end(QHash<Key, T>::constEnd()); + + template <typename K> iterator findImpl(const K &key, const T &value) + { + if (isEmpty()) + return end(); + const auto copy = isDetached() ? QMultiHash() : *this; // keep 'key'/'value' alive across the detach + detach(); + auto it = constFind(key, value); + return iterator(it.i, it.e); + } + template <typename K> const_iterator constFindImpl(const K &key, const T &value) const noexcept + { + const_iterator i(constFind(key)); + const_iterator end(constEnd()); while (i != end && i.key() == key) { if (i.value() == value) return i; @@ -1082,78 +2212,261 @@ public: } return end; } - typename QHash<Key, T>::const_iterator constFind(const Key &key, const T &value) const - { return find(key, value); } + +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()) { + *this = other; + } else if (other.isEmpty()) { + ; + } else { + QMultiHash copy(other); + detach(); + for (auto cit = copy.cbegin(); cit != copy.cend(); ++cit) + insert(cit.key(), *cit); + } + return *this; + } + + QMultiHash &unite(const QHash<Key, T> &other) + { + for (auto cit = other.cbegin(); cit != other.cend(); ++cit) + insert(cit.key(), *cit); + return *this; + } + + QMultiHash &unite(QHash<Key, T> &&other) + { + if (!other.isDetached()) { + unite(other); + return *this; + } + auto it = other.d->begin(); + for (const auto end = other.d->end(); it != end; ++it) + emplace(std::move(it.node()->key), std::move(it.node()->takeValue())); + other.clear(); + return *this; + } + + std::pair<iterator, iterator> equal_range(const Key &key) + { + return equal_range_impl(key); + } private: - T &operator[](const Key &key); - const T operator[](const Key &key) const; -}; + 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 {iterator(pair.first.i), iterator(pair.second.i)}; + } -template <class Key, class T> -Q_INLINE_TEMPLATE bool QMultiHash<Key, T>::contains(const Key &key, const T &value) const -{ - return constFind(key, value) != QHash<Key, T>::constEnd(); -} +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 {end(), end()}; + + auto bucket = d->findBucket(key); + if (bucket.isUnused()) + return {end(), end()}; + auto it = bucket.toIterator(d); + auto end = it; + ++end; + return {const_iterator(it), const_iterator(end)}; + } -template <class Key, class T> -Q_INLINE_TEMPLATE int QMultiHash<Key, T>::remove(const Key &key, const T &value) -{ - int n = 0; - typename QHash<Key, T>::iterator i(find(key)); - typename QHash<Key, T>::iterator end(QHash<Key, T>::end()); - while (i != end && i.key() == key) { - if (i.value() == value) { - i = this->erase(i); - ++n; + void detach_helper() + { + if (!d) { + d = new Data; + return; + } + Data *dd = new Data(*d); + if (!d->ref.deref()) + delete d; + d = dd; + } + + template<typename... Args> + iterator emplace_helper(Key &&key, Args &&...args) + { + auto result = d->findOrInsert(key); + if (!result.initialized) + Node::createInPlace(result.it.node(), std::move(key), std::forward<Args>(args)...); + else + result.it.node()->insertMulti(std::forward<Args>(args)...); + ++m_size; + return iterator(result.it); + } + + template<typename... Args> + iterator emplaceReplace_helper(Key &&key, Args &&...args) + { + auto result = d->findOrInsert(key); + if (!result.initialized) { + Node::createInPlace(result.it.node(), std::move(key), std::forward<Args>(args)...); + ++m_size; } else { - ++i; + result.it.node()->emplaceValue(std::forward<Args>(args)...); } + return iterator(result.it); } - return n; -} -template <class Key, class T> -Q_INLINE_TEMPLATE int QMultiHash<Key, T>::count(const Key &key, const T &value) const -{ - int n = 0; - typename QHash<Key, T>::const_iterator i(constFind(key)); - typename QHash<Key, T>::const_iterator end(QHash<Key, T>::constEnd()); - while (i != end && i.key() == key) { - if (i.value() == value) - ++n; - ++i; +public: +#ifdef __cpp_concepts + qsizetype remove(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) + { + return removeImpl(key); } - return n; -} + 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_ITERATOR(Hash) -Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR(Hash) +Q_DECLARE_ASSOCIATIVE_FORWARD_ITERATOR(Hash) +Q_DECLARE_MUTABLE_ASSOCIATIVE_FORWARD_ITERATOR(Hash) +Q_DECLARE_ASSOCIATIVE_FORWARD_ITERATOR(MultiHash) +Q_DECLARE_MUTABLE_ASSOCIATIVE_FORWARD_ITERATOR(MultiHash) template <class Key, class T> -uint qHash(const QHash<Key, T> &key, uint seed = 0) +size_t qHash(const QHash<Key, T> &key, size_t seed = 0) noexcept(noexcept(qHash(std::declval<Key&>())) && noexcept(qHash(std::declval<T&>()))) { - QtPrivate::QHashCombineCommutative hash; + size_t hash = 0; for (auto it = key.begin(), end = key.end(); it != end; ++it) { - const Key &k = it.key(); - const T &v = it.value(); - seed = hash(seed, std::pair<const Key&, const T&>(k, v)); + QtPrivate::QHashCombine combine; + size_t h = combine(seed, it.key()); + // use + to keep the result independent of the ordering of the keys + hash += combine(h, it.value()); } - return seed; + return hash; } template <class Key, class T> -inline uint qHash(const QMultiHash<Key, T> &key, uint seed = 0) +inline size_t qHash(const QMultiHash<Key, T> &key, size_t seed = 0) noexcept(noexcept(qHash(std::declval<Key&>())) && noexcept(qHash(std::declval<T&>()))) { - const QHash<Key, T> &key2 = key; - return qHash(key2, seed); + size_t hash = 0; + for (auto it = key.begin(), end = key.end(); it != end; ++it) { + QtPrivate::QHashCombine combine; + size_t h = combine(seed, it.key()); + // use + to keep the result independent of the ordering of the keys + hash += combine(h, it.value()); + } + return hash; } -QT_END_NAMESPACE +template <typename Key, typename T, typename Predicate> +qsizetype erase_if(QHash<Key, T> &hash, Predicate pred) +{ + return QtPrivate::associative_erase_if(hash, pred); +} -#if defined(Q_CC_MSVC) -#pragma warning( pop ) -#endif +template <typename Key, typename T, typename Predicate> +qsizetype erase_if(QMultiHash<Key, T> &hash, Predicate pred) +{ + return QtPrivate::associative_erase_if(hash, pred); +} + +QT_END_NAMESPACE #endif // QHASH_H |