diff --git a/README.md b/README.md index 17c6d82..92da847 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,12 @@ SG14, see [the mailing list](http://lists.isocpp.org/mailman/listinfo.cgi/sg14). ## What's included -### Efficient algorithms +Each subheading of the form "(C++YY > C++XX)" means that some approximation of +this feature is present in C++YY, but the `sg14::` version is intended to compile +as far back as C++XX. A subheading "(future > C++XX)" means that this feature +isn't available in any version of standard C++, yet. + +### Efficient removal algorithms (future > C++14) ``` #include @@ -22,10 +27,37 @@ of the non-removed elements. It doesn't "shuffle elements leftward"; it simply " removed element with `*--last`." These algorithms were proposed in [P0041](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0041r0.html). -Note that `std::erase_if(lst, Pred)` is more efficient than -`lst.erase(sg14::unstable_remove_if(lst, Pred), lst.end())` when -`lst` is a `std::list`; therefore P0041 also proposed a set of -`sg14::unstable_erase_if` overloads, which we don't provide, yet. +#### Why not `sg14::unstable_erase(ctr, x)`? + +When `lst` is a `std::list`, +`std::erase(lst, x)` is more efficient than +`lst.erase(sg14::unstable_remove(lst.begin(), lst.end(), x), lst.end())`. +Therefore P0041 also proposed new overload sets `std::unstable_erase` +and `std::unstable_erase_if`. + +We don't provide `sg14::unstable_erase{,_if}` +mainly because there's no good way to do it without either +including every container's header (``, ``, ``, +``, ``,...), which is expensive; or else forward-declaring every container, +which is implementation-defined and error-prone. + +There is also the minor question of whether, when `dq` is a `deque`, +`unstable_erase_if(dq, pred)` should be implemented as +``` + dq.erase(dq.begin(), std::find_if_not(dq.begin(), dq.end(), pred)); + dq.erase(sg14::unstable_remove_if(dq.begin(), dq.end(), pred), dq.end()); +``` +or +``` + dq.erase(std::find_if_not(dq.rbegin(), dq.rend(), pred).base(), dq.end()); + dq.erase(dq.begin(), sg14::unstable_remove_if(dq.rbegin(), dq.rend(), pred).base()); +``` +The answer depends not only on the internal state of the deque (known only to the +STL implementation) but also on the expected workload (known only to the user-programmer): +If we plan to `push_back` soon, then it makes sense to swap items toward the front of the +deque, but if we plan to `push_front`, then it makes more sense to swap toward the back. + +### Uninitialized memory algorithms (C++17 > C++14) ``` FwdIt sg14::uninitialized_move(It first, Sent last, FwdIt d_first); @@ -40,7 +72,7 @@ and adopted into C++17. The `sg14` versions are slightly more general (they take iterator-sentinel pairs) and portable back to C++11, but in C++17 you should use the `std` versions, please. -### Flat associative container adaptors +### Flat associative container adaptors (C++23 > C++14) ``` #include @@ -64,7 +96,7 @@ C++23 also provides `flat_multimap` and `flat_multiset`, which we don't provide. Boost also provides all four adaptors; see [`boost::container::flat_set`](https://www.boost.org/doc/libs/1_81_0/doc/html/container/non_standard_containers.html#container.non_standard_containers.flat_xxx). -### In-place type-erased types +### In-place type-erased types (future > C++14) ``` #include @@ -112,7 +144,7 @@ follows that same design. For example: In practice, most of your uses of `std::function` should be replaced not with `sg14::inplace_function` but rather with `sg14::inplace_function`. -### Circular `ring_span` +### Circular `ring_span` (future > C++14) ``` #include @@ -140,7 +172,7 @@ This adaptor was proposed in [P0059](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0059r4.pdf). Martin Moene has another implementation at [martinmoene/ring-span-lite](https://github.com/martinmoene/ring-span-lite). -### `slot_map` +### `slot_map` (future > C++14) ``` #include @@ -171,7 +203,7 @@ back into a dereferenceable `iterator`: This container adaptor was proposed in [P0661](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0661r0.pdf). -### `hive` (C++17 and later) +### `hive` (future > C++17) ``` #include diff --git a/test/unstable_remove_test.cpp b/test/unstable_remove_test.cpp index 31e4851..962a4ef 100644 --- a/test/unstable_remove_test.cpp +++ b/test/unstable_remove_test.cpp @@ -5,8 +5,10 @@ #include #include #include +#include #include #include +#include #include struct foo @@ -20,6 +22,50 @@ struct foo } }; +TEST(unstable_remove, deque_examples) +{ + std::mt19937 g; + std::deque original; + for (int i = 0; i < 10'000; ++i) { + original.push_back(g()); + } + auto pred = [](int x) { return (x % 2) == 0; }; + + auto expected = original; + expected.erase(std::remove_if(expected.begin(), expected.end(), pred), expected.end()); + + { + auto dq = original; + dq.erase(sg14::unstable_remove_if(dq.begin(), dq.end(), pred), dq.end()); + + EXPECT_EQ(dq.size(), expected.size()); + EXPECT_TRUE(std::is_permutation(dq.begin(), dq.end(), expected.begin(), expected.end())); + } + { + auto dq = original; + dq.erase(dq.begin(), std::find_if_not(dq.begin(), dq.end(), pred)); + dq.erase(sg14::unstable_remove_if(dq.begin(), dq.end(), pred), dq.end()); + + EXPECT_EQ(dq.size(), expected.size()); + EXPECT_TRUE(std::is_permutation(dq.begin(), dq.end(), expected.begin(), expected.end())); + } + { + auto dq = original; + dq.erase(dq.begin(), sg14::unstable_remove_if(dq.rbegin(), dq.rend(), pred).base()); + + EXPECT_EQ(dq.size(), expected.size()); + EXPECT_TRUE(std::is_permutation(dq.begin(), dq.end(), expected.begin(), expected.end())); + } + { + auto dq = original; + dq.erase(std::find_if_not(dq.rbegin(), dq.rend(), pred).base(), dq.end()); + dq.erase(dq.begin(), sg14::unstable_remove_if(dq.rbegin(), dq.rend(), pred).base()); + + EXPECT_EQ(dq.size(), expected.size()); + EXPECT_TRUE(std::is_permutation(dq.begin(), dq.end(), expected.begin(), expected.end())); + } +} + TEST(unstable_remove, all) { size_t test_runs = 200;