QHash: Unconceptify heterogeneous search code

That allows the code to run in C++17 mode - at the expense of
significantly worse compile time error messages, potentially worse
compilation speed and harder to read code.

Adjust the test type to actually model
detail::is_equality_comparable_with (which requires all four
relational operators even in C++17).

As a drive-by, fix the return value of remove() from auto to bool.

Done-with: Marc Mutz <marc.mutz@qt.io>
Fixes: QTBUG-128470
Change-Id: I68df26db579c60812a18e09b76dd5712e73ccaa2
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit 7fe3cee36352c74cbaaff52e863bd0da1170affa)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Fabian Kosmale 2024-08-14 09:47:29 +02:00 committed by Qt Cherry-pick Bot
parent 16ed1fe089
commit 722c7edf03
3 changed files with 124 additions and 69 deletions

View File

@ -1377,69 +1377,83 @@ private:
return iterator(result.it); return iterator(result.it);
} }
template <typename K>
using if_heterogeneously_seachable = QHashPrivate::if_heterogeneously_seachable_with<Key, K>;
public: public:
#ifdef __cpp_concepts template <typename K, if_heterogeneously_seachable<K> = true>
bool remove(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) bool remove(const K &key)
{ {
return removeImpl(key); return removeImpl(key);
} }
T take(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) template <typename K, if_heterogeneously_seachable<K> = true>
T take(const K &key)
{ {
return takeImpl(key); return takeImpl(key);
} }
bool contains(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const template <typename K, if_heterogeneously_seachable<K> = true>
bool contains(const K &key) const
{ {
return d ? d->findNode(key) != nullptr : false; return d ? d->findNode(key) != nullptr : false;
} }
qsizetype count(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const template <typename K, if_heterogeneously_seachable<K> = true>
qsizetype count(const K &key) const
{ {
return contains(key) ? 1 : 0; return contains(key) ? 1 : 0;
} }
T value(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
T value(const K &key) const noexcept
{ {
if (auto *v = valueImpl(key)) if (auto *v = valueImpl(key))
return *v; return *v;
else else
return T(); return T();
} }
T value(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &defaultValue) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
T value(const K &key, const T &defaultValue) const noexcept
{ {
if (auto *v = valueImpl(key)) if (auto *v = valueImpl(key))
return *v; return *v;
else else
return defaultValue; return defaultValue;
} }
T &operator[](const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) template <typename K, if_heterogeneously_seachable<K> = true>
T &operator[](const K &key)
{ {
return operatorIndexImpl(key); return operatorIndexImpl(key);
} }
const T operator[](const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
const T operator[](const K &key) const noexcept
{ {
return value(key); return value(key);
} }
template <typename K, if_heterogeneously_seachable<K> = true>
std::pair<iterator, iterator> std::pair<iterator, iterator>
equal_range(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) equal_range(const K &key)
{ {
return equal_range_impl(*this, key); return equal_range_impl(*this, key);
} }
template <typename K, if_heterogeneously_seachable<K> = true>
std::pair<const_iterator, const_iterator> std::pair<const_iterator, const_iterator>
equal_range(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept equal_range(const K &key) const noexcept
{ {
return equal_range_impl(*this, key); return equal_range_impl(*this, key);
} }
iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) template <typename K, if_heterogeneously_seachable<K> = true>
iterator find(const K &key)
{ {
return findImpl(key); return findImpl(key);
} }
const_iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
const_iterator find(const K &key) const noexcept
{ {
return constFindImpl(key); return constFindImpl(key);
} }
const_iterator constFind(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
const_iterator constFind(const K &key) const noexcept
{ {
return find(key); return find(key);
} }
#endif // __cpp_concepts
}; };
@ -2372,99 +2386,120 @@ private:
return iterator(result.it); return iterator(result.it);
} }
template <typename K>
using if_heterogeneously_seachable = QHashPrivate::if_heterogeneously_seachable_with<Key, K>;
public: public:
#ifdef __cpp_concepts template <typename K, if_heterogeneously_seachable<K> = true>
qsizetype remove(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) qsizetype remove(const K &key)
{ {
return removeImpl(key); return removeImpl(key);
} }
T take(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) template <typename K, if_heterogeneously_seachable<K> = true>
T take(const K &key)
{ {
return takeImpl(key); return takeImpl(key);
} }
bool contains(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
bool contains(const K &key) const noexcept
{ {
if (!d) if (!d)
return false; return false;
return d->findNode(key) != nullptr; return d->findNode(key) != nullptr;
} }
T value(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
T value(const K &key) const noexcept
{ {
if (auto *v = valueImpl(key)) if (auto *v = valueImpl(key))
return *v; return *v;
else else
return T(); return T();
} }
T value(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &defaultValue) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
T value(const K &key, const T &defaultValue) const noexcept
{ {
if (auto *v = valueImpl(key)) if (auto *v = valueImpl(key))
return *v; return *v;
else else
return defaultValue; return defaultValue;
} }
T &operator[](const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) template <typename K, if_heterogeneously_seachable<K> = true>
T &operator[](const K &key)
{ {
return operatorIndexImpl(key); return operatorIndexImpl(key);
} }
const T operator[](const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
const T operator[](const K &key) const noexcept
{ {
return value(key); return value(key);
} }
QList<T> values(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) template <typename K, if_heterogeneously_seachable<K> = true>
QList<T> values(const K &key)
{ {
return valuesImpl(key); return valuesImpl(key);
} }
iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) template <typename K, if_heterogeneously_seachable<K> = true>
iterator find(const K &key)
{ {
return findImpl(key); return findImpl(key);
} }
const_iterator constFind(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
const_iterator constFind(const K &key) const noexcept
{ {
return constFindImpl(key); return constFindImpl(key);
} }
const_iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
const_iterator find(const K &key) const noexcept
{ {
return constFindImpl(key); return constFindImpl(key);
} }
bool contains(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
bool contains(const K &key, const T &value) const noexcept
{ {
return containsImpl(key, value); return containsImpl(key, value);
} }
qsizetype remove(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) template <typename K, if_heterogeneously_seachable<K> = true>
qsizetype remove(const K &key, const T &value)
{ {
return removeImpl(key, value); return removeImpl(key, value);
} }
qsizetype count(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
qsizetype count(const K &key) const noexcept
{ {
return countImpl(key); return countImpl(key);
} }
qsizetype count(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
qsizetype count(const K &key, const T &value) const noexcept
{ {
return countImpl(key, value); return countImpl(key, value);
} }
iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) template <typename K, if_heterogeneously_seachable<K> = true>
iterator find(const K &key, const T &value)
{ {
return findImpl(key, value); return findImpl(key, value);
} }
const_iterator constFind(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
const_iterator constFind(const K &key, const T &value) const noexcept
{ {
return constFindImpl(key, value); return constFindImpl(key, value);
} }
const_iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key, const T &value) const noexcept template <typename K, if_heterogeneously_seachable<K> = true>
const_iterator find(const K &key, const T &value) const noexcept
{ {
return constFind(key, value); return constFind(key, value);
} }
template <typename K, if_heterogeneously_seachable<K> = true>
std::pair<iterator, iterator> std::pair<iterator, iterator>
equal_range(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) equal_range(const K &key)
{ {
return equal_range_impl(key); return equal_range_impl(key);
} }
template <typename K, if_heterogeneously_seachable<K> = true>
std::pair<const_iterator, const_iterator> std::pair<const_iterator, const_iterator>
equal_range(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key) const noexcept equal_range(const K &key) const noexcept
{ {
return equal_range_impl(key); return equal_range_impl(key);
} }
#endif // __cpp_concepts
}; };
Q_DECLARE_ASSOCIATIVE_FORWARD_ITERATOR(Hash) Q_DECLARE_ASSOCIATIVE_FORWARD_ITERATOR(Hash)

View File

@ -9,9 +9,6 @@
#include <QtCore/qstring.h> #include <QtCore/qstring.h>
#include <QtCore/qstringfwd.h> #include <QtCore/qstringfwd.h>
#ifdef __cpp_concepts
#include <concepts>
#endif
#include <numeric> // for std::accumulate #include <numeric> // for std::accumulate
#include <functional> // for std::hash #include <functional> // for std::hash
#include <utility> // For std::pair #include <utility> // For std::pair
@ -238,19 +235,44 @@ size_t qHash(const T &t, size_t seed, Args&&...) noexcept(noexcept(qHash(t)))
#endif // < Qt 7 #endif // < Qt 7
namespace QHashPrivate { namespace QHashPrivate {
#ifdef __cpp_concepts
template <typename Key, typename T> concept HeterogeneouslySearchableWithHelper = namespace detail {
// approximates std::equality_comparable_with
template <typename T, typename U, typename = void>
struct is_equality_comparable_with : std::false_type {};
template <typename T, typename U>
struct is_equality_comparable_with<T, U,
std::void_t<
decltype(bool(std::declval<T>() == std::declval<U>())),
decltype(bool(std::declval<U>() == std::declval<T>())),
decltype(bool(std::declval<T>() != std::declval<U>())),
decltype(bool(std::declval<U>() != std::declval<T>()))
>>
: std::true_type {};
}
template <typename Key, typename T> struct HeterogeneouslySearchableWithHelper
: std::conjunction<
// if Key and T are not the same (member already exists) // if Key and T are not the same (member already exists)
!std::is_same_v<Key, T> std::negation<std::is_same<Key, T>>,
// but are comparable amongst each other // but are comparable amongst each other
&& std::equality_comparable_with<Key, T> detail::is_equality_comparable_with<Key, T>,
// and supports heteregenous hashing // and supports heteregenous hashing
&& QHashHeterogeneousSearch<Key, T>::value; QHashHeterogeneousSearch<Key, T>
template <typename Key, typename T> concept HeterogeneouslySearchableWith = > {};
HeterogeneouslySearchableWithHelper<q20::remove_cvref_t<Key>, q20::remove_cvref_t<T>>;
#else template <typename Key, typename T>
template <typename Key, typename T> constexpr bool HeterogeneouslySearchableWith = false; using HeterogeneouslySearchableWith = HeterogeneouslySearchableWithHelper<
#endif q20::remove_cvref_t<Key>,
q20::remove_cvref_t<T>
>;
template <typename Key, typename K>
using if_heterogeneously_seachable_with = std::enable_if_t<
QHashPrivate::HeterogeneouslySearchableWith<Key, K>::value,
bool>;
} }
template<typename T> template<typename T>
@ -259,9 +281,8 @@ bool qHashEquals(const T &a, const T &b)
return a == b; return a == b;
} }
template <typename T1, typename T2> template <typename T1, typename T2, QHashPrivate::if_heterogeneously_seachable_with<T1, T2> = true>
std::enable_if_t<QHashPrivate::HeterogeneouslySearchableWith<T1, T2>, bool> bool qHashEquals(const T1 &a, const T2 &b)
qHashEquals(const T1 &a, const T2 &b)
{ {
return a == b; return a == b;
} }

View File

@ -1169,9 +1169,12 @@ void tst_QHash::operator_eq()
} }
} }
#ifdef __cpp_concepts
struct HeterogeneousHashingType struct HeterogeneousHashingType
{ {
#ifndef __cpp_aggregate_paren_init
HeterogeneousHashingType() = default;
HeterogeneousHashingType(const QString &string) : s(string) {}
#endif
inline static int conversionCount = 0; inline static int conversionCount = 0;
QString s; QString s;
@ -1182,12 +1185,18 @@ struct HeterogeneousHashingType
} }
// std::equality_comparable_with requires we be self-comparable too // std::equality_comparable_with requires we be self-comparable too
friend bool operator==(const HeterogeneousHashingType &t1, const HeterogeneousHashingType &t2) = default; friend bool operator==(const HeterogeneousHashingType &t1, const HeterogeneousHashingType &t2) { return t1.s == t2.s; };
friend bool operator==(const QString &string, const HeterogeneousHashingType &tester) friend bool operator==(const QString &string, const HeterogeneousHashingType &tester)
{ return tester.s == string; } { return tester.s == string; }
#ifndef __cpp_impl_three_way_compare // full set required for detail::is_equality_comparable_with<QString>
friend bool operator!=(const QString &string, const HeterogeneousHashingType &tester) friend bool operator!=(const QString &string, const HeterogeneousHashingType &tester)
{ return !(tester.s == string); } { return !operator==(string, tester); }
friend bool operator==(const HeterogeneousHashingType &tester, const QString &string)
{ return operator==(string, tester); }
friend bool operator!=(const HeterogeneousHashingType &tester, const QString &string)
{ return !operator==(string, tester); }
#endif
friend size_t qHash(const HeterogeneousHashingType &tester, size_t seed) friend size_t qHash(const HeterogeneousHashingType &tester, size_t seed)
{ return qHash(tester.s, seed); } { return qHash(tester.s, seed); }
@ -1196,10 +1205,9 @@ QT_BEGIN_NAMESPACE
template <> struct QHashHeterogeneousSearch<QString, HeterogeneousHashingType> : std::true_type {}; template <> struct QHashHeterogeneousSearch<QString, HeterogeneousHashingType> : std::true_type {};
template <> struct QHashHeterogeneousSearch<HeterogeneousHashingType, QString> : std::true_type {}; template <> struct QHashHeterogeneousSearch<HeterogeneousHashingType, QString> : std::true_type {};
QT_END_NAMESPACE QT_END_NAMESPACE
static_assert(std::is_same_v<QString, std::common_type_t<QString, HeterogeneousHashingType>>); static_assert(QHashPrivate::detail::is_equality_comparable_with<QString, HeterogeneousHashingType>::value);
static_assert(std::equality_comparable_with<QString, HeterogeneousHashingType>); static_assert(QHashPrivate::HeterogeneouslySearchableWith<QString, HeterogeneousHashingType>::value);
static_assert(QHashPrivate::HeterogeneouslySearchableWith<QString, HeterogeneousHashingType>); static_assert(QHashPrivate::HeterogeneouslySearchableWith<HeterogeneousHashingType, QString>::value);
static_assert(QHashPrivate::HeterogeneouslySearchableWith<HeterogeneousHashingType, QString>);
template <typename T> struct HeterogeneousSearchTestHelper template <typename T> struct HeterogeneousSearchTestHelper
{ {
@ -1219,14 +1227,10 @@ template <> struct HeterogeneousSearchTestHelper<HeterogeneousHashingType>
QCOMPARE(HeterogeneousHashingType::conversionCount, 0); QCOMPARE(HeterogeneousHashingType::conversionCount, 0);
} }
}; };
#else
using HeterogeneousHashingType = QString;
#endif
template <template <typename, typename> class Hash, typename String, typename View, typename Converter> template <template <typename, typename> class Hash, typename String, typename View, typename Converter>
static void heterogeneousSearchTest(const QList<std::remove_const_t<String>> &keys, Converter conv) static void heterogeneousSearchTest(const QList<std::remove_const_t<String>> &keys, Converter conv)
{ {
#ifdef __cpp_concepts
using Helper = HeterogeneousSearchTestHelper<View>; using Helper = HeterogeneousSearchTestHelper<View>;
String key = keys.last(); String key = keys.last();
String otherKey = keys.first(); String otherKey = keys.first();
@ -1334,11 +1338,6 @@ static void heterogeneousSearchTest(const QList<std::remove_const_t<String>> &ke
QCOMPARE_EQ(hash.find(keyView), hash.end()); QCOMPARE_EQ(hash.find(keyView), hash.end());
QCOMPARE_EQ(hash.constFind(keyView), hash.constEnd()); QCOMPARE_EQ(hash.constFind(keyView), hash.constEnd());
Helper::checkCounter(); Helper::checkCounter();
#else
Q_UNUSED(keys);
Q_UNUSED(conv);
QSKIP("This feature requires C++20 (concepts)");
#endif
} }
template <template <typename, typename> class Hash, typename String, typename View> template <template <typename, typename> class Hash, typename String, typename View>