diff options
author | Marc Mutz <marc.mutz@qt.io> | 2022-02-25 08:47:48 +0100 |
---|---|---|
committer | Marc Mutz <marc.mutz@qt.io> | 2022-03-03 00:25:14 +0100 |
commit | 6f5c78fe3d445f1c6c8738f9cedb9dbd847645fa (patch) | |
tree | bdc3d95dee98a54ab84dc30b3f5bd469f06af150 /src/corelib/tools | |
parent | b2c7f17b940fb7735ff88d9af6e03729a2fdcdd0 (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.h | 75 |
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); |