diff --git a/doc/unordered/changes.adoc b/doc/unordered/changes.adoc index 4064aa89..79a28a51 100644 --- a/doc/unordered/changes.adoc +++ b/doc/unordered/changes.adoc @@ -11,7 +11,8 @@ * Improved {cpp}20 support: ** All containers have been updated to support heterogeneous `count`, `equal_range` and `find`. - ** All containers now implement the member function `contains` + ** All containers now implement the member function `contains`. + ** `erase_if` has been implemented for all containers. * Improved {cpp}23 support: ** All containers have been updated to support heterogeneous `erase` and `extract`. diff --git a/doc/unordered/unordered_map.adoc b/doc/unordered/unordered_map.adoc index e8737402..9610d853 100644 --- a/doc/unordered/unordered_map.adoc +++ b/doc/unordered/unordered_map.adoc @@ -198,6 +198,10 @@ template void xref:#unordered_map_swap_2[swap](unordered_map& x, unordered_map& y) noexcept(noexcept(x.swap(y))); + +template + typename unordered_map::size_type + xref:#unordered_map_erase_if[erase_if](unordered_map& c, Predicate pred); ----- --- @@ -1424,4 +1428,31 @@ Effects:;; `x.swap(y)` Throws:;; Doesn't throw an exception unless it is thrown by the copy constructor or copy assignment operator of `key_equal` or `hasher`. Notes:;; The exception specifications aren't quite the same as the C++11 standard, as the equality predicate and hash function are swapped using their copy constructors. +--- +=== erase_if +```c++ +template + typename unordered_map::size_type + erase_if(unordered_map& c, Predicate pred); +``` + +Traverses the container `c` and removes all elements for which the supplied predicate returns `true`. + +[horizontal] +Returns:;; The number of erased elements. +Notes:;; Equivalent to: + ++ +```c++ +auto original_size = c.size(); +for (auto i = c.begin(), last = c.end(); i != last; ) { + if (pred(*i)) { + i = c.erase(i); + } else { + ++i; + } +} +return original_size - c.size(); +``` + +--- diff --git a/doc/unordered/unordered_multimap.adoc b/doc/unordered/unordered_multimap.adoc index 8eb8fb30..bd6745f9 100644 --- a/doc/unordered/unordered_multimap.adoc +++ b/doc/unordered/unordered_multimap.adoc @@ -193,6 +193,11 @@ template void xref:#unordered_multimap_swap_2[swap](unordered_multimap& x, unordered_multimap& y) noexcept(noexcept(x.swap(y))); + +template + typename unordered_multimap::size_type + xref:#unordered_multimap_erase_if[erase_if](unordered_multimap& c, Predicate pred); + ----- --- @@ -1368,4 +1373,31 @@ Effects:;; `x.swap(y)` Throws:;; Doesn't throw an exception unless it is thrown by the copy constructor or copy assignment operator of `key_equal` or `hasher`. Notes:;; The exception specifications aren't quite the same as the C++11 standard, as the equality predicate and hash function are swapped using their copy constructors. +--- +=== erase_if +```c++ +template + typename unordered_multimap::size_type + erase_if(unordered_multimap& c, Predicate pred); +``` + +Traverses the container `c` and removes all elements for which the supplied predicate returns `true`. + +[horizontal] +Returns:;; The number of erased elements. +Notes:;; Equivalent to: + ++ +```c++ +auto original_size = c.size(); +for (auto i = c.begin(), last = c.end(); i != last; ) { + if (pred(*i)) { + i = c.erase(i); + } else { + ++i; + } +} +return original_size - c.size(); +``` + +--- diff --git a/doc/unordered/unordered_multiset.adoc b/doc/unordered/unordered_multiset.adoc index 122a1b27..d900799d 100644 --- a/doc/unordered/unordered_multiset.adoc +++ b/doc/unordered/unordered_multiset.adoc @@ -187,6 +187,10 @@ template void xref:#unordered_multiset_swap_2[swap](unordered_multiset& x, unordered_multiset& y) noexcept(noexcept(x.swap(y))); + +template + typename unordered_multiset::size_type + xref:#unordered_multiset_erase_if[erase_if](unordered_multiset& c, Predicate pred); ----- --- @@ -1326,4 +1330,31 @@ Effects:;; `x.swap(y)` Throws:;; Doesn't throw an exception unless it is thrown by the copy constructor or copy assignment operator of `key_equal` or `hasher`. Notes:;; The exception specifications aren't quite the same as the C++11 standard, as the equality predicate and hash function are swapped using their copy constructors. +--- +=== erase_if +```c++ +template + typename unordered_multiset::size_type + erase_if(unordered_multiset& c, Predicate pred); +``` + +Traverses the container `c` and removes all elements for which the supplied predicate returns `true`. + +[horizontal] +Returns:;; The number of erased elements. +Notes:;; Equivalent to: + ++ +```c++ +auto original_size = c.size(); +for (auto i = c.begin(), last = c.end(); i != last; ) { + if (pred(*i)) { + i = c.erase(i); + } else { + ++i; + } +} +return original_size - c.size(); +``` + +--- diff --git a/doc/unordered/unordered_set.adoc b/doc/unordered/unordered_set.adoc index e976b97a..1987b317 100644 --- a/doc/unordered/unordered_set.adoc +++ b/doc/unordered/unordered_set.adoc @@ -188,6 +188,10 @@ template void xref:#unordered_set_swap_2[swap](unordered_set& x, unordered_set& y) noexcept(noexcept(x.swap(y))); + +template + typename unordered_set::size_type + xref:#unordered_set_erase_if[erase_if](unordered_set& c, Predicate pred); ----- --- @@ -1349,3 +1353,30 @@ Throws:;; Doesn't throw an exception unless it is thrown by the copy constructor Notes:;; The exception specifications aren't quite the same as the C++11 standard, as the equality predicate and hash function are swapped using their copy constructors. --- + +=== erase_if +```c++ +template + typename unordered_set::size_type + erase_if(unordered_set& c, Predicate pred); +``` + +Traverses the container `c` and removes all elements for which the supplied predicate returns `true`. + +[horizontal] +Returns:;; The number of erased elements. +Notes:;; Equivalent to: + ++ +```c++ +auto original_size = c.size(); +for (auto i = c.begin(), last = c.end(); i != last; ) { + if (pred(*i)) { + i = c.erase(i); + } else { + ++i; + } +} +return original_size - c.size(); +``` + +--- diff --git a/include/boost/unordered/detail/implementation.hpp b/include/boost/unordered/detail/implementation.hpp index 02ae8fc2..668a5984 100644 --- a/include/boost/unordered/detail/implementation.hpp +++ b/include/boost/unordered/detail/implementation.hpp @@ -4360,6 +4360,25 @@ namespace boost { typedef typename pick::bucket bucket; typedef typename pick::link_pointer link_pointer; }; + + template + typename Container::size_type erase_if(Container& c, Predicate& pred) + { + typedef typename Container::size_type size_type; + typedef typename Container::iterator iterator; + + size_type const size = c.size(); + + for (iterator pos = c.begin(), end = c.end(); pos != end;) { + if (pred(*pos)) { + pos = c.erase(pos); + } else { + ++pos; + } + } + + return (size - c.size()); + } } } } diff --git a/include/boost/unordered/unordered_map.hpp b/include/boost/unordered/unordered_map.hpp index a7815d87..d5b75cd0 100644 --- a/include/boost/unordered/unordered_map.hpp +++ b/include/boost/unordered/unordered_map.hpp @@ -2168,6 +2168,13 @@ namespace boost { m1.swap(m2); } + template + typename unordered_map::size_type erase_if( + unordered_map& c, Predicate pred) + { + return detail::erase_if(c, pred); + } + //////////////////////////////////////////////////////////////////////////// template @@ -2613,6 +2620,13 @@ namespace boost { m1.swap(m2); } + template + typename unordered_multimap::size_type erase_if( + unordered_multimap& c, Predicate pred) + { + return detail::erase_if(c, pred); + } + template class node_handle_map { BOOST_MOVABLE_BUT_NOT_COPYABLE(node_handle_map) diff --git a/include/boost/unordered/unordered_map_fwd.hpp b/include/boost/unordered/unordered_map_fwd.hpp index 794b7042..4f45841b 100644 --- a/include/boost/unordered/unordered_map_fwd.hpp +++ b/include/boost/unordered/unordered_map_fwd.hpp @@ -34,6 +34,10 @@ namespace boost { unordered_map& m1, unordered_map& m2) BOOST_NOEXCEPT_IF(BOOST_NOEXCEPT_EXPR(m1.swap(m2))); + template + typename unordered_map::size_type erase_if( + unordered_map& c, Predicate pred); + template , class P = std::equal_to, class A = std::allocator > > @@ -50,6 +54,10 @@ namespace boost { unordered_multimap& m2) BOOST_NOEXCEPT_IF(BOOST_NOEXCEPT_EXPR(m1.swap(m2))); + template + typename unordered_multimap::size_type erase_if( + unordered_multimap& c, Predicate pred); + template class node_handle_map; template struct insert_return_type_map; } diff --git a/include/boost/unordered/unordered_set.hpp b/include/boost/unordered/unordered_set.hpp index 57133eda..ab096b04 100644 --- a/include/boost/unordered/unordered_set.hpp +++ b/include/boost/unordered/unordered_set.hpp @@ -1676,6 +1676,13 @@ namespace boost { m1.swap(m2); } + template + typename unordered_set::size_type erase_if( + unordered_set& c, Predicate pred) + { + return detail::erase_if(c, pred); + } + //////////////////////////////////////////////////////////////////////////// template @@ -2079,6 +2086,13 @@ namespace boost { m1.swap(m2); } + template + typename unordered_multiset::size_type erase_if( + unordered_multiset& c, Predicate pred) + { + return detail::erase_if(c, pred); + } + template class node_handle_set { BOOST_MOVABLE_BUT_NOT_COPYABLE(node_handle_set) diff --git a/include/boost/unordered/unordered_set_fwd.hpp b/include/boost/unordered/unordered_set_fwd.hpp index 35e2820f..10a29ac6 100644 --- a/include/boost/unordered/unordered_set_fwd.hpp +++ b/include/boost/unordered/unordered_set_fwd.hpp @@ -33,6 +33,10 @@ namespace boost { unordered_set& m1, unordered_set& m2) BOOST_NOEXCEPT_IF(BOOST_NOEXCEPT_EXPR(m1.swap(m2))); + template + typename unordered_set::size_type erase_if( + unordered_set& c, Predicate pred); + template , class P = std::equal_to, class A = std::allocator > class unordered_multiset; @@ -48,6 +52,10 @@ namespace boost { unordered_multiset& m1, unordered_multiset& m2) BOOST_NOEXCEPT_IF(BOOST_NOEXCEPT_EXPR(m1.swap(m2))); + template + typename unordered_multiset::size_type erase_if( + unordered_multiset& c, Predicate pred); + template class node_handle_set; template struct insert_return_type_set; } diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 72d5b5ec..698dff92 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -76,6 +76,7 @@ test-suite unordered [ run unordered/reserve_tests.cpp ] [ run unordered/contains_tests.cpp ] [ run unordered/mix_policy.cpp ] + [ run unordered/erase_if.cpp ] [ run unordered/compile_set.cpp : : : BOOST_UNORDERED_USE_MOVE diff --git a/test/unordered/erase_if.cpp b/test/unordered/erase_if.cpp new file mode 100644 index 00000000..de3f5411 --- /dev/null +++ b/test/unordered/erase_if.cpp @@ -0,0 +1,168 @@ +// Copyright 2021 Christian Mazakas. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// clang-format off +#include "../helpers/prefix.hpp" +#include +#include +#include "../helpers/postfix.hpp" +// clang-format on + +#include "../helpers/test.hpp" + +#include + +#if BOOST_CXX_VERSION >= 201103L +#define UNORDERED_LVALUE_QUAL & +#else +#define UNORDERED_LVALUE_QUAL +#endif + +namespace test { + struct is_even + { + is_even() {} + +#if BOOST_CXX_VERSION >= 201703L + // immovable for C++17 + is_even(is_even const&) = delete; + is_even(is_even&&) = delete; + + is_even& operator=(is_even const&) = delete; + is_even& operator=(is_even&&) = delete; +#elif BOOST_CXX_VERSION >= 201103L + // move-only for C++11 + is_even(is_even const&) = delete; + is_even(is_even&&) = default; + + is_even& operator=(is_even const&) = delete; + is_even& operator=(is_even&&) = default; +#else + // copyable otherwise + is_even(is_even const&) {} + is_even& operator=(is_even const&) { return *this; } +#endif + + bool operator()( + std::pair& key_value) UNORDERED_LVALUE_QUAL + { + int const v = key_value.second; + return (v % 2 == 0); + } + + bool operator()(int const& value) UNORDERED_LVALUE_QUAL + { + int const v = value; + return (v % 2 == 0); + } + }; + + struct is_too_large + { + is_too_large() {} + +#if BOOST_CXX_VERSION >= 201703L + // immovable for C++17 + is_too_large(is_too_large const&) = delete; + is_too_large(is_too_large&&) = delete; + + is_too_large& operator=(is_too_large const&) = delete; + is_too_large& operator=(is_too_large&&) = delete; +#elif BOOST_CXX_VERSION >= 201103L + // move-only for C++11 + is_too_large(is_too_large const&) = delete; + is_too_large(is_too_large&&) = default; + + is_too_large& operator=(is_too_large const&) = delete; + is_too_large& operator=(is_too_large&&) = default; +#else + // copyable otherwise + is_too_large(is_too_large const&) {} + + is_too_large& operator=(is_too_large const&) { return *this; } +#endif + + bool operator()( + std::pair& key_value) UNORDERED_LVALUE_QUAL + { + int const v = key_value.second; + return v >= 1000; + } + + bool operator()(int const& value) UNORDERED_LVALUE_QUAL + { + int const v = value; + return v >= 1000; + } + }; + +} // namespace test + +template void test_map_erase_if() +{ + typedef UnorderedMap map_type; + typedef typename map_type::size_type size_type; + + map_type map; + size_type num_erased = erase_if(map, test::is_even()); + BOOST_TEST(map.empty()); + BOOST_TEST_EQ(num_erased, 0u); + + map.emplace("a", 1); + map.emplace("b", 2); + map.emplace("b", 4); + map.emplace("b", 8); + map.emplace("b", 16); + map.emplace("c", 3); + + size_type size = map.size(); + + num_erased = erase_if(map, test::is_too_large()); + + BOOST_TEST_EQ(map.size(), size); + BOOST_TEST_EQ(num_erased, 0u); + + num_erased = erase_if(map, test::is_even()); + BOOST_TEST_EQ(map.size(), 2u); + BOOST_TEST_EQ(num_erased, size - map.size()); +} + +template void test_set_erase_if() +{ + typedef UnorderedSet set_type; + typedef typename set_type::size_type size_type; + + set_type set; + size_type num_erased = erase_if(set, test::is_even()); + BOOST_TEST(set.empty()); + BOOST_TEST_EQ(num_erased, 0u); + + set.emplace(1); + set.emplace(2); + set.emplace(2); + set.emplace(2); + set.emplace(2); + set.emplace(3); + + size_type size = set.size(); + + num_erased = erase_if(set, test::is_too_large()); + + BOOST_TEST_EQ(set.size(), size); + BOOST_TEST_EQ(num_erased, 0u); + + num_erased = erase_if(set, test::is_even()); + BOOST_TEST_EQ(set.size(), 2u); + BOOST_TEST_EQ(num_erased, size - set.size()); +} + +UNORDERED_AUTO_TEST (unordered_erase_if) { + test_map_erase_if >(); + test_map_erase_if >(); + + test_set_erase_if >(); + test_set_erase_if >(); +} + +RUN_TESTS()