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:
parent
62b67092ea
commit
08373fb02d
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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)))
|
||||
{
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user