summaryrefslogtreecommitdiffstats
path: root/src/corelib/tools
diff options
context:
space:
mode:
authorMarc Mutz <marc.mutz@qt.io>2022-02-25 08:47:48 +0100
committerMarc Mutz <marc.mutz@qt.io>2022-03-03 00:25:14 +0100
commit6f5c78fe3d445f1c6c8738f9cedb9dbd847645fa (patch)
treebdc3d95dee98a54ab84dc30b3f5bd469f06af150 /src/corelib/tools
parentb2c7f17b940fb7735ff88d9af6e03729a2fdcdd0 (diff)
QFlatMap: add remove_if
The existing API of QFlatMap did not allow efficient removal of elements: - std::remove_if does not apply, because it works by moving elements back in the range onto those that need to be removed, which doesn't work in flat_map's case, because, like for all associative containers, the key in value_type is const. - The node-based erase-loop (over it = cond ? c.erase(it) : std::next(it)) works, but, unlike in traditional associative containers, is quadratic, because flat_map::erase is a linear operation. According to Stepanov's principle of Efficient Computational Basis (Elements of Programming, Section 1.4), we're therefore missing API. Add it. I couldn't make up my mind about the calling convention for the predicate and, despite having authored a merged paper about erase_if, can never remember what the predicate is supposed to take, so be fancy and accept all: (*it), (it.key(), it.value()), (it.key()). This means that unary predicates can either not be generic or must be properly constrained to distinguish between pair<const K, V> and K, but that's not necessarily a bad thing. There's no reason to supply a Qt-ified removeIf on top of the standard name, because this is private API and doubling the names would do nothing except double the testing overhead. Fixes: QTBUG-100983 Change-Id: I12545058958fc5d620baa770f92193c8de8b2d26 Reviewed-by: Edward Welbourne <edward.welbourne@qt.io> Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Diffstat (limited to 'src/corelib/tools')
-rw-r--r--src/corelib/tools/qflatmap_p.h75
1 files changed, 75 insertions, 0 deletions
diff --git a/src/corelib/tools/qflatmap_p.h b/src/corelib/tools/qflatmap_p.h
index 18d5dec652..7500cfb42d 100644
--- a/src/corelib/tools/qflatmap_p.h
+++ b/src/corelib/tools/qflatmap_p.h
@@ -863,6 +863,81 @@ public:
return it;
}
+ template <typename Predicate>
+ size_type remove_if(Predicate pred)
+ {
+ const auto indirect_call_to_pred = [pred = std::move(pred)](iterator it) {
+ auto dependent_false = [](auto &&...) { return false; };
+ using Pair = decltype(*it);
+ using K = decltype(it.key());
+ using V = decltype(it.value());
+ using P = Predicate;
+ if constexpr (std::is_invocable_v<P, K, V>) {
+ return pred(it.key(), it.value());
+ } else if constexpr (std::is_invocable_v<P, Pair> && !std::is_invocable_v<P, K>) {
+ return pred(*it);
+ } else if constexpr (std::is_invocable_v<P, K> && !std::is_invocable_v<P, Pair>) {
+ return pred(it.key());
+ } else {
+ static_assert(dependent_false(pred),
+ "Don't know how to call the predicate.\n"
+ "Options:\n"
+ "- pred(*it)\n"
+ "- pred(it.key(), it.value())\n"
+ "- pred(it.key())");
+ }
+ };
+
+ auto first = begin();
+ const auto last = end();
+
+ // find_if prefix loop
+ while (first != last && !indirect_call_to_pred(first))
+ ++first;
+
+ if (first == last)
+ return 0; // nothing to do
+
+ // we know that we need to remove *first
+
+ auto kdest = toKeysIterator(first);
+ auto vdest = toValuesIterator(first);
+
+ ++first;
+
+ auto k = std::next(kdest);
+ auto v = std::next(vdest);
+
+ // Main Loop
+ // - first is used only for indirect_call_to_pred
+ // - operations are done on k, v
+ // Loop invariants:
+ // - first, k, v are pointing to the same element
+ // - [begin(), first[, [c.keys.begin(), k[, [c.values.begin(), v[: already processed
+ // - [first, end()[, [k, c.keys.end()[, [v, c.values.end()[: still to be processed
+ // - [c.keys.begin(), kdest[ and [c.values.begin(), vdest[ are keepers
+ // - [kdest, k[, [vdest, v[ are considered removed
+ // - kdest is not c.keys.end()
+ // - vdest is not v.values.end()
+ while (first != last) {
+ if (!indirect_call_to_pred(first)) {
+ // keep *first, aka {*k, *v}
+ *kdest = std::move(*k);
+ *vdest = std::move(*v);
+ ++kdest;
+ ++vdest;
+ }
+ ++k;
+ ++v;
+ ++first;
+ }
+
+ const size_type r = std::distance(kdest, c.keys.end());
+ c.keys.erase(kdest, c.keys.end());
+ c.values.erase(vdest, c.values.end());
+ return r;
+ }
+
key_compare key_comp() const noexcept
{
return static_cast<key_compare>(*this);