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);
}
template <typename K>
using if_heterogeneously_seachable = QHashPrivate::if_heterogeneously_seachable_with<Key, K>;
public:
#ifdef __cpp_concepts
bool remove(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key)
template <typename K, if_heterogeneously_seachable<K> = true>
bool remove(const K &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);
}
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;
}
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;
}
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))
return *v;
else
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))
return *v;
else
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);
}
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);
}
template <typename K, if_heterogeneously_seachable<K> = true>
std::pair<iterator, iterator>
equal_range(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key)
equal_range(const K &key)
{
return equal_range_impl(*this, key);
}
template <typename K, if_heterogeneously_seachable<K> = true>
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);
}
iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key)
template <typename K, if_heterogeneously_seachable<K> = true>
iterator find(const K &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);
}
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);
}
#endif // __cpp_concepts
};
@ -2372,99 +2386,120 @@ private:
return iterator(result.it);
}
template <typename K>
using if_heterogeneously_seachable = QHashPrivate::if_heterogeneously_seachable_with<Key, K>;
public:
#ifdef __cpp_concepts
qsizetype remove(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key)
template <typename K, if_heterogeneously_seachable<K> = true>
qsizetype remove(const K &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);
}
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)
return false;
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))
return *v;
else
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))
return *v;
else
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);
}
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);
}
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);
}
iterator find(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key)
template <typename K, if_heterogeneously_seachable<K> = true>
iterator find(const K &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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
template <typename K, if_heterogeneously_seachable<K> = true>
std::pair<iterator, iterator>
equal_range(const QHashPrivate::HeterogeneouslySearchableWith<Key> auto &key)
equal_range(const K &key)
{
return equal_range_impl(key);
}
template <typename K, if_heterogeneously_seachable<K> = true>
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);
}
#endif // __cpp_concepts
};
Q_DECLARE_ASSOCIATIVE_FORWARD_ITERATOR(Hash)

View File

@ -9,9 +9,6 @@
#include <QtCore/qstring.h>
#include <QtCore/qstringfwd.h>
#ifdef __cpp_concepts
#include <concepts>
#endif
#include <numeric> // for std::accumulate
#include <functional> // for std::hash
#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
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)
!std::is_same_v<Key, T>
std::negation<std::is_same<Key, T>>,
// but are comparable amongst each other
&& std::equality_comparable_with<Key, T>
detail::is_equality_comparable_with<Key, T>,
// and supports heteregenous hashing
&& QHashHeterogeneousSearch<Key, T>::value;
template <typename Key, typename T> concept HeterogeneouslySearchableWith =
HeterogeneouslySearchableWithHelper<q20::remove_cvref_t<Key>, q20::remove_cvref_t<T>>;
#else
template <typename Key, typename T> constexpr bool HeterogeneouslySearchableWith = false;
#endif
QHashHeterogeneousSearch<Key, T>
> {};
template <typename Key, typename T>
using HeterogeneouslySearchableWith = HeterogeneouslySearchableWithHelper<
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>
@ -259,9 +281,8 @@ bool qHashEquals(const T &a, const T &b)
return a == b;
}
template <typename T1, typename T2>
std::enable_if_t<QHashPrivate::HeterogeneouslySearchableWith<T1, T2>, bool>
qHashEquals(const T1 &a, const T2 &b)
template <typename T1, typename T2, QHashPrivate::if_heterogeneously_seachable_with<T1, T2> = true>
bool qHashEquals(const T1 &a, const T2 &b)
{
return a == b;
}

View File

@ -1169,9 +1169,12 @@ void tst_QHash::operator_eq()
}
}
#ifdef __cpp_concepts
struct HeterogeneousHashingType
{
#ifndef __cpp_aggregate_paren_init
HeterogeneousHashingType() = default;
HeterogeneousHashingType(const QString &string) : s(string) {}
#endif
inline static int conversionCount = 0;
QString s;
@ -1182,12 +1185,18 @@ struct HeterogeneousHashingType
}
// 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)
{ 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)
{ 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)
{ return qHash(tester.s, seed); }
@ -1196,10 +1205,9 @@ QT_BEGIN_NAMESPACE
template <> struct QHashHeterogeneousSearch<QString, HeterogeneousHashingType> : std::true_type {};
template <> struct QHashHeterogeneousSearch<HeterogeneousHashingType, QString> : std::true_type {};
QT_END_NAMESPACE
static_assert(std::is_same_v<QString, std::common_type_t<QString, HeterogeneousHashingType>>);
static_assert(std::equality_comparable_with<QString, HeterogeneousHashingType>);
static_assert(QHashPrivate::HeterogeneouslySearchableWith<QString, HeterogeneousHashingType>);
static_assert(QHashPrivate::HeterogeneouslySearchableWith<HeterogeneousHashingType, QString>);
static_assert(QHashPrivate::detail::is_equality_comparable_with<QString, HeterogeneousHashingType>::value);
static_assert(QHashPrivate::HeterogeneouslySearchableWith<QString, HeterogeneousHashingType>::value);
static_assert(QHashPrivate::HeterogeneouslySearchableWith<HeterogeneousHashingType, QString>::value);
template <typename T> struct HeterogeneousSearchTestHelper
{
@ -1219,14 +1227,10 @@ template <> struct HeterogeneousSearchTestHelper<HeterogeneousHashingType>
QCOMPARE(HeterogeneousHashingType::conversionCount, 0);
}
};
#else
using HeterogeneousHashingType = QString;
#endif
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)
{
#ifdef __cpp_concepts
using Helper = HeterogeneousSearchTestHelper<View>;
String key = keys.last();
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.constFind(keyView), hash.constEnd());
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>