From b53c153a10261eb851385f4aa0afb663797fdeb9 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Fri, 2 Feb 2024 17:55:03 -0800 Subject: [PATCH] QHash: add support for heterogeneous key lookups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This implements support in QHash and QMultiHash for lookups of heterogeneous key types that produce the same hash value. This is implemented by duplicating each of the following functions into an overload on Key and one a template that is enable_if-constrained to a key type that meets the requirement: * contains * count * equals_range * find * operator[] (const only) * remove * take * value * values (QMultiHash) The non-const operator[] may insert into the hash, so it's not part of this commit. Change-Id: I664b9f014ffc48cbb49bfffd17b037852f0fd192 Reviewed-by: MÃ¥rten Nordheim --- src/corelib/tools/qhash.h | 287 +++++++++++++++++-- tests/auto/corelib/tools/qhash/tst_qhash.cpp | 210 ++++++++++++++ 2 files changed, 473 insertions(+), 24 deletions(-) diff --git a/src/corelib/tools/qhash.h b/src/corelib/tools/qhash.h index 781ea1a157..0ce33759d0 100644 --- a/src/corelib/tools/qhash.h +++ b/src/corelib/tools/qhash.h @@ -677,8 +677,10 @@ struct Data return size >= (numBuckets >> 1); } - Bucket findBucket(const Key &key) const noexcept + template Bucket findBucket(const K &key) const noexcept { + static_assert(std::is_same_v, K> || + QHashHeterogeneousSearch, K>::value); Q_ASSERT(numBuckets > 0); size_t hash = QHashPrivate::calculateHash(key, seed); Bucket bucket(this, GrowthPolicy::bucketForHash(numBuckets, hash)); @@ -697,7 +699,7 @@ struct Data } } - Node *findNode(const Key &key) const noexcept + template Node *findNode(const K &key) const noexcept { auto bucket = findBucket(key); if (bucket.isUnused()) @@ -954,6 +956,11 @@ public: } bool remove(const Key &key) + { + return removeImpl(key); + } +private: + template bool removeImpl(const K &key) { if (isEmpty()) // prevents detaching shared null return false; @@ -967,12 +974,20 @@ public: d->erase(it); return true; } + +public: template qsizetype removeIf(Predicate pred) { return QtPrivate::associative_erase_if(*this, pred); } + T take(const Key &key) + { + return takeImpl(key); + } +private: + template T takeImpl(const K &key) { if (isEmpty()) // prevents detaching shared null return T(); @@ -988,6 +1003,7 @@ public: return value; } +public: bool contains(const Key &key) const noexcept { if (!d) @@ -1025,7 +1041,7 @@ public: } private: - template T valueImpl(const Key &key, Fn &&defaultValue) const noexcept + template T valueImpl(const K &key, Fn &&defaultValue) const noexcept { if (d) { Node *n = d->findNode(key); @@ -1240,11 +1256,7 @@ private: return std::make_pair(first, second); } -public: - typedef iterator Iterator; - typedef const_iterator ConstIterator; - inline qsizetype count() const noexcept { return d ? qsizetype(d->size) : 0; } - iterator find(const Key &key) + template iterator findImpl(const K &key) { if (isEmpty()) // prevents detaching shared null return end(); @@ -1256,7 +1268,7 @@ public: return end(); return iterator(it.toIterator(d)); } - const_iterator find(const Key &key) const noexcept + template const_iterator constFindImpl(const K &key) const noexcept { if (isEmpty()) return end(); @@ -1265,6 +1277,19 @@ public: return end(); return const_iterator({d, it.toBucketIndex(d)}); } + +public: + typedef iterator Iterator; + typedef const_iterator ConstIterator; + inline qsizetype count() const noexcept { return d ? qsizetype(d->size) : 0; } + iterator find(const Key &key) + { + return findImpl(key); + } + const_iterator find(const Key &key) const noexcept + { + return constFindImpl(key); + } const_iterator constFind(const Key &key) const noexcept { return find(key); @@ -1328,8 +1353,61 @@ private: result.it.node()->emplaceValue(std::forward(args)...); return iterator(result.it); } -}; +public: +#ifdef __cpp_concepts + bool remove(const QHashPrivate::HeterogeneouslySearchableWith auto &key) + { + return removeImpl(key); + } + T take(const QHashPrivate::HeterogeneouslySearchableWith auto &key) + { + return takeImpl(key); + } + bool contains(const QHashPrivate::HeterogeneouslySearchableWith auto &key) + { + return d ? d->findNode(key) != nullptr : false; + } + qsizetype count(const QHashPrivate::HeterogeneouslySearchableWith auto &key) + { + return contains(key) ? 1 : 0; + } + T value(const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + return valueImpl(key, [] { return T(); }); + } + T value(const QHashPrivate::HeterogeneouslySearchableWith auto &key, const T &defaultValue) const noexcept + { + return valueImpl(key, [&] { return defaultValue; }); + } + const T operator[](const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + return value(key); + } + std::pair + equal_range(const QHashPrivate::HeterogeneouslySearchableWith auto &key) + { + return equal_range_impl(*this, key); + } + std::pair + equal_range(const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + return equal_range_impl(*this, key); + } + iterator find(const QHashPrivate::HeterogeneouslySearchableWith auto &key) + { + return findImpl(key); + } + const_iterator find(const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + return constFindImpl(key); + } + const_iterator constFind(const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + return find(key); + } +#endif // __cpp_concepts +}; template @@ -1506,6 +1584,11 @@ public: } qsizetype remove(const Key &key) + { + return removeImpl(key); + } +private: + template qsizetype removeImpl(const K &key) { if (isEmpty()) // prevents detaching shared null return 0; @@ -1522,12 +1605,20 @@ public: d->erase(it); return n; } + +public: template qsizetype removeIf(Predicate pred) { return QtPrivate::associative_erase_if(*this, pred); } + T take(const Key &key) + { + return takeImpl(key); + } +private: + template T takeImpl(const K &key) { if (isEmpty()) // prevents detaching shared null return T(); @@ -1553,6 +1644,7 @@ public: return t; } +public: bool contains(const Key &key) const noexcept { if (!d) @@ -1586,7 +1678,7 @@ public: } private: - template T valueImpl(const Key &key, Fn &&defaultValue) const noexcept + template T valueImpl(const K &key, Fn &&defaultValue) const noexcept { if (d) { Node *n = d->findNode(key); @@ -1650,8 +1742,14 @@ public: } return res; } + QList values() const { return QList(begin(), end()); } QList values(const Key &key) const + { + return valuesImpl(key); + } +private: + template QList valuesImpl(const K &key) const { QList values; if (d) { @@ -1667,6 +1765,7 @@ public: return values; } +public: class const_iterator; class iterator @@ -1878,7 +1977,9 @@ public: typedef iterator Iterator; typedef const_iterator ConstIterator; inline qsizetype count() const noexcept { return size(); } - iterator find(const Key &key) + +private: + template iterator findImpl(const K &key) { if (isEmpty()) return end(); @@ -1891,11 +1992,7 @@ public: return end(); return iterator(it.toIterator(d)); } - const_iterator find(const Key &key) const noexcept - { - return constFind(key); - } - const_iterator constFind(const Key &key) const noexcept + template const_iterator constFindImpl(const K &key) const noexcept { if (isEmpty()) return end(); @@ -1904,6 +2001,20 @@ public: return constEnd(); return const_iterator(it.toIterator(d)); } +public: + iterator find(const Key &key) + { + return findImpl(key); + } + const_iterator constFind(const Key &key) const noexcept + { + return constFindImpl(key); + } + const_iterator find(const Key &key) const noexcept + { + return constFindImpl(key); + } + iterator insert(const Key &key, const T &value) { return emplace(key, value); @@ -1968,6 +2079,11 @@ public: { QMultiHash result = *this; result += other; return result; } bool contains(const Key &key, const T &value) const noexcept + { + return containsImpl(key, value); + } +private: + template bool containsImpl(const K &key, const T &value) const noexcept { if (isEmpty()) return false; @@ -1977,7 +2093,13 @@ public: return n->value->contains(value); } +public: qsizetype remove(const Key &key, const T &value) + { + return removeImpl(key, value); + } +private: + template qsizetype removeImpl(const K &key, const T &value) { if (isEmpty()) // prevents detaching shared null return 0; @@ -2007,7 +2129,13 @@ public: return n; } +public: qsizetype count(const Key &key) const noexcept + { + return countImpl(key); + } +private: + template qsizetype countImpl(const K &key) const noexcept { if (!d) return 0; @@ -2024,7 +2152,13 @@ public: return n; } +public: qsizetype count(const Key &key, const T &value) const noexcept + { + return countImpl(key, value); + } +private: + template qsizetype countImpl(const K &key, const T &value) const noexcept { if (!d) return 0; @@ -2042,7 +2176,7 @@ public: return n; } - iterator find(const Key &key, const T &value) + template iterator findImpl(const K &key, const T &value) { if (isEmpty()) return end(); @@ -2051,11 +2185,7 @@ public: auto it = constFind(key, value); return iterator(it.i, it.e); } - const_iterator find(const Key &key, const T &value) const noexcept - { - return constFind(key, value); - } - const_iterator constFind(const Key &key, const T &value) const noexcept + template const_iterator constFindImpl(const K &key, const T &value) const noexcept { const_iterator i(constFind(key)); const_iterator end(constEnd()); @@ -2067,6 +2197,21 @@ public: return end; } +public: + iterator find(const Key &key, const T &value) + { + return findImpl(key, value); + } + + const_iterator constFind(const Key &key, const T &value) const noexcept + { + return constFindImpl(key, value); + } + const_iterator find(const Key &key, const T &value) const noexcept + { + return constFind(key, value); + } + QMultiHash &unite(const QMultiHash &other) { if (isEmpty()) { @@ -2103,6 +2248,11 @@ public: } std::pair equal_range(const Key &key) + { + return equal_range_impl(key); + } +private: + template std::pair equal_range_impl(const K &key) { const auto copy = isDetached() ? QMultiHash() : *this; // keep 'key' alive across the detach detach(); @@ -2110,7 +2260,13 @@ public: return {iterator(pair.first.i), iterator(pair.second.i)}; } +public: std::pair equal_range(const Key &key) const noexcept + { + return equal_range_impl(key); + } +private: + template std::pair equal_range_impl(const K &key) const noexcept { if (!d) return {end(), end()}; @@ -2124,7 +2280,6 @@ public: return {const_iterator(it), const_iterator(end)}; } -private: void detach_helper() { if (!d) { @@ -2161,6 +2316,90 @@ private: } return iterator(result.it); } + +public: +#ifdef __cpp_concepts + qsizetype remove(const QHashPrivate::HeterogeneouslySearchableWith auto &key) + { + return removeImpl(key); + } + T take(const QHashPrivate::HeterogeneouslySearchableWith auto &key) + { + return takeImpl(key); + } + bool contains(const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + if (!d) + return false; + return d->findNode(key) != nullptr; + } + T value(const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + return valueImpl(key, [] { return T(); }); + } + T value(const QHashPrivate::HeterogeneouslySearchableWith auto &key, const T &defaultValue) const noexcept + { + return valueImpl(key, [&] { return defaultValue; }); + } + const T operator[](const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + return value(key); + } + QList values(const QHashPrivate::HeterogeneouslySearchableWith auto &key) + { + return valuesImpl(key); + } + iterator find(const QHashPrivate::HeterogeneouslySearchableWith auto &key) + { + return findImpl(key); + } + const_iterator constFind(const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + return constFindImpl(key); + } + const_iterator find(const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + return constFindImpl(key); + } + bool contains(const QHashPrivate::HeterogeneouslySearchableWith auto &key, const T &value) const noexcept + { + return containsImpl(key, value); + } + qsizetype remove(const QHashPrivate::HeterogeneouslySearchableWith auto &key, const T &value) + { + return removeImpl(key, value); + } + qsizetype count(const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + return countImpl(key); + } + qsizetype count(const QHashPrivate::HeterogeneouslySearchableWith auto &key, const T &value) const noexcept + { + return countImpl(key, value); + } + iterator find(const QHashPrivate::HeterogeneouslySearchableWith auto &key, const T &value) + { + return findImpl(key, value); + } + const_iterator constFind(const QHashPrivate::HeterogeneouslySearchableWith auto &key, const T &value) const noexcept + { + return constFindImpl(key, value); + } + const_iterator find(const QHashPrivate::HeterogeneouslySearchableWith auto &key, const T &value) const noexcept + { + return constFind(key, value); + } + std::pair + equal_range(const QHashPrivate::HeterogeneouslySearchableWith auto &key) + { + return equal_range_impl(key); + } + std::pair + equal_range(const QHashPrivate::HeterogeneouslySearchableWith auto &key) const noexcept + { + return equal_range_impl(key); + } +#endif // __cpp_concepts }; Q_DECLARE_ASSOCIATIVE_FORWARD_ITERATOR(Hash) diff --git a/tests/auto/corelib/tools/qhash/tst_qhash.cpp b/tests/auto/corelib/tools/qhash/tst_qhash.cpp index e59ad4de4d..e31c22ab99 100644 --- a/tests/auto/corelib/tools/qhash/tst_qhash.cpp +++ b/tests/auto/corelib/tools/qhash/tst_qhash.cpp @@ -38,6 +38,11 @@ private slots: void qhash(); void take(); // copied from tst_QMap void operator_eq(); // slightly modified from tst_QMap + void heterogeneousSearch(); + void heterogeneousSearchConstKey(); + void heterogeneousSearchByteArray(); + void heterogeneousSearchString(); + void rehash_isnt_quadratic(); void dont_need_default_constructor(); void qmultihash_specific(); @@ -45,6 +50,10 @@ private slots: void qmultihash_qhash_rvalue_ref_unite(); void qmultihashUnite(); void qmultihashSize(); + void qmultihashHeterogeneousSearch(); + void qmultihashHeterogeneousSearchConstKey(); + void qmultihashHeterogeneousSearchByteArray(); + void qmultihashHeterogeneousSearchString(); void compare(); void compare2(); @@ -1158,6 +1167,187 @@ void tst_QHash::operator_eq() } } +#ifdef __cpp_concepts +struct HeterogeneousHashingType +{ + inline static int conversionCount = 0; + QString s; + + Q_IMPLICIT operator QString() const + { + ++conversionCount; + return s; + } + + // std::equality_comparable_with requires we be self-comparable too + friend bool operator==(const HeterogeneousHashingType &t1, const HeterogeneousHashingType &t2) = default; + + friend bool operator==(const QString &string, const HeterogeneousHashingType &tester) + { return tester.s == string; } + friend bool operator!=(const QString &string, const HeterogeneousHashingType &tester) + { return !(tester.s == string); } + + friend size_t qHash(const HeterogeneousHashingType &tester, size_t seed) + { return qHash(tester.s, seed); } +}; +QT_BEGIN_NAMESPACE +template <> struct QHashHeterogeneousSearch : std::true_type {}; +template <> struct QHashHeterogeneousSearch : std::true_type {}; +QT_END_NAMESPACE +static_assert(std::is_same_v>); +static_assert(std::equality_comparable_with); +static_assert(QHashPrivate::HeterogeneouslySearchableWith); +static_assert(QHashPrivate::HeterogeneouslySearchableWith); + +template struct HeterogeneousSearchTestHelper +{ + static void resetCounter() {} + static void checkCounter() {} +}; +template <> struct HeterogeneousSearchTestHelper +{ + static void resetCounter() + { + HeterogeneousHashingType::conversionCount = 0; + } + static void checkCounter() + { + QTest::setThrowOnFail(true); + auto scopeExit = qScopeGuard([] { QTest::setThrowOnFail(false); }); + QCOMPARE(HeterogeneousHashingType::conversionCount, 0); + } +}; +#else +using HeterogeneousHashingType = QString; +#endif + +template