qhashfunctions.h: add specializations of std::hash for some Qt types

We have a problem. Our types don't play well with the std unordered
containers, because they do not specialize std::hash. We therefore
force our users to come up with an implementation, hindering
interoperability, since any two developers are unlikely to come up
with compatible implementations. So combining libraries written by
different developers will result in ODR violations.

Now that we depend on C++11, and thus the presence of std::hash, we
still face the problem that the standard does not provide us with a
means to compose new hash functions out of old ones. In particular, we
cannot, yet, depend on C++17's std::hash<std::string_view> to
implement std::hash<QByteArray>, say. There's also no std::hash for
std::tuple, which would allow easy composition by using std::tie().

So piggy-back on the work we have done over the years on qHash()
functions, and implement the std::hash specializations for Qt types
using the existing qHash() functions, with a twist: The standard
allows implementations to provide means against predictable hash
values. Qt has this, too, but the seed is managed by the container and
passed to the qHash() function as a separate argument. The standard
does not have this explicit seed, so any protection must be implicit
in the normal use of std::hash.

To reap whatever protection that std library has on offer, if any, we
calculate a seed value by hashing int(0). This will be subject to
constant folding if there's no actual seed, but will produce a value
dependent on the seed if there is one.

Add some tests.

A question that remains is how to document the specialization. Can we
have a \stdhashable QDoc macro that does everything for us?

Task-number: QTBUG-33428
Change-Id: Idfe775f1661f8489587353c4b148d76611ac76f3
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Marc Mutz 2019-05-15 14:04:11 +02:00
parent 0f777a3b75
commit 4469e36d72
2 changed files with 72 additions and 0 deletions

View File

@ -45,6 +45,7 @@
#include <QtCore/qpair.h>
#include <numeric> // for std::accumulate
#include <functional> // for std::hash
#if 0
#pragma qt_class(QHashFunctions)
@ -172,6 +173,41 @@ template <typename T1, typename T2> inline uint qHash(const std::pair<T1, T2> &k
return seed;
}
#define QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH(Class, Arguments) \
QT_BEGIN_INCLUDE_NAMESPACE \
namespace std { \
template <> \
struct hash< QT_PREPEND_NAMESPACE(Class) > { \
using argument_type = QT_PREPEND_NAMESPACE(Class); \
using result_type = size_t; \
size_t operator()(Arguments s) const \
noexcept(noexcept(QT_PREPEND_NAMESPACE(qHash)(s))) \
{ \
/* this seeds qHash with the result of */ \
/* std::hash applied to an int, to reap */ \
/* any protection against predictable hash */ \
/* values the std implementation may provide */ \
return QT_PREPEND_NAMESPACE(qHash)(s, \
QT_PREPEND_NAMESPACE(qHash)( \
std::hash<int>{}(0))); \
} \
}; \
} \
QT_END_INCLUDE_NAMESPACE \
/*end*/
#define QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_CREF(Class) \
QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH(Class, const argument_type &)
#define QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_VALUE(Class) \
QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH(Class, argument_type)
QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_CREF(QString)
QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_CREF(QStringRef)
QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_VALUE(QStringView)
QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_VALUE(QLatin1String)
QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_CREF(QByteArray)
QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_CREF(QBitArray)
QT_END_NAMESPACE
#if defined(Q_CC_MSVC)

View File

@ -34,6 +34,8 @@
#include <sstream>
#include <algorithm>
#include <unordered_set>
class tst_QHashFunctions : public QObject
{
Q_OBJECT
@ -59,6 +61,8 @@ private Q_SLOTS:
void range();
void rangeCommutative();
void stdHash();
void setGlobalQHashSeed();
};
@ -281,6 +285,38 @@ void tst_QHashFunctions::rangeCommutative()
(void)qHashRangeCommutative(hashables, hashables + numHashables, seed);
}
void tst_QHashFunctions::stdHash()
{
{
std::unordered_set<QString> s = {QStringLiteral("Hello"), QStringLiteral("World")};
QCOMPARE(s.size(), 2UL);
s.insert(QStringLiteral("Hello"));
QCOMPARE(s.size(), 2UL);
}
{
std::unordered_set<QStringView> s = {QStringLiteral("Hello"), QStringLiteral("World")};
QCOMPARE(s.size(), 2UL);
s.insert(QStringLiteral("Hello"));
QCOMPARE(s.size(), 2UL);
}
{
std::unordered_set<QLatin1String> s = {QLatin1String("Hello"), QLatin1String("World")};
QCOMPARE(s.size(), 2UL);
s.insert(QLatin1String("Hello"));
QCOMPARE(s.size(), 2UL);
}
{
std::unordered_set<QByteArray> s = {QByteArrayLiteral("Hello"), QByteArrayLiteral("World")};
QCOMPARE(s.size(), 2UL);
s.insert(QByteArray("Hello"));
QCOMPARE(s.size(), 2UL);
}
}
void tst_QHashFunctions::setGlobalQHashSeed()
{
// Setter works as advertised