qHashMulti: switch to passing the seed to the qHash() function in 7.0

Amends 707129fd5a7c6390fbdf4270119226df2a427fcd.

This is much better for our string types because qHashBits() has a
faster implementation when seed!=0. The algorithm is the same, except
that QHashCombine now separates the seed from the combined hash, not
merging the two unrelated concepts together.

We can't fix it in Qt 6.x because it changes the hashing algorithm of
existing types that used qHashMulti(), which will have been inlined
throughout user code.

The deprecated constructor is there only until the rest of Qt is fixed.
Since this is a private class, that won't stay for long.

Task-number: QTBUG-134683
Fixes: QTBUG-134690
Change-Id: I2167e154f083089d12a1fffd61c1ab8670731156
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Thiago Macieira 2025-03-13 08:30:06 -07:00
parent b10c7b1680
commit c05ae82efb
11 changed files with 52 additions and 33 deletions

View File

@ -1445,7 +1445,7 @@ void QJsonObject::removeAt(qsizetype index)
size_t qHash(const QJsonObject &object, size_t seed)
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
for (auto it = object.begin(), end = object.end(); it != end; ++it) {
const QString key = it.key();
const QJsonValue value = it.value();

View File

@ -2710,9 +2710,9 @@ template <class Key, class T>
size_t qHash(const QHash<Key, T> &key, size_t seed = 0)
noexcept(noexcept(qHash(std::declval<Key&>())) && noexcept(qHash(std::declval<T&>())))
{
const QtPrivate::QHashCombine combine(seed);
size_t hash = 0;
for (auto it = key.begin(), end = key.end(); it != end; ++it) {
QtPrivate::QHashCombine combine;
size_t h = combine(seed, it.key());
// use + to keep the result independent of the ordering of the keys
hash += combine(h, it.value());
@ -2724,9 +2724,9 @@ template <class Key, class T>
inline size_t qHash(const QMultiHash<Key, T> &key, size_t seed = 0)
noexcept(noexcept(qHash(std::declval<Key&>())) && noexcept(qHash(std::declval<T&>())))
{
const QtPrivate::QHashCombine combine(seed);
size_t hash = 0;
for (auto it = key.begin(), end = key.end(); it != end; ++it) {
QtPrivate::QHashCombine combine;
size_t h = combine(seed, it.key());
// use + to keep the result independent of the ordering of the keys
hash += combine(h, it.value());

View File

@ -298,18 +298,40 @@ bool qHashEquals(const T1 &a, const T2 &b)
}
namespace QtPrivate {
struct QHashCombine
template <typename Mixer> struct QHashCombiner : private Mixer
{
typedef size_t result_type;
using result_type = typename Mixer::result_type ;
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) || defined(QT_BOOTSTRAPPED)
// Qt 6.x didn't use to pass the seed; bootstrap has no seed
static constexpr size_t seed = 0;
constexpr QHashCombiner(result_type) noexcept {}
Q_DECL_DEPRECATED_X("pass the seed argument") constexpr QHashCombiner() noexcept {}
#else
size_t seed;
constexpr QHashCombiner(result_type s) : seed(s) noexcept {}
#endif
template <typename T>
constexpr result_type operator()(size_t seed, const T &t) const
constexpr result_type operator()(result_type result, const T &t) const
noexcept(noexcept(qHash(t, seed)))
// combiner taken from N3876 / boost::hash_combine
{ return seed ^ (qHash(t, size_t(0)) + 0x9e3779b9 + (seed << 6) + (seed >> 2)); }
{
return Mixer::operator()(result, qHash(t, seed));
}
};
struct QHashCombineCommutative
struct QHashCombineMixer
{
typedef size_t result_type;
constexpr result_type operator()(result_type result, result_type hash) const noexcept
{
// combiner taken from N3876 / boost::hash_combine
return result ^ (hash + 0x9e3779b9 + (result << 6) + (result >> 2));
}
};
using QHashCombine = QHashCombiner<QHashCombineMixer>;
struct QHashCombineCommutativeMixer : std::plus<size_t>
{
// QHashCombine is a good hash combiner, but is not commutative,
// ie. it depends on the order of the input elements. That is
@ -317,11 +339,8 @@ struct QHashCombineCommutative
// {1,3,0}. Except when it isn't (e.g. for QSet and
// QHash). Therefore, provide a commutative combiner, too.
typedef size_t result_type;
template <typename T>
constexpr result_type operator()(size_t seed, const T &t) const
noexcept(noexcept(qHash(t, seed)))
{ return seed + qHash(t, size_t(0)); } // don't use xor!
};
using QHashCombineCommutative = QHashCombiner<QHashCombineCommutativeMixer>;
template <typename... T>
using QHashMultiReturnType = decltype(
@ -356,7 +375,7 @@ QtPrivate::QHashMultiReturnType<T...>
qHashMulti(size_t seed, const T &... args)
noexcept(std::conjunction_v<QtPrivate::QNothrowHashable<T>...>)
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
return ((seed = hash(seed, args)), ...), seed;
}
@ -370,7 +389,7 @@ QtPrivate::QHashMultiReturnType<T...>
qHashMultiCommutative(size_t seed, const T &... args)
noexcept(std::conjunction_v<QtPrivate::QNothrowHashable<T>...>)
{
QtPrivate::QHashCombineCommutative hash;
QtPrivate::QHashCombineCommutative hash(seed);
return ((seed = hash(seed, args)), ...), seed;
}
@ -378,14 +397,14 @@ 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
{
return std::accumulate(first, last, seed, QtPrivate::QHashCombine());
return std::accumulate(first, last, seed, QtPrivate::QHashCombine(seed));
}
template <typename InputIterator>
inline size_t qHashRangeCommutative(InputIterator first, InputIterator last, size_t seed = 0)
noexcept(noexcept(qHash(*first))) // assume iterator operations don't throw
{
return std::accumulate(first, last, seed, QtPrivate::QHashCombineCommutative());
return std::accumulate(first, last, seed, QtPrivate::QHashCombineCommutative(seed));
}
namespace QHashPrivate {

View File

@ -810,7 +810,7 @@ private:
return seed;
// don't use qHashRange to avoid its compile-time overhead:
return std::accumulate(key.d->m.begin(), key.d->m.end(), seed,
QtPrivate::QHashCombine{});
QtPrivate::QHashCombine{seed});
}
#endif // !Q_QDOC
};

View File

@ -784,7 +784,7 @@ bool QTransform::operator==(const QTransform &o) const
*/
size_t qHash(const QTransform &key, size_t seed) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, key.m11());
seed = hash(seed, key.m12());
seed = hash(seed, key.m21());

View File

@ -6543,7 +6543,7 @@ bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBind
size_t qHash(const QRhiShaderResourceBinding &b, size_t seed) noexcept
{
const QRhiShaderResourceBinding::Data *d = QRhiImplementation::shaderResourceBindingData(b);
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, d->binding);
seed = hash(seed, d->stage);
seed = hash(seed, d->type);

View File

@ -69,7 +69,7 @@ private:
friend size_t qHash(const QRhiDepthStencilClearValue &v, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, v.m_d);
seed = hash(seed, v.m_s);
return seed;
@ -118,7 +118,7 @@ private:
friend size_t qHash(const QRhiViewport &v, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, v.m_rect[0]);
seed = hash(seed, v.m_rect[1]);
seed = hash(seed, v.m_rect[2]);
@ -161,7 +161,7 @@ private:
friend size_t qHash(const QRhiScissor &v, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, v.m_rect[0]);
seed = hash(seed, v.m_rect[1]);
seed = hash(seed, v.m_rect[2]);
@ -215,7 +215,7 @@ private:
friend size_t qHash(const QRhiVertexInputBinding &v, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, v.m_stride);
seed = hash(seed, v.m_classification);
seed = hash(seed, v.m_instanceStepRate);
@ -303,7 +303,7 @@ private:
friend size_t qHash(const QRhiVertexInputAttribute &v, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, v.m_binding);
seed = hash(seed, v.m_location);
seed = hash(seed, v.m_format);
@ -363,7 +363,7 @@ private:
friend size_t qHash(const QRhiVertexInputLayout &v, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, v.m_bindings);
seed = hash(seed, v.m_attributes);
return seed;
@ -420,7 +420,7 @@ private:
friend size_t qHash(const QRhiShaderStage &v, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, v.m_type);
seed = hash(seed, v.m_shader);
seed = hash(seed, v.m_shaderVariant);

View File

@ -507,7 +507,7 @@ struct Q_D3D12_SAMPLER_DESC
friend size_t qHash(const Q_D3D12_SAMPLER_DESC &key, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, key.desc.Filter);
seed = hash(seed, key.desc.AddressU);
seed = hash(seed, key.desc.AddressV);

View File

@ -812,7 +812,7 @@ bool operator==(const QShader &lhs, const QShader &rhs) noexcept
size_t qHash(const QShader &s, size_t seed) noexcept
{
if (s.d) {
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, s.stage());
if (!s.d->shaders.isEmpty()) {
seed = hash(seed, s.d->shaders.firstKey());

View File

@ -51,7 +51,7 @@ inline bool operator!=(const QtFontFallbacksCacheKey &lhs, const QtFontFallbacks
inline size_t qHash(const QtFontFallbacksCacheKey &key, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, key.family);
seed = hash(seed, int(key.style));
seed = hash(seed, int(key.styleHint));

View File

@ -81,7 +81,7 @@ inline bool operator!=(const QVulkanLayer &lhs, const QVulkanLayer &rhs) noexcep
inline size_t qHash(const QVulkanLayer &key, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, key.name);
seed = hash(seed, key.version);
seed = hash(seed, key.specVersion);
@ -104,7 +104,7 @@ inline bool operator!=(const QVulkanExtension &lhs, const QVulkanExtension &rhs)
inline size_t qHash(const QVulkanExtension &key, size_t seed = 0) noexcept
{
QtPrivate::QHashCombine hash;
QtPrivate::QHashCombine hash(seed);
seed = hash(seed, key.name);
seed = hash(seed, key.version);
return seed;