QHash: support std::hash as hashing function
In addition (and as a fallback) from requiring qHash, add support for std::hash specializations. This catches two birds with one stone: 1) users of Qt can simply specialize std::hash for their datatypes, and use them in both QHash and stdlib unordered associative containers; 2) we get QHash support for any (stdlib) datatype that is hashable without having to overload qHash for them. [ChangeLog][QtCore][QHash] QHash, QMultiHash and QSet now support for key types anything that can be hashed via std::hash, instead of always requiring a qHash() overload. Change-Id: Ib5ecba86e4b376d318389500bd24883ac6534c5f Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Lars Knoll <lars.knoll@qt.io> Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>bb10
parent
5dab710b90
commit
81e893fb2d
|
|
@ -350,3 +350,13 @@ size_t qHash(const K &key);
|
|||
size_t qHash(K key, size_t seed);
|
||||
size_t qHash(const K &key, size_t seed);
|
||||
//! [32]
|
||||
|
||||
//! [33]
|
||||
namespace std {
|
||||
template <> struct hash<K>
|
||||
{
|
||||
// seed is optional
|
||||
size_t operator()(const K &key, size_t seed = 0) const;
|
||||
};
|
||||
}
|
||||
//! [33]
|
||||
|
|
|
|||
|
|
@ -1194,22 +1194,26 @@ size_t qHash(long double key, size_t seed) noexcept
|
|||
instead, store a QWidget *.
|
||||
|
||||
\target qHash
|
||||
\section2 The qHash() hashing function
|
||||
\section2 The hashing function
|
||||
|
||||
A QHash's key type has additional requirements other than being an
|
||||
assignable data type: it must provide operator==(), and there must also be
|
||||
a qHash() function in the type's namespace that returns a hash value for an
|
||||
argument of the key's type.
|
||||
a hashing function that returns a hash value for an argument of the
|
||||
key's type.
|
||||
|
||||
The qHash() function computes a numeric value based on a key. It
|
||||
The hashing function computes a numeric value based on a key. It
|
||||
can use any algorithm imaginable, as long as it always returns
|
||||
the same value if given the same argument. In other words, if
|
||||
\c{e1 == e2}, then \c{qHash(e1) == qHash(e2)} must hold as well.
|
||||
However, to obtain good performance, the qHash() function should
|
||||
\c{e1 == e2}, then \c{hash(e1) == hash(e2)} must hold as well.
|
||||
However, to obtain good performance, the hashing function should
|
||||
attempt to return different hash values for different keys to the
|
||||
largest extent possible.
|
||||
|
||||
For a key type \c{K}, the qHash function must have one of these signatures:
|
||||
A hashing function for a key type \c{K} may be provided in two
|
||||
different ways.
|
||||
|
||||
The first way is by having an overload of \c{qHash()} in \c{K}'s
|
||||
namespace. The \c{qHash()} function must have one of these signatures:
|
||||
|
||||
\snippet code/src_corelib_tools_qhash.cpp 32
|
||||
|
||||
|
|
@ -1220,6 +1224,20 @@ size_t qHash(long double key, size_t seed) noexcept
|
|||
the latter is used by QHash (note that you can simply define a
|
||||
two-arguments version, and use a default value for the seed parameter).
|
||||
|
||||
The second way to provide a hashing function is by specializing
|
||||
the \c{std::hash} class for the key type \c{K}, and providing a
|
||||
suitable function call operator for it:
|
||||
|
||||
\snippet code/src_corelib_tools_qhash.cpp 33
|
||||
|
||||
The seed argument has the same meaning as for \c{qHash()},
|
||||
and may be left out.
|
||||
|
||||
This second way allows to reuse the same hash function between
|
||||
QHash and the C++ Standard Library unordered associative containers.
|
||||
If both a \c{qHash()} overload and a \c{std::hash} specializations
|
||||
are provided for a type, then the \c{qHash()} overload is preferred.
|
||||
|
||||
Here's a partial list of the C++ and Qt types that can serve as keys in a
|
||||
QHash: any integer type (char, unsigned long, etc.), any pointer type,
|
||||
QChar, QString, and QByteArray. For all of these, the \c <QHash> header
|
||||
|
|
@ -1228,9 +1246,11 @@ size_t qHash(long double key, size_t seed) noexcept
|
|||
the documentation of each class.
|
||||
|
||||
If you want to use other types as the key, make sure that you provide
|
||||
operator==() and a qHash() implementation. The convenience qHashMulti()
|
||||
function can be used to implement qHash() for a custom type, where
|
||||
one usually wants to produce a hash value from multiple fields:
|
||||
operator==() and a hash implementation.
|
||||
|
||||
The convenience qHashMulti() function can be used to implement
|
||||
qHash() for a custom type, where one usually wants to produce a
|
||||
hash value from multiple fields:
|
||||
|
||||
Example:
|
||||
\snippet code/src_corelib_tools_qhash.cpp 13
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** 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>
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
|
|
@ -48,6 +49,7 @@
|
|||
#include <QtCore/qrefcount.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <functional> // for std::hash
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
|
@ -58,6 +60,46 @@ struct QHashDummyValue
|
|||
|
||||
namespace QHashPrivate {
|
||||
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
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 {
|
||||
static_assert(sizeof(T) == 0, "The key type must have a qHash overload or a std::hash specialization");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// QHash uses a power of two growth policy.
|
||||
namespace GrowthPolicy {
|
||||
inline constexpr size_t maxNumBuckets() noexcept
|
||||
|
|
@ -545,7 +587,7 @@ struct Data
|
|||
iterator find(const Key &key) const noexcept
|
||||
{
|
||||
Q_ASSERT(numBuckets > 0);
|
||||
size_t hash = qHash(key, seed);
|
||||
size_t hash = QHashPrivate::calculateHash(key, seed);
|
||||
size_t bucket = 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
|
||||
|
|
@ -614,7 +656,7 @@ struct Data
|
|||
size_t nextIndex = next & Span::LocalBucketMask;
|
||||
if (!spans[nextSpan].hasNode(nextIndex))
|
||||
break;
|
||||
size_t hash = qHash(spans[nextSpan].at(nextIndex).key, seed);
|
||||
size_t hash = QHashPrivate::calculateHash(spans[nextSpan].at(nextIndex).key, seed);
|
||||
size_t newBucket = GrowthPolicy::bucketForHash(numBuckets, hash);
|
||||
while (true) {
|
||||
if (newBucket == next) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
|
||||
class tst_QHash : public QObject
|
||||
{
|
||||
|
|
@ -76,6 +78,8 @@ private slots:
|
|||
|
||||
void badHashFunction();
|
||||
void hashOfHash();
|
||||
|
||||
void stdHash();
|
||||
};
|
||||
|
||||
struct IdentityTracker {
|
||||
|
|
@ -1883,5 +1887,87 @@ void tst_QHash::hashOfHash()
|
|||
(void)qHash(multiHash);
|
||||
}
|
||||
|
||||
template <bool HasQHash_>
|
||||
struct StdHashKeyType {
|
||||
static inline constexpr bool HasQHash = HasQHash_;
|
||||
static bool StdHashUsed;
|
||||
|
||||
int i;
|
||||
friend bool operator==(const StdHashKeyType &lhs, const StdHashKeyType &rhs)
|
||||
{ return lhs.i == rhs.i; }
|
||||
};
|
||||
|
||||
template <bool HasQHash>
|
||||
bool StdHashKeyType<HasQHash>::StdHashUsed = false;
|
||||
|
||||
namespace std {
|
||||
template <bool HasQHash> struct hash<StdHashKeyType<HasQHash>>
|
||||
{
|
||||
size_t operator()(const StdHashKeyType<HasQHash> &s, size_t seed = 0) const {
|
||||
StdHashKeyType<HasQHash>::StdHashUsed = true;
|
||||
return hash<int>()(s.i) ^ seed;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <bool HasQHash>
|
||||
std::enable_if_t<HasQHash, size_t>
|
||||
qHash(const StdHashKeyType<HasQHash> &s, size_t seed)
|
||||
{
|
||||
return qHash(s.i, seed);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void stdHashImpl()
|
||||
{
|
||||
QHash<T, int> hash;
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
hash.insert(T{i}, i);
|
||||
|
||||
QCOMPARE(hash.size(), 1000);
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
QCOMPARE(hash.value(T{i}, -1), i);
|
||||
|
||||
for (int i = 500; i < 1500; ++i)
|
||||
hash.insert(T{i}, i);
|
||||
|
||||
QCOMPARE(hash.size(), 1500);
|
||||
for (int i = 0; i < 1500; ++i)
|
||||
QCOMPARE(hash.value(T{i}, -1), i);
|
||||
|
||||
qsizetype count = 0;
|
||||
for (int i = -2000; i < 2000; ++i) {
|
||||
if (hash.contains(T{i}))
|
||||
++count;
|
||||
}
|
||||
QCOMPARE(count, 1500);
|
||||
QCOMPARE(T::StdHashUsed, !T::HasQHash);
|
||||
|
||||
|
||||
std::unordered_set<T> set;
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
set.insert(T{i});
|
||||
|
||||
for (int i = 500; i < 1500; ++i)
|
||||
set.insert(T{i});
|
||||
|
||||
QCOMPARE(set.size(), size_t(1500));
|
||||
count = 0;
|
||||
for (int i = -2000; i < 2000; ++i)
|
||||
count += qsizetype(set.count(T{i}));
|
||||
QCOMPARE(count, 1500);
|
||||
QVERIFY(T::StdHashUsed);
|
||||
}
|
||||
|
||||
void tst_QHash::stdHash()
|
||||
{
|
||||
stdHashImpl<StdHashKeyType<false>>();
|
||||
stdHashImpl<StdHashKeyType<true>>();
|
||||
|
||||
QSet<std::string> strings{ "a", "b", "c" };
|
||||
QVERIFY(strings.contains("a"));
|
||||
QVERIFY(!strings.contains("z"));
|
||||
}
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_QHash)
|
||||
#include "tst_qhash.moc"
|
||||
|
|
|
|||
Loading…
Reference in New Issue