Associative containers: add erase_if

Use a trick similar to the one we use for their ranged
constructors: support predicates that either take a
container's iterator, or that take a std::pair (for STL
compatibility).

[ChangeLog][QtCore][QMap] Added removeIf() and erase_if().

[ChangeLog][QtCore][QMultiMap] Added removeIf() and erase_if().

[ChangeLog][QtCore][QHash] Added removeIf() and erase_if().

[ChangeLog][QtCore][QMultiHash] Added removeIf() and erase_if().

Change-Id: Ie40aadf6217d7a4126a626c390d530812ebcf020
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Giuseppe D'Angelo 2020-10-16 20:52:48 +02:00
parent 9341f2e3d0
commit e12e2b43b7
7 changed files with 281 additions and 0 deletions

View File

@ -215,6 +215,58 @@ qsizetype qset_erase_if(QSet<T> &set, Predicate &pred)
return result;
}
// Prerequisite: F is invocable on ArgTypes
template <typename R, typename F, typename ... ArgTypes>
struct is_invoke_result_explicitly_convertible : std::is_constructible<R, std::invoke_result_t<F, ArgTypes...>>
{};
// is_invocable_r checks for implicit conversions, but we need to check
// for explicit conversions in remove_if. So, roll our own trait.
template <typename R, typename F, typename ... ArgTypes>
constexpr bool is_invocable_explicit_r_v = std::conjunction_v<
std::is_invocable<F, ArgTypes...>,
is_invoke_result_explicitly_convertible<R, F, ArgTypes...>
>;
template <typename Container, typename Predicate>
auto associative_erase_if(Container &c, Predicate &pred)
{
// we support predicates callable with either Container::iterator
// or with std::pair<const Key &, Value &>
using Iterator = typename Container::iterator;
using Key = typename Container::key_type;
using Value = typename Container::mapped_type;
using KeyValuePair = std::pair<const Key &, Value &>;
typename Container::size_type result = 0;
auto it = c.begin();
const auto e = c.end();
while (it != e) {
if constexpr (is_invocable_explicit_r_v<bool, Predicate &, Iterator &>) {
if (pred(it)) {
it = c.erase(it);
++result;
} else {
++it;
}
} else if constexpr (is_invocable_explicit_r_v<bool, Predicate &, KeyValuePair &&>) {
KeyValuePair p(it.key(), it.value());
if (pred(std::move(p))) {
it = c.erase(it);
++result;
} else {
++it;
}
} else {
static_assert(sizeof(Container) == 0, "Predicate has an incompatible signature");
}
}
return result;
}
} // namespace QtPrivate
QT_END_NAMESPACE

View File

@ -1521,6 +1521,21 @@ size_t qHash(long double key, size_t seed) noexcept
\sa clear(), take()
*/
/*! \fn template <class Key, class T> template <typename Predicate> qsizetype QHash<Key, T>::removeIf(Predicate pred)
\since 6.1
Removes all elements for which the predicate \a pred returns true
from the hash.
The function supports predicates which take either an argument of
type \c{QHash<Key, T>::iterator}, or an argument of type
\c{std::pair<const Key &, T &>}.
Returns the number of elements removed, if any.
\sa clear(), take()
*/
/*! \fn template <class Key, class T> T QHash<Key, T>::take(const Key &key)
Removes the item with the \a key from the hash and returns
@ -2651,6 +2666,21 @@ size_t qHash(long double key, size_t seed) noexcept
\sa remove()
*/
/*! \fn template <class Key, class T> template <typename Predicate> qsizetype QMultiHash<Key, T>::removeIf(Predicate pred)
\since 6.1
Removes all elements for which the predicate \a pred returns true
from the multi hash.
The function supports predicates which take either an argument of
type \c{QMultiHash<Key, T>::iterator}, or an argument of type
\c{std::pair<const Key &, T &>}.
Returns the number of elements removed, if any.
\sa clear(), take()
*/
/*! \fn template <class Key, class T> T QMultiHash<Key, T>::take(const Key &key)
Removes the item with the \a key from the hash and returns
@ -3338,4 +3368,32 @@ size_t qHash(long double key, size_t seed) noexcept
Type \c T must be supported by qHash().
*/
/*! \fn template <typename Key, typename T, typename Predicate> qsizetype erase_if(QHash<Key, T> &hash, Predicate pred)
\relates QHash
\since 6.1
Removes all elements for which the predicate \a pred returns true
from the hash \a hash.
The function supports predicates which take either an argument of
type \c{QHash<Key, T>::iterator}, or an argument of type
\c{std::pair<const Key &, T &>}.
Returns the number of elements removed, if any.
*/
/*! \fn template <typename Key, typename T, typename Predicate> qsizetype erase_if(QMultiHash<Key, T> &hash, Predicate pred)
\relates QMultiHash
\since 6.1
Removes all elements for which the predicate \a pred returns true
from the multi hash \a hash.
The function supports predicates which take either an argument of
type \c{QMultiHash<Key, T>::iterator}, or an argument of type
\c{std::pair<const Key &, T &>}.
Returns the number of elements removed, if any.
*/
QT_END_NAMESPACE

View File

@ -872,6 +872,11 @@ public:
d->erase(it);
return true;
}
template <typename Predicate>
qsizetype removeIf(Predicate pred)
{
return QtPrivate::associative_erase_if(*this, pred);
}
T take(const Key &key)
{
if (isEmpty()) // prevents detaching shared null
@ -1354,6 +1359,11 @@ public:
d->erase(it);
return n;
}
template <typename Predicate>
qsizetype removeIf(Predicate pred)
{
return QtPrivate::associative_erase_if(*this, pred);
}
T take(const Key &key)
{
if (isEmpty()) // prevents detaching shared null
@ -1946,6 +1956,18 @@ inline size_t qHash(const QMultiHash<Key, T> &key, size_t seed = 0)
return hash;
}
template <typename Key, typename T, typename Predicate>
qsizetype erase_if(QHash<Key, T> &hash, Predicate pred)
{
return QtPrivate::associative_erase_if(hash, pred);
}
template <typename Key, typename T, typename Predicate>
qsizetype erase_if(QMultiHash<Key, T> &hash, Predicate pred)
{
return QtPrivate::associative_erase_if(hash, pred);
}
QT_END_NAMESPACE
#endif // QHASH_H

View File

@ -343,6 +343,12 @@ public:
return result;
}
template <typename Predicate>
size_type removeIf(Predicate pred)
{
return QtPrivate::associative_erase_if(*this, pred);
}
T take(const Key &key)
{
if (!d)
@ -742,6 +748,12 @@ public:
Q_DECLARE_ASSOCIATIVE_ITERATOR(Map)
Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR(Map)
template <typename Key, typename T, typename Predicate>
qsizetype erase_if(QMap<Key, T> &map, Predicate pred)
{
return QtPrivate::associative_erase_if(map, pred);
}
//
// QMultiMap
//
@ -938,6 +950,12 @@ public:
return result;
}
template <typename Predicate>
size_type removeIf(Predicate pred)
{
return QtPrivate::associative_erase_if(*this, pred);
}
T take(const Key &key)
{
if (!d)
@ -1441,6 +1459,12 @@ QMultiMap<Key, T> operator+=(QMultiMap<Key, T> &lhs, const QMultiMap<Key, T> &rh
return lhs.unite(rhs);
}
template <typename Key, typename T, typename Predicate>
qsizetype erase_if(QMultiMap<Key, T> &map, Predicate pred)
{
return QtPrivate::associative_erase_if(map, pred);
}
QT_END_NAMESPACE
#endif // QMAP_H

View File

@ -334,6 +334,21 @@
\sa clear(), take()
*/
/*! \fn template <class Key, class T> template <typename Predicate> size_type QMap<Key, T>::removeIf(Predicate pred)
\since 6.1
Removes all elements for which the predicate \a pred returns true
from the map.
The function supports predicates which take either an argument of
type \c{QMap<Key, T>::iterator}, or an argument of type
\c{std::pair<const Key &, T &>}.
Returns the number of elements removed, if any.
\sa clear(), take()
*/
/*! \fn template <class Key, class T> T QMap<Key, T>::take(const Key &key)
Removes the item with the key \a key from the map and returns
@ -1372,3 +1387,17 @@
\sa{Serializing Qt Data Types}{Format of the QDataStream operators}
*/
/*! \fn template <typename Key, typename T, typename Predicate> qsizetype erase_if(QMap<Key, T> &map, Predicate pred)
\relates QMap
\since 6.1
Removes all elements for which the predicate \a pred returns true
from the map \a map.
The function supports predicates which take either an argument of
type \c{QMap<Key, T>::iterator}, or an argument of type
\c{std::pair<const Key &, T &>}.
Returns the number of elements removed, if any.
*/

View File

@ -352,6 +352,21 @@
\sa clear(), take()
*/
/*! \fn template <class Key, class T> template <typename Predicate> size_type QMultiMap<Key, T>::removeIf(Predicate pred)
\since 6.1
Removes all elements for which the predicate \a pred returns true
from the multi map.
The function supports predicates which take either an argument of
type \c{QMultiMap<Key, T>::iterator}, or an argument of type
\c{std::pair<const Key &, T &>}.
Returns the number of elements removed, if any.
\sa clear(), take()
*/
/*! \fn template <class Key, class T> T QMultiMap<Key, T>::take(const Key &key)
Removes the item with the key \a key from the multi map and returns
@ -1508,3 +1523,17 @@
\sa{Serializing Qt Data Types}{Format of the QDataStream operators}
*/
/*! \fn template <typename Key, typename T, typename Predicate> qsizetype erase_if(QMultiMap<Key, T> &map, Predicate pred)
\relates QMultiMap
\since 6.1
Removes all elements for which the predicate \a pred returns true
from the multi map \a map.
The function supports predicates which take either an argument of
type \c{QMultiMap<Key, T>::iterator}, or an argument of type
\c{std::pair<const Key &, T &>}.
Returns the number of elements removed, if any.
*/

View File

@ -334,6 +334,9 @@ private:
template <typename Container>
void erase_if_impl() const;
template <typename Container>
void erase_if_associative_impl() const;
private Q_SLOTS:
void erase_QList() { erase_impl<QList<int>>(); }
void erase_QVarLengthArray() { erase_impl<QVarLengthArray<int>>(); }
@ -355,6 +358,10 @@ private Q_SLOTS:
erase_if_impl<std::vector<int>>();
#endif
}
void erase_if_QMap() { erase_if_associative_impl<QMap<int, int>>(); }
void erase_if_QMultiMap() {erase_if_associative_impl<QMultiMap<int, int>>(); }
void erase_if_QHash() { erase_if_associative_impl<QHash<int, int>>(); }
void erase_if_QMultIHash() { erase_if_associative_impl<QMultiHash<int, int>>(); }
};
void tst_ContainerApiSymmetry::init()
@ -647,6 +654,17 @@ Container make(int size)
return c;
}
template <typename Container>
Container makeAssociative(int size)
{
using K = typename Container::key_type;
using V = typename Container::mapped_type;
Container c;
for (int i = 1; i <= size; ++i)
c.insert(K(i), V(i));
return c;
}
static QString s_string = QStringLiteral("\1\2\3\4\5\6\7");
template <> QString make(int size) { return s_string.left(size); }
@ -728,5 +746,54 @@ void tst_ContainerApiSymmetry::erase_if_impl() const
QCOMPARE(c.size(), S(0));
}
template <typename Container>
void tst_ContainerApiSymmetry::erase_if_associative_impl() const
{
using S = typename Container::size_type;
using K = typename Container::key_type;
using V = typename Container::mapped_type;
using I = typename Container::iterator;
using P = std::pair<const K &, V &>;
auto c = makeAssociative<Container>(20);
QCOMPARE(c.size(), S(20));
auto result = erase_if(c, [](const P &p) { return Conv::toInt(p.first) % 2 == 0; });
QCOMPARE(result, S(10));
QCOMPARE(c.size(), S(10));
result = erase_if(c, [](const P &p) { return Conv::toInt(p.first) % 3 == 0; });
QCOMPARE(result, S(3));
QCOMPARE(c.size(), S(7));
result = erase_if(c, [](const P &p) { return Conv::toInt(p.first) % 42 == 0; });
QCOMPARE(result, S(0));
QCOMPARE(c.size(), S(7));
result = erase_if(c, [](const P &p) { return Conv::toInt(p.first) % 2 == 1; });
QCOMPARE(result, S(7));
QCOMPARE(c.size(), S(0));
// same, but with a predicate taking a Qt iterator
c = makeAssociative<Container>(20);
QCOMPARE(c.size(), S(20));
result = erase_if(c, [](const I &it) { return Conv::toInt(it.key()) % 2 == 0; });
QCOMPARE(result, S(10));
QCOMPARE(c.size(), S(10));
result = erase_if(c, [](const I &it) { return Conv::toInt(it.key()) % 3 == 0; });
QCOMPARE(result, S(3));
QCOMPARE(c.size(), S(7));
result = erase_if(c, [](const I &it) { return Conv::toInt(it.key()) % 42 == 0; });
QCOMPARE(result, S(0));
QCOMPARE(c.size(), S(7));
result = erase_if(c, [](const I &it) { return Conv::toInt(it.key()) % 2 == 1; });
QCOMPARE(result, S(7));
QCOMPARE(c.size(), S(0));
}
QTEST_APPLESS_MAIN(tst_ContainerApiSymmetry)
#include "tst_containerapisymmetry.moc"