Add qHashRange and qHashRangeCommutative

qHashRange() takes an (input iterator) range and hashes each element, combining
the hash values using the hash combiner from Boost/N1837 with the magic number
0x9e3779b9, as described here:
http://stackoverflow.com/questions/4948780/magic-number-in-boosthash-combine

qHashRangeCommutative() does the same but with a cummutative combiner (unsigned
addition) to create hash values that are order-independent, e.g. for hashed
containers. The obvious combiner, XOR, is a bad one because it eliminates
duplicate elements. Signed addition cannot be used, since signed overflow
leads to undefined behavior.

[ChangeLog][QtCore] Added qHashRange() and qHashRangeCommutative() functions to aid
implementing qHash() overloads for custom types.

Change-Id: I3c2bbc9ce4bd0455262a70e0cf248486525e534f
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Marc Mutz 2014-03-17 15:44:29 +01:00
parent 62b67092ea
commit 08373fb02d
4 changed files with 205 additions and 0 deletions

View File

@ -307,3 +307,17 @@ inline uint qHash(const std::vector<int> &key, uint seed = 0)
return qHashBits(&key.front(), key.size() * sizeof(int), seed);
}
//! [qhashbits]
//! [qhashrange]
inline uint qHash(const std::vector<int> &key, uint seed = 0)
{
return qHashRange(key.begin(), key.end(), seed);
}
//! [qhashrange]
//! [qhashrangecommutative]
inline uint qHash(const std::unordered_set<int> &key, uint seed = 0)
{
return qHashRangeCommutative(key.begin(), key.end(), seed);
}
//! [qhashrangecommutative]

View File

@ -665,6 +665,85 @@ void QHashData::checkSanity()
Types \c T1 and \c T2 must be supported by qHash().
*/
/*! \fn uint qHashRange(InputIterator first, InputIterator last, uint seed = 0)
\relates QHash
\since 5.5
Returns the hash value for the range [\a{first},\a{last}), using \a seed
to seed the calculation, by successively applying qHash() to each
element and combining the hash values into a single one.
The return value of this function depends on the order of elements
in the range. That means that
\code
{0, 1, 2}
\endcode
and
\code
{1, 2, 0}
\endcode
hash to \b{different} values. If order does not matter, for example for hash
tables, use qHashRangeCommutative() instead. If you are hashing raw
memory, use qHashBits().
Use this function only to implement qHash() for your own custom
types. For example, here's how you could implement a qHash() overload for
std::vector<int>:
\snippet code/src_corelib_tools_qhash.cpp qhashrange
It bears repeating that the implementation of qHashRange() - like
the qHash() overloads offered by Qt - may change at any time. You
\b{must not} rely on the fact that qHashRange() will give the same
results (for the same inputs) across different Qt versions, even
if qHash() for the element type would.
\sa qHashBits(), qHashRangeCommutative()
*/
/*! \fn uint qHashRangeCommutative(InputIterator first, InputIterator last, uint seed = 0)
\relates QHash
\since 5.5
Returns the hash value for the range [\a{first},\a{last}), using \a seed
to seed the calculation, by successively applying qHash() to each
element and combining the hash values into a single one.
The return value of this function does not depend on the order of
elements in the range. That means that
\code
{0, 1, 2}
\endcode
and
\code
{1, 2, 0}
\endcode
hash to the \b{same} values. If order matters, for example, for vectors
and arrays, use qHashRange() instead. If you are hashing raw
memory, use qHashBits().
Use this function only to implement qHash() for your own custom
types. For example, here's how you could implement a qHash() overload for
std::unordered_set<int>:
\snippet code/src_corelib_tools_qhash.cpp qhashrangecommutative
It bears repeating that the implementation of
qHashRangeCommutative() - like the qHash() overloads offered by Qt
- may change at any time. You \b{must not} rely on the fact that
qHashRangeCommutative() will give the same results (for the same
inputs) across different Qt versions, even if qHash() for the
element type would.
\sa qHashBits(), qHashRange()
*/
/*! \fn uint qHashBits(const void *p, size_t len, uint seed = 0)
\relates QHash
\since 5.4
@ -678,10 +757,16 @@ void QHashData::checkSanity()
\snippet code/src_corelib_tools_qhash.cpp qhashbits
This takes advantage of the fact that std::vector lays out its data
contiguously. If that is not the case, or the contained type has
padding, you should use qHashRange() instead.
It bears repeating that the implementation of qHashBits() - like
the qHash() overloads offered by Qt - may change at any time. You
\b{must not} rely on the fact that qHashBits() will give the same
results (for the same inputs) across different Qt versions.
\sa qHashRange(), qHashRangeCommutative()
*/
/*! \fn uint qHash(char key, uint seed = 0)

View File

@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz <marc.mutz@kdab.com>
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtCore module of the Qt Toolkit.
@ -40,6 +41,7 @@
#include <QtCore/qpair.h>
#include <QtCore/qrefcount.h>
#include <numeric> // for std::accumulate
#ifdef Q_COMPILER_INITIALIZER_LISTS
#include <initializer_list>
#endif
@ -101,6 +103,44 @@ template<typename T> inline uint qHash(const T &t, uint seed)
Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(t)))
{ return (qHash(t) ^ seed); }
namespace QtPrivate {
struct QHashCombine {
typedef uint result_type;
template <typename T>
Q_DECL_CONSTEXPR result_type operator()(uint seed, const T &t) const Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(t)))
// combiner taken from N3876 / boost::hash_combine
{ return seed ^ (qHash(t) + 0x9e3779b9 + (seed << 6) + (seed >> 2)) ; }
};
struct QHashCombineCommutative {
// QHashCombine is a good hash combiner, but is not commutative,
// ie. it depends on the order of the input elements. That is
// usually what we want: {0,1,3} should hash differently than
// {1,3,0}. Except when it isn't (e.g. for QSet and
// QHash). Therefore, provide a commutative combiner, too.
typedef uint result_type;
template <typename T>
Q_DECL_CONSTEXPR result_type operator()(uint seed, const T &t) const Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(t)))
{ return seed + qHash(t); } // don't use xor!
};
} // namespace QtPrivate
template <typename InputIterator>
inline uint qHashRange(InputIterator first, InputIterator last, uint seed = 0)
Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(*first))) // assume iterator operations don't throw
{
return std::accumulate(first, last, seed, QtPrivate::QHashCombine());
}
template <typename InputIterator>
inline uint qHashRangeCommutative(InputIterator first, InputIterator last, uint seed = 0)
Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(*first))) // assume iterator operations don't throw
{
return std::accumulate(first, last, seed, QtPrivate::QHashCombineCommutative());
}
template <typename T1, typename T2> inline uint qHash(const QPair<T1, T2> &key, uint seed = 0)
Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(key.first, seed)) && noexcept(qHash(key.second, seed)))
{

View File

@ -42,6 +42,11 @@
#include <QtTest/QtTest>
#include <qhash.h>
#include <qtypetraits.h>
#include <iterator>
#include <sstream>
#include <algorithm>
class tst_QHashFunctions : public QObject
{
@ -51,6 +56,8 @@ private Q_SLOTS:
void fp_qhash_of_zero_is_zero();
void qthash_data();
void qthash();
void range();
void rangeCommutative();
};
void tst_QHashFunctions::qhash()
@ -149,5 +156,64 @@ void tst_QHashFunctions::qthash()
QTEST(result, "hash");
}
namespace SomeNamespace {
struct Hashable { int i; };
inline uint qHash(Hashable h, uint seed = 0)
{ return QT_PREPEND_NAMESPACE(qHash)(h.i, seed); }
}
void tst_QHashFunctions::range()
{
static const int ints[] = {0, 1, 2, 3, 4, 5};
static const size_t numInts = sizeof ints / sizeof *ints;
// empty range just gives the seed:
QCOMPARE(qHashRange(ints, ints, 0xdeadbeefU), 0xdeadbeefU);
// verify that order matters:
QVERIFY(qHashRange(ints, ints + numInts) !=
qHashRange(std::reverse_iterator<const int*>(ints + numInts), std::reverse_iterator<const int*>(ints)));
{
// verify that the input iterator category suffices:
std::stringstream sstream;
Q_STATIC_ASSERT((QtPrivate::is_same<std::input_iterator_tag, std::istream_iterator<int>::iterator_category>::value));
std::copy(ints, ints + numInts, std::ostream_iterator<int>(sstream, " "));
sstream.seekg(0);
std::istream_iterator<int> it(sstream), end;
QCOMPARE(qHashRange(ints, ints + numInts), qHashRange(it, end));
}
SomeNamespace::Hashable hashables[] = {{0}, {1}, {2}, {3}, {4}, {5}};
static const size_t numHashables = sizeof hashables / sizeof *hashables;
// compile check: is qHash() found using ADL?
(void)qHashRange(hashables, hashables + numHashables);
}
void tst_QHashFunctions::rangeCommutative()
{
int ints[] = {0, 1, 2, 3, 4, 5};
static const size_t numInts = sizeof ints / sizeof *ints;
// empty range just gives the seed:
QCOMPARE(qHashRangeCommutative(ints, ints, 0xdeadbeefU), 0xdeadbeefU);
// verify that order doesn't matter:
QCOMPARE(qHashRangeCommutative(ints, ints + numInts),
qHashRangeCommutative(std::reverse_iterator<int*>(ints + numInts), std::reverse_iterator<int*>(ints)));
{
// verify that the input iterator category suffices:
std::stringstream sstream;
std::copy(ints, ints + numInts, std::ostream_iterator<int>(sstream, " "));
sstream.seekg(0);
std::istream_iterator<int> it(sstream), end;
QCOMPARE(qHashRangeCommutative(ints, ints + numInts), qHashRangeCommutative(it, end));
}
SomeNamespace::Hashable hashables[] = {{0}, {1}, {2}, {3}, {4}, {5}};
static const size_t numHashables = sizeof hashables / sizeof *hashables;
// compile check: is qHash() found using ADL?
(void)qHashRangeCommutative(hashables, hashables + numHashables);
}
QTEST_APPLESS_MAIN(tst_QHashFunctions)
#include "tst_qhashfunctions.moc"