Long live qHashMulti(Commutative)

Add a helper function so that we have a shortcut.
Instead of writing:

  QHashCombine hash;
  seed = hash(seed, fieldA);
  seed = hash(seed, fieldB);
  // etc.
  return seed;

one can now simply write:

  return qHashMulti(seed, fieldA, fieldB, fieldC);

Port a few usages inside qtbase as a demonstration.

[ChangeLog][QtCore][QHash] Added the qHashMulti and
qHashMultiCommutative functions as convenience helpers
to calculate a hash from multiple variables (typically,
data members of a class).

Change-Id: I881a9ad41168df20ceecc6588a94abe7ddc6a532
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Reviewed-by: Marc Mutz <marc.mutz@kdab.com>
This commit is contained in:
Giuseppe D'Angelo 2020-02-14 16:16:56 +01:00
parent 2844631aa9
commit 707129fd5a
9 changed files with 114 additions and 47 deletions

View File

@ -151,7 +151,7 @@ inline bool operator==(const Employee &e1, const Employee &e2)
inline size_t qHash(const Employee &key, size_t seed)
{
return qHash(key.name(), seed) ^ key.dateOfBirth().day();
return qHashMulti(seed, key.name(), key.dateOfBirth());
}
#endif // EMPLOYEE_H

View File

@ -5030,10 +5030,7 @@ QByteArray QByteArray::toPercentEncoding(const QByteArray &exclude, const QByteA
*/
size_t qHash(const QByteArray::FromBase64Result &key, size_t seed) noexcept
{
QtPrivate::QHashCombine hash;
seed = hash(seed, key.decoded);
seed = hash(seed, static_cast<int>(key.decodingStatus));
return seed;
return qHashMulti(seed, key.decoded, static_cast<int>(key.decodingStatus));
}
QT_END_NAMESPACE

View File

@ -1091,10 +1091,7 @@ bool QLocale::operator!=(const QLocale &other) const
*/
size_t qHash(const QLocale &key, size_t seed) noexcept
{
QtPrivate::QHashCombine hash;
seed = hash(seed, key.d->m_data);
seed = hash(seed, key.d->m_numberOptions);
return seed;
return qHashMulti(seed, key.d->m_data, key.d->m_numberOptions);
}
/*!

View File

@ -1030,11 +1030,7 @@ static bool operator==(const QRegExpEngineKey &key1, const QRegExpEngineKey &key
static size_t qHash(const QRegExpEngineKey &key, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
seed = hash(seed, key.pattern);
seed = hash(seed, key.patternSyntax);
seed = hash(seed, key.cs);
return seed;
return qHashMulti(seed, key.pattern, key.patternSyntax, key.cs);
}
class QRegExpEngine;

View File

@ -1712,10 +1712,7 @@ bool QRegularExpression::operator==(const QRegularExpression &re) const
*/
size_t qHash(const QRegularExpression &key, size_t seed) noexcept
{
QtPrivate::QHashCombine hash;
seed = hash(seed, key.d->pattern);
seed = hash(seed, key.d->patternOptions);
return seed;
return qHashMulti(seed, key.d->pattern, key.d->patternOptions);
}
#if QT_STRINGVIEW_LEVEL < 2

View File

@ -596,6 +596,48 @@ uint qt_hash(QStringView key, uint chained) noexcept
constraints, we cannot change the QPair algorithm to match the std::pair one before Qt 6.
*/
/*!
\fn template <typename... T> size_t qHashMulti(size_t seed, const T &...args)
\relates QHash
\since 6.0
Returns the hash value for the \a{args}, using \a seed to seed
the calculation, by successively applying qHash() to each
element and combining the hash values into a single one.
Note that the order of the arguments is significant. If order does
not matter, use qHashMultiCommutative() instead. If you are hashing raw
memory, use qHashBits(); if you are hashing a range, use qHashRange().
This function is provided as a convenience to implement qHash() for
your own custom types. For example, here's how you could implement
a qHash() overload for a class \c{Employee}:
\snippet code/src_corelib_tools_qhash.cpp 13
\sa qHashMultiCommutative, qHashRange
*/
/*!
\fn template <typename... T> size_t qHashMultiCommutative(size_t seed, const T &...args)
\relates QHash
\since 6.0
Returns the hash value for the \a{args}, using \a seed to seed
the calculation, by successively applying qHash() to each
element and combining the hash values into a single one.
The order of the arguments is insignificant. If order does
matter, use qHashMulti() instead, as it may produce better quality
hashing. If you are hashing raw memory, use qHashBits(); if you are
hashing a range, use qHashRange().
This function is provided as a convenience to implement qHash() for
your own custom types.
\sa qHashMulti, qHashRange
*/
/*! \fn template <typename InputIterator> size_t qHashRange(InputIterator first, InputIterator last, size_t seed = 0)
\relates QHash
\since 5.5
@ -1059,15 +1101,16 @@ 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.
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:
Example:
\snippet code/src_corelib_tools_qhash.cpp 13
In the example above, we've relied on Qt's global qHash(const
QString &, uint) to give us a hash value for the employee's name, and
XOR'ed this with the day they were born to help produce unique
hashes for people with the same name.
In the example above, we've relied on Qt's own implementation of
qHash() for QString and QDate to give us a hash value for the
employee's name and date of birth respectively.
Note that the implementation of the qHash() overloads offered by Qt
may change at any time. You \b{must not} rely on the fact that qHash()

View File

@ -190,8 +190,54 @@ struct QHashCombineCommutative {
{ return seed + qHash(t); } // don't use xor!
};
template <typename... T>
using QHashMultiReturnType = decltype(
std::declval< std::enable_if_t<(sizeof...(T) > 0)> >(),
(qHash(std::declval<const T &>()), ...),
size_t{}
);
// workaround for a MSVC ICE,
// https://developercommunity.visualstudio.com/content/problem/996540/internal-compiler-error-on-msvc-1924-when-doing-sf.html
template <typename T>
inline constexpr bool QNothrowHashableHelper_v = noexcept(qHash(std::declval<const T &>()));
template <typename T, typename Enable = void>
struct QNothrowHashable : std::false_type {};
template <typename T>
struct QNothrowHashable<T, std::enable_if_t<QNothrowHashableHelper_v<T>>> : std::true_type {};
} // namespace QtPrivate
template <typename... T>
constexpr
#ifdef Q_QDOC
size_t
#else
QtPrivate::QHashMultiReturnType<T...>
#endif
qHashMulti(size_t seed, const T &... args)
noexcept(std::conjunction_v<QtPrivate::QNothrowHashable<T>...>)
{
QtPrivate::QHashCombine hash;
return ((seed = hash(seed, args)), ...), seed;
}
template <typename... T>
constexpr
#ifdef Q_QDOC
size_t
#else
QtPrivate::QHashMultiReturnType<T...>
#endif
qHashMultiCommutative(size_t seed, const T &... args)
noexcept(std::conjunction_v<QtPrivate::QNothrowHashable<T>...>)
{
QtPrivate::QHashCombineCommutative hash;
return ((seed = hash(seed, args)), ...), seed;
}
template <typename InputIterator>
inline size_t qHashRange(InputIterator first, InputIterator last, size_t seed = 0)
noexcept(noexcept(qHash(*first))) // assume iterator operations don't throw
@ -218,10 +264,7 @@ template <typename T1, typename T2> inline size_t qHash(const QPair<T1, T2> &key
template <typename T1, typename T2> inline size_t qHash(const std::pair<T1, T2> &key, size_t seed = 0)
noexcept(noexcept(qHash(key.first, seed)) && noexcept(qHash(key.second, seed)))
{
QtPrivate::QHashCombine hash;
seed = hash(seed, key.first);
seed = hash(seed, key.second);
return seed;
return qHashMulti(seed, key.first, key.second);
}
#define QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH(Class, Arguments) \

View File

@ -138,20 +138,19 @@ struct QFontDef
inline size_t qHash(const QFontDef &fd, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
seed = hash(seed, qRound64(fd.pixelSize*10000)); // use only 4 fractional digits
seed = hash(seed, fd.weight);
seed = hash(seed, fd.style);
seed = hash(seed, fd.stretch);
seed = hash(seed, fd.styleHint);
seed = hash(seed, fd.styleStrategy);
seed = hash(seed, fd.ignorePitch);
seed = hash(seed, fd.fixedPitch);
seed = hash(seed, fd.family);
seed = hash(seed, fd.families);
seed = hash(seed, fd.styleName);
seed = hash(seed, fd.hintingPreference);
return seed;
return qHashMulti(seed,
qRound64(fd.pixelSize*10000), // use only 4 fractional digits
fd.weight,
fd.style,
fd.stretch,
fd.styleHint,
fd.styleStrategy,
fd.ignorePitch,
fd.fixedPitch,
fd.family,
fd.families,
fd.styleName,
fd.hintingPreference);
}
class QFontEngineData

View File

@ -410,12 +410,7 @@ inline bool operator ==(const QFontEngine::FaceId &f1, const QFontEngine::FaceId
inline size_t qHash(const QFontEngine::FaceId &f, size_t seed = 0)
noexcept(noexcept(qHash(f.filename)))
{
QtPrivate::QHashCombine hash;
seed = hash(seed, f.filename);
seed = hash(seed, f.uuid);
seed = hash(seed, f.index);
seed = hash(seed, f.encoding);
return seed;
return qHashMulti(seed, f.filename, f.uuid, f.index, f.encoding);
}