[libcxx-commits] [libcxx] f8cbe3c - [libc++] Implement ranges::remove{, _if}

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Wed Jul 6 09:47:23 PDT 2022


Author: Nikolas Klauser
Date: 2022-07-06T18:47:13+02:00
New Revision: f8cbe3cdf024865eaa26c14fc8e8f43b3cad455a

URL: https://github.com/llvm/llvm-project/commit/f8cbe3cdf024865eaa26c14fc8e8f43b3cad455a
DIFF: https://github.com/llvm/llvm-project/commit/f8cbe3cdf024865eaa26c14fc8e8f43b3cad455a.diff

LOG: [libc++] Implement ranges::remove{, _if}

Reviewed By: var-const, #libc

Spies: huixie90, sstefan1, libcxx-commits, mgorny

Differential Revision: https://reviews.llvm.org/D128618

Added: 
    libcxx/include/__algorithm/ranges_remove.h
    libcxx/include/__algorithm/ranges_remove_if.h
    libcxx/test/std/algorithms/alg.modifying.operations/alg.remove/ranges.remove.pass.cpp
    libcxx/test/std/algorithms/alg.modifying.operations/alg.remove/ranges.remove_if.pass.cpp

Modified: 
    libcxx/docs/Status/RangesAlgorithms.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/algorithm
    libcxx/include/module.modulemap.in
    libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp
    libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp
    libcxx/test/libcxx/private_headers.verify.cpp
    libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/RangesAlgorithms.csv b/libcxx/docs/Status/RangesAlgorithms.csv
index 91468f1841705..4eda4ee86b51e 100644
--- a/libcxx/docs/Status/RangesAlgorithms.csv
+++ b/libcxx/docs/Status/RangesAlgorithms.csv
@@ -65,8 +65,8 @@ Merge,set_
diff erence,Hui Xie,n/a,Not started
 Merge,set_intersection,Hui Xie,n/a,Not started
 Merge,set_symmetric_
diff erence,Hui Xie,n/a,Not started
 Merge,set_union,Not assigned,Hui Xie,Not started
-Permutation,remove,Not assigned,n/a,Not started
-Permutation,remove_if,Not assigned,n/a,Not started
+Permutation,remove,Nikolas Klauser,`D128618 <https://llvm.org/D128618>`,✅
+Permutation,remove_if,Nikolas Klauser,`D128618 <https://llvm.org/D128618>`,✅
 Permutation,reverse,Nikolas Klauser,`D125752 <https://llvm.org/D125752>`_,✅
 Permutation,rotate,Nikolas Klauser,`D124122 <https://llvm.org/D124122>`_,Under review
 Permutation,shuffle,Not assigned,n/a,Not started

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 621dc3171e26a..479c4090d083c 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -103,6 +103,8 @@ set(files
   __algorithm/ranges_move.h
   __algorithm/ranges_move_backward.h
   __algorithm/ranges_none_of.h
+  __algorithm/ranges_remove.h
+  __algorithm/ranges_remove_if.h
   __algorithm/ranges_replace.h
   __algorithm/ranges_replace_if.h
   __algorithm/ranges_reverse.h

diff  --git a/libcxx/include/__algorithm/ranges_remove.h b/libcxx/include/__algorithm/ranges_remove.h
new file mode 100644
index 0000000000000..a6a1200763d20
--- /dev/null
+++ b/libcxx/include/__algorithm/ranges_remove.h
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___ALGORITHM_RANGES_REMOVE_H
+#define _LIBCPP___ALGORITHM_RANGES_REMOVE_H
+#include <__config>
+
+#include <__algorithm/ranges_remove_if.h>
+#include <__functional/identity.h>
+#include <__functional/ranges_operations.h>
+#include <__iterator/concepts.h>
+#include <__iterator/permutable.h>
+#include <__iterator/projected.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/subrange.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace ranges {
+namespace __remove {
+struct __fn {
+
+  template <permutable _Iter, sentinel_for<_Iter> _Sent, class _Type, class _Proj = identity>
+    requires indirect_binary_predicate<ranges::equal_to, projected<_Iter, _Proj>, const _Type*>
+  _LIBCPP_HIDE_FROM_ABI constexpr
+  subrange<_Iter> operator()(_Iter __first, _Sent __last, const _Type& __value, _Proj __proj = {}) const {
+    auto __pred = [&](auto&& __other) { return __value == __other; };
+    return ranges::__remove_if_impl(std::move(__first), std::move(__last), __pred, __proj);
+  }
+
+  template <forward_range _Range, class _Type, class _Proj = identity>
+    requires permutable<iterator_t<_Range>>
+          && indirect_binary_predicate<ranges::equal_to, projected<iterator_t<_Range>, _Proj>, const _Type*>
+  _LIBCPP_HIDE_FROM_ABI constexpr
+  borrowed_subrange_t<_Range> operator()(_Range&& __range, const _Type& __value, _Proj __proj = {}) const {
+    auto __pred = [&](auto&& __other) { return __value == __other; };
+    return ranges::__remove_if_impl(ranges::begin(__range), ranges::end(__range), __pred, __proj);
+  }
+};
+} // namespace __remove
+
+inline namespace __cpo {
+  inline constexpr auto remove = __remove::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+#endif // _LIBCPP___ALGORITHM_RANGES_REMOVE_H

diff  --git a/libcxx/include/__algorithm/ranges_remove_if.h b/libcxx/include/__algorithm/ranges_remove_if.h
new file mode 100644
index 0000000000000..d4e382e551c66
--- /dev/null
+++ b/libcxx/include/__algorithm/ranges_remove_if.h
@@ -0,0 +1,85 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___ALGORITHM_RANGES_REMOVE_IF_H
+#define _LIBCPP___ALGORITHM_RANGES_REMOVE_IF_H
+#include <__config>
+
+#include <__algorithm/ranges_find_if.h>
+#include <__functional/identity.h>
+#include <__functional/invoke.h>
+#include <__functional/ranges_operations.h>
+#include <__iterator/concepts.h>
+#include <__iterator/iter_move.h>
+#include <__iterator/permutable.h>
+#include <__iterator/projected.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/subrange.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace ranges {
+
+template <class _Iter, class _Sent, class _Proj, class _Pred>
+_LIBCPP_HIDE_FROM_ABI constexpr
+subrange<_Iter> __remove_if_impl(_Iter __first, _Sent __last, _Pred& __pred, _Proj& __proj) {
+  auto __new_end = ranges::__find_if_impl(__first, __last, __pred, __proj);
+  if (__new_end == __last)
+    return {__new_end, __new_end};
+
+  _Iter __i = __new_end;
+  while (++__i != __last) {
+    if (!std::invoke(__pred, std::invoke(__proj, *__i))) {
+      *__new_end = ranges::iter_move(__i);
+      ++__new_end;
+    }
+  }
+  return {__new_end, __i};
+}
+
+namespace __remove_if {
+struct __fn {
+
+  template <permutable _Iter, sentinel_for<_Iter> _Sent,
+            class _Proj = identity,
+            indirect_unary_predicate<projected<_Iter, _Proj>> _Pred>
+  _LIBCPP_HIDE_FROM_ABI constexpr
+  subrange<_Iter> operator()(_Iter __first, _Sent __last, _Pred __pred, _Proj __proj = {}) const {
+    return ranges::__remove_if_impl(std::move(__first), std::move(__last), __pred, __proj);
+  }
+
+  template <forward_range _Range,
+            class _Proj = identity,
+            indirect_unary_predicate<projected<iterator_t<_Range>, _Proj>> _Pred>
+    requires permutable<iterator_t<_Range>>
+  _LIBCPP_HIDE_FROM_ABI constexpr
+  borrowed_subrange_t<_Range> operator()(_Range&& __range, _Pred __pred, _Proj __proj = {}) const {
+    return ranges::__remove_if_impl(ranges::begin(__range), ranges::end(__range), __pred, __proj);
+  }
+
+};
+} // namespace __remove_if
+
+inline namespace __cpo {
+  inline constexpr auto remove_if = __remove_if::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+#endif // _LIBCPP___ALGORITHM_RANGES_REMOVE_IF_H

diff  --git a/libcxx/include/algorithm b/libcxx/include/algorithm
index 76bdf3b85a656..a7236a31d7061 100644
--- a/libcxx/include/algorithm
+++ b/libcxx/include/algorithm
@@ -511,6 +511,26 @@ namespace ranges {
       merge(R1&& r1, R2&& r2, O result,
             Comp comp = {}, Proj1 proj1 = {}, Proj2 proj2 = {});                                    // since C++20
 
+  template<permutable I, sentinel_for<I> S, class T, class Proj = identity>
+    requires indirect_binary_predicate<ranges::equal_to, projected<I, Proj>, const T*>
+    constexpr subrange<I> ranges::remove(I first, S last, const T& value, Proj proj = {});          // since C++20
+
+  template<forward_range R, class T, class Proj = identity>
+    requires permutable<iterator_t<R>> &&
+             indirect_binary_predicate<ranges::equal_to, projected<iterator_t<R>, Proj>, const T*>
+    constexpr borrowed_subrange_t<R>
+      ranges::remove(R&& r, const T& value, Proj proj = {});                                        // since C++20
+
+  template<permutable I, sentinel_for<I> S, class Proj = identity,
+           indirect_unary_predicate<projected<I, Proj>> Pred>
+    constexpr subrange<I> ranges::remove_if(I first, S last, Pred pred, Proj proj = {});            // since C++20
+
+  template<forward_range R, class Proj = identity,
+           indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
+    requires permutable<iterator_t<R>>
+    constexpr borrowed_subrange_t<R>
+      ranges::remove_if(R&& r, Pred pred, Proj proj = {});                                          // since C++20
+
 }
 
     constexpr bool     // constexpr in C++20
@@ -1275,6 +1295,8 @@ template <class BidirectionalIterator, class Compare>
 #include <__algorithm/ranges_move.h>
 #include <__algorithm/ranges_move_backward.h>
 #include <__algorithm/ranges_none_of.h>
+#include <__algorithm/ranges_remove.h>
+#include <__algorithm/ranges_remove_if.h>
 #include <__algorithm/ranges_replace.h>
 #include <__algorithm/ranges_replace_if.h>
 #include <__algorithm/ranges_reverse.h>

diff  --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index becfec44ea7b1..b547b50ab2b1a 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -342,6 +342,8 @@ module std [system] {
       module ranges_move                     { private header "__algorithm/ranges_move.h" }
       module ranges_move_backward            { private header "__algorithm/ranges_move_backward.h" }
       module ranges_none_of                  { private header "__algorithm/ranges_none_of.h" }
+      module ranges_remove                   { private header "__algorithm/ranges_remove.h" }
+      module ranges_remove_if                { private header "__algorithm/ranges_remove_if.h" }
       module ranges_replace                  { private header "__algorithm/ranges_replace.h" }
       module ranges_replace_if               { private header "__algorithm/ranges_replace_if.h" }
       module ranges_reverse                  { private header "__algorithm/ranges_reverse.h" }

diff  --git a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp
index 9d6c80f28f68a..4652e2c384463 100644
--- a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp
+++ b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp
@@ -189,8 +189,8 @@ constexpr bool all_the_algorithms()
     //(void)std::ranges::push_heap(a, Less(&copies)); assert(copies == 0);
     //(void)std::ranges::remove_copy_if(first, last, first2, UnaryTrue(&copies)); assert(copies == 0);
     //(void)std::ranges::remove_copy_if(a, first2, UnaryTrue(&copies)); assert(copies == 0);
-    //(void)std::ranges::remove_if(first, last, UnaryTrue(&copies)); assert(copies == 0);
-    //(void)std::ranges::remove_if(a, UnaryTrue(&copies)); assert(copies == 0);
+    (void)std::ranges::remove_if(first, last, UnaryTrue(&copies)); assert(copies == 0);
+    (void)std::ranges::remove_if(a, UnaryTrue(&copies)); assert(copies == 0);
     //(void)std::ranges::replace_copy_if(first, last, first2, UnaryTrue(&copies), value); assert(copies == 0);
     //(void)std::ranges::replace_copy_if(a, first2, UnaryTrue(&copies), value); assert(copies == 0);
     (void)std::ranges::replace_if(first, last, UnaryTrue(&copies), value); assert(copies == 0);

diff  --git a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp
index 75a932a398fe4..e47f675c18bd1 100644
--- a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp
+++ b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp
@@ -174,10 +174,10 @@ constexpr bool all_the_algorithms()
     //(void)std::ranges::remove_copy(a, first2, value, Proj(&copies)); assert(copies == 0);
     //(void)std::ranges::remove_copy_if(first, last, first2, UnaryTrue(), Proj(&copies)); assert(copies == 0);
     //(void)std::ranges::remove_copy_if(a, first2, UnaryTrue(), Proj(&copies)); assert(copies == 0);
-    //(void)std::ranges::remove(first, last, value, Proj(&copies)); assert(copies == 0);
-    //(void)std::ranges::remove(a, value, Proj(&copies)); assert(copies == 0);
-    //(void)std::ranges::remove_if(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 0);
-    //(void)std::ranges::remove_if(a, UnaryTrue(), Proj(&copies)); assert(copies == 0);
+    (void)std::ranges::remove(first, last, value, Proj(&copies)); assert(copies == 0);
+    (void)std::ranges::remove(a, value, Proj(&copies)); assert(copies == 0);
+    (void)std::ranges::remove_if(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 0);
+    (void)std::ranges::remove_if(a, UnaryTrue(), Proj(&copies)); assert(copies == 0);
     //(void)std::ranges::replace_copy(first, last, first2, value, T(), Proj(&copies)); assert(copies == 0);
     //(void)std::ranges::replace_copy(a, first2, value, T(), Proj(&copies)); assert(copies == 0);
     //(void)std::ranges::replace_copy_if(first, last, first2, UnaryTrue(), T(), Proj(&copies)); assert(copies == 0);

diff  --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp
index cf75caf4409ea..ae5ff43e8be5b 100644
--- a/libcxx/test/libcxx/private_headers.verify.cpp
+++ b/libcxx/test/libcxx/private_headers.verify.cpp
@@ -140,6 +140,8 @@ END-SCRIPT
 #include <__algorithm/ranges_move.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_move.h'}}
 #include <__algorithm/ranges_move_backward.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_move_backward.h'}}
 #include <__algorithm/ranges_none_of.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_none_of.h'}}
+#include <__algorithm/ranges_remove.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_remove.h'}}
+#include <__algorithm/ranges_remove_if.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_remove_if.h'}}
 #include <__algorithm/ranges_replace.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_replace.h'}}
 #include <__algorithm/ranges_replace_if.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_replace_if.h'}}
 #include <__algorithm/ranges_reverse.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_reverse.h'}}

diff  --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.remove/ranges.remove.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.remove/ranges.remove.pass.cpp
new file mode 100644
index 0000000000000..5faf741ac5705
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.remove/ranges.remove.pass.cpp
@@ -0,0 +1,207 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <algorithm>
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<permutable I, sentinel_for<I> S, class T, class Proj = identity>
+//   requires indirect_binary_predicate<ranges::equal_to, projected<I, Proj>, const T*>
+//   constexpr subrange<I> ranges::remove(I first, S last, const T& value, Proj proj = {});
+// template<forward_range R, class T, class Proj = identity>
+//   requires permutable<iterator_t<R>> &&
+//            indirect_binary_predicate<ranges::equal_to, projected<iterator_t<R>, Proj>, const T*>
+//   constexpr borrowed_subrange_t<R>
+//     ranges::remove(R&& r, const T& value, Proj proj = {});
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <ranges>
+
+#include "almost_satisfies_types.h"
+#include "boolean_testable.h"
+#include "test_iterators.h"
+
+template <class Iter, class Sent = sentinel_wrapper<Iter>>
+concept HasRemoveIt = requires(Iter first, Sent last) { std::ranges::remove(first, last, 0); };
+
+static_assert(HasRemoveIt<int*>);
+static_assert(!HasRemoveIt<PermutableNotForwardIterator>);
+static_assert(!HasRemoveIt<PermutableNotSwappable>);
+static_assert(!HasRemoveIt<int*, SentinelForNotSemiregular>);
+static_assert(!HasRemoveIt<int*, SentinelForNotWeaklyEqualityComparableWith>);
+static_assert(!HasRemoveIt<int**>); // not indirect_binary_prediacte
+
+template <class Range>
+concept HasRemoveR = requires(Range range) { std::ranges::remove(range, 0); };
+
+static_assert(HasRemoveR<UncheckedRange<int*>>);
+static_assert(!HasRemoveR<PermutableRangeNotForwardIterator>);
+static_assert(!HasRemoveR<PermutableRangeNotSwappable>);
+static_assert(!HasRemoveR<SentinelForNotSemiregular>);
+static_assert(!HasRemoveR<SentinelForNotWeaklyEqualityComparableWith>);
+static_assert(!HasRemoveR<UncheckedRange<int**>>); // not indirect_binary_prediacte
+
+template <int N, int M>
+struct Data {
+  std::array<int, N> input;
+  std::array<int, M> expected;
+  int val;
+};
+
+template <class Iter, class Sent, int N, int M>
+constexpr void test(Data<N, M> d) {
+  { // iterator overload
+    auto input = d.input;
+
+    std::same_as<std::ranges::subrange<Iter>> decltype(auto) ret =
+        std::ranges::remove(Iter(input.data()), Sent(Iter(input.data() + input.size())), d.val);
+
+    assert(base(ret.begin()) == input.data() + M);
+    assert(base(ret.end()) == input.data() + N);
+    assert(std::ranges::equal(input.begin(), base(ret.begin()), d.expected.begin(), d.expected.end()));
+  }
+
+  { // range overload
+    auto input = d.input;
+    auto range = std::ranges::subrange(Iter(input.data()), Sent(Iter(input.data() + input.size())));
+
+    std::same_as<std::ranges::subrange<Iter>> decltype(auto) ret = std::ranges::remove(range, d.val);
+
+    assert(base(ret.begin()) == input.data() + M);
+    assert(base(ret.end()) == input.data() + N);
+    assert(std::ranges::equal(base(input.begin()), base(ret.begin()), d.expected.begin(), d.expected.end()));
+  }
+}
+
+template <class Iter, class Sent>
+constexpr void tests() {
+  // simple test
+  test<Iter, Sent, 6, 5>({.input = {1, 2, 3, 4, 5, 6}, .expected = {1, 2, 3, 4, 6}, .val = 5});
+  // empty range
+  test<Iter, Sent, 0, 0>({});
+  // single element range - match
+  test<Iter, Sent, 1, 0>({.input = {1}, .expected = {}, .val = 1});
+  // single element range - no match
+  test<Iter, Sent, 1, 1>({.input = {1}, .expected = {1}, .val = 2});
+  // two element range - same order
+  test<Iter, Sent, 2, 1>({.input = {1, 2}, .expected = {1}, .val = 2});
+  // two element range - reversed order
+  test<Iter, Sent, 2, 1>({.input = {1, 2}, .expected = {2}, .val = 1});
+  // all elements match
+  test<Iter, Sent, 5, 0>({.input = {1, 1, 1, 1, 1}, .expected = {}, .val = 1});
+  // the relative order of elements isn't changed
+  test<Iter, Sent, 8, 5>({.input = {1, 2, 3, 2, 3, 4, 2, 5}, .expected = {1, 3, 3, 4, 5}, .val = 2});
+}
+
+template <class Iter>
+constexpr void test_sentinels() {
+  tests<Iter, Iter>();
+  tests<Iter, sentinel_wrapper<Iter>>();
+  tests<Iter, sized_sentinel<Iter>>();
+}
+
+constexpr void test_iterators() {
+  test_sentinels<forward_iterator<int*>>();
+  test_sentinels<bidirectional_iterator<int*>>();
+  test_sentinels<random_access_iterator<int*>>();
+  test_sentinels<contiguous_iterator<int*>>();
+  test_sentinels<int*>();
+}
+
+constexpr bool test() {
+  test_iterators();
+
+  { // check that ranges::dangling is returned
+    [[maybe_unused]] std::same_as<std::ranges::dangling> decltype(auto) ret =
+        std::ranges::remove(std::array{1, 2, 3, 4}, 1);
+  }
+
+  { // check complexity requirements
+    struct CompCounter {
+      int* comp_count;
+
+      constexpr bool operator==(const CompCounter&) const {
+        ++*comp_count;
+        return false;
+      }
+    };
+    {
+      int proj_count = 0;
+      auto proj      = [&](CompCounter i) {
+        ++proj_count;
+        return i;
+      };
+      int comp_count = 0;
+
+      CompCounter a[] = {{&comp_count}, {&comp_count}, {&comp_count}, {&comp_count}};
+      auto ret        = std::ranges::remove(std::begin(a), std::end(a), CompCounter{&comp_count}, proj);
+      assert(ret.begin() == std::end(a) && ret.end() == std::end(a));
+      assert(comp_count == 4);
+      assert(proj_count == 4);
+    }
+    {
+      int proj_count = 0;
+      auto proj      = [&](CompCounter i) {
+        ++proj_count;
+        return i;
+      };
+      int comp_count = 0;
+
+      CompCounter a[] = {{&comp_count}, {&comp_count}, {&comp_count}, {&comp_count}};
+      auto ret        = std::ranges::remove(a, CompCounter{&comp_count}, proj);
+      assert(ret.begin() == std::end(a) && ret.end() == std::end(a));
+      assert(comp_count == 4);
+      assert(proj_count == 4);
+    }
+  }
+
+  { // check that std::invoke is used
+    struct S {
+      constexpr S& identity() { return *this; }
+      bool operator==(const S&) const = default;
+    };
+    {
+      S a[4]   = {};
+      auto ret = std::ranges::remove(std::begin(a), std::end(a), S{}, &S::identity);
+      assert(ret.begin() == std::begin(a));
+      assert(ret.end() == std::end(a));
+    }
+    {
+      S a[4]   = {};
+      auto ret = std::ranges::remove(a, S{}, &S::identity);
+      assert(ret.begin() == std::begin(a));
+      assert(ret.end() == std::end(a));
+    }
+  }
+
+  {
+    // check that the implicit conversion to bool works
+    {
+      StrictComparable<int> a[] = {1, 2, 3, 4};
+      auto ret                  = std::ranges::remove(a, a + 4, StrictComparable<int>{2});
+      assert(ret.begin() == a + 3);
+    }
+    {
+      StrictComparable<int> a[] = {1, 2, 3, 4};
+      auto ret                  = std::ranges::remove(a, StrictComparable<int>{2});
+      assert(ret.begin() == a + 3);
+    }
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.remove/ranges.remove_if.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.remove/ranges.remove_if.pass.cpp
new file mode 100644
index 0000000000000..95d4477fa8598
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.remove/ranges.remove_if.pass.cpp
@@ -0,0 +1,224 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <algorithm>
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<permutable I, sentinel_for<I> S, class Proj = identity,
+//          indirect_unary_predicate<projected<I, Proj>> Pred>
+//   constexpr subrange<I> ranges::remove_if(I first, S last, Pred pred, Proj proj = {});
+// template<forward_range R, class Proj = identity,
+//          indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
+//   requires permutable<iterator_t<R>>
+//   constexpr borrowed_subrange_t<R>
+//     ranges::remove_if(R&& r, Pred pred, Proj proj = {});
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <ranges>
+
+#include "almost_satisfies_types.h"
+#include "boolean_testable.h"
+#include "test_iterators.h"
+
+struct FalsePredicate {
+  bool operator()(int) { return false; }
+};
+
+template <class Iter, class Sent = sentinel_wrapper<Iter>>
+concept HasRemoveIfIt = requires(Iter first, Sent last) { std::ranges::remove_if(first, last, FalsePredicate{}); };
+
+static_assert(HasRemoveIfIt<int*>);
+static_assert(!HasRemoveIfIt<PermutableNotForwardIterator>);
+static_assert(!HasRemoveIfIt<PermutableNotSwappable>);
+static_assert(!HasRemoveIfIt<int*, SentinelForNotSemiregular>);
+static_assert(!HasRemoveIfIt<int*, SentinelForNotWeaklyEqualityComparableWith>);
+static_assert(!HasRemoveIfIt<int**>); // not indirect_unary_predicate
+
+template <class Range>
+concept HasRemoveIfR = requires(Range range) { std::ranges::remove_if(range, FalsePredicate{}); };
+
+static_assert(HasRemoveIfR<UncheckedRange<int*>>);
+static_assert(!HasRemoveIfR<PermutableRangeNotForwardIterator>);
+static_assert(!HasRemoveIfR<PermutableRangeNotSwappable>);
+static_assert(!HasRemoveIfR<SentinelForNotSemiregular>);
+static_assert(!HasRemoveIfR<SentinelForNotWeaklyEqualityComparableWith>);
+static_assert(!HasRemoveIfR<UncheckedRange<int**>>); // not indirect_unary_predicate
+
+template <int N, int M>
+struct Data {
+  std::array<int, N> input;
+  std::array<int, M> expected;
+  int cutoff;
+};
+
+template <class Iter, class Sent, int N, int M>
+constexpr void test(Data<N, M> d) {
+  { // iterator overload
+    auto input = d.input;
+
+    auto first = Iter(input.data());
+    auto last  = Sent(Iter(input.data() + input.size()));
+
+    std::same_as<std::ranges::subrange<Iter>> decltype(auto) ret =
+        std::ranges::remove_if(std::move(first), std::move(last), [&](int i) { return i < d.cutoff; });
+
+    assert(base(ret.begin()) == input.data() + M);
+    assert(base(ret.end()) == input.data() + N);
+    assert(std::ranges::equal(input.data(), base(ret.begin()), d.expected.begin(), d.expected.end()));
+  }
+
+  { // range overload
+    auto input = d.input;
+    auto range = std::ranges::subrange(Iter(input.data()), Sent(Iter(input.data() + input.size())));
+
+    std::same_as<std::ranges::subrange<Iter>> decltype(auto) ret = std::ranges::remove_if(range, [&](int i) {
+      return i < d.cutoff;
+    });
+
+    assert(base(ret.begin()) == input.data() + M);
+    assert(base(ret.end()) == input.data() + N);
+    assert(std::ranges::equal(base(input.begin()), base(ret.begin()), d.expected.begin(), d.expected.end()));
+  }
+}
+
+template <class Iter, class Sent>
+constexpr void tests() {
+  // simple test
+  test<Iter, Sent, 6, 2>({.input = {1, 2, 3, 4, 5, 6}, .expected = {5, 6}, .cutoff = 5});
+  // empty range
+  test<Iter, Sent, 0, 0>({});
+  // single element range - no match
+  test<Iter, Sent, 1, 1>({.input = {1}, .expected = {1}, .cutoff = 1});
+  // single element range - match
+  test<Iter, Sent, 1, 0>({.input = {1}, .expected = {}, .cutoff = 2});
+  // two element range
+  test<Iter, Sent, 2, 1>({.input = {1, 2}, .expected = {2}, .cutoff = 2});
+  // all elements match
+  test<Iter, Sent, 5, 0>({.input = {1, 1, 1, 1, 1}, .expected = {}, .cutoff = 2});
+  // no elements match
+  test<Iter, Sent, 5, 5>({.input = {1, 1, 1, 1, 1}, .expected = {1, 1, 1, 1, 1}, .cutoff = 0});
+  // the relative order of elements isn't changed
+  test<Iter, Sent, 8, 7>({.input = {1, 2, 3, 2, 3, 4, 2, 5}, .expected = {2, 3, 2, 3, 4, 2, 5}, .cutoff = 2});
+  // multiple matches in a row
+  test<Iter, Sent, 5, 3>({.input = {1, 2, 2, 2, 1}, .expected = {2, 2, 2}, .cutoff = 2});
+  // only the last element matches
+  test<Iter, Sent, 3, 2>({.input = {2, 2, 1}, .expected = {2, 2}, .cutoff = 2});
+  // only the last element doesn't match
+  test<Iter, Sent, 3, 1>({.input = {1, 1, 2}, .expected = {2}, .cutoff = 2});
+}
+
+template <class Iter>
+constexpr void test_sentinels() {
+  tests<Iter, Iter>();
+  tests<Iter, sentinel_wrapper<Iter>>();
+  tests<Iter, sized_sentinel<Iter>>();
+}
+
+constexpr void test_iterators() {
+  test_sentinels<forward_iterator<int*>>();
+  test_sentinels<bidirectional_iterator<int*>>();
+  test_sentinels<random_access_iterator<int*>>();
+  test_sentinels<contiguous_iterator<int*>>();
+  test_sentinels<int*>();
+}
+
+constexpr bool test() {
+  test_iterators();
+
+  { // check that ranges::dangling is returned
+    [[maybe_unused]] std::same_as<std::ranges::dangling> decltype(auto) ret =
+        std::ranges::remove_if(std::array{1, 2, 3, 4}, [](int i) { return i < 0; });
+  }
+
+  {// check complexity requirements
+
+   // This is https://llvm.org/PR56382 - clang-format behaves weird if function-local structs are used
+   // clang-format off
+    {
+      int comp_count = 0;
+      auto comp = [&](int i) {
+        ++comp_count;
+        return i == 0;
+      };
+      int proj_count = 0;
+      auto proj      = [&](int i) {
+        ++proj_count;
+        return i;
+      };
+      int a[]  = {1, 2, 3, 4};
+      auto ret = std::ranges::remove_if(std::begin(a), std::end(a), comp, proj);
+      assert(ret.begin() == std::end(a) && ret.end() == std::end(a));
+      assert(comp_count == 4);
+      assert(proj_count == 4);
+    }
+    {
+      int comp_count = 0;
+      auto comp      = [&](int i) {
+        ++comp_count;
+        return i == 0;
+      };
+      int proj_count = 0;
+      auto proj      = [&](int i) {
+        ++proj_count;
+        return i;
+      };
+      int a[]  = {1, 2, 3, 4};
+      auto ret = std::ranges::remove_if(a, comp, proj);
+      assert(ret.begin() == std::end(a) && ret.end() == std::end(a));
+      assert(comp_count == 4);
+      assert(proj_count == 4);
+    }
+  }
+
+  { // check that std::invoke is used
+    struct S {
+      constexpr S& identity() { return *this; }
+      constexpr bool predicate() const { return true; }
+    };
+    {
+      S a[4]   = {};
+      auto ret = std::ranges::remove_if(std::begin(a), std::end(a), &S::predicate, &S::identity);
+      assert(ret.begin() == std::begin(a));
+      assert(ret.end() == std::end(a));
+    }
+    {
+      S a[4]   = {};
+      auto ret = std::ranges::remove_if(a, &S::predicate, &S::identity);
+      assert(ret.begin() == std::begin(a));
+      assert(ret.end() == std::end(a));
+    }
+  }
+
+  {
+    // check that the implicit conversion to bool works
+    {
+      int a[]  = {1, 2, 3, 4};
+      auto ret = std::ranges::remove_if(a, a + 4, [](const int& i) { return BooleanTestable{i == 3}; });
+      assert(ret.begin() == a + 3);
+    }
+    {
+      int a[]  = {1, 2, 3, 4};
+      auto ret = std::ranges::remove_if(a, [](const int& b) { return BooleanTestable{b == 3}; });
+      assert(ret.begin() == a + 3);
+    }
+  }
+
+  return true;
+}
+// clang-format on
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp
index b66c78951b55c..dda8f71e56914 100644
--- a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp
+++ b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp
@@ -117,10 +117,10 @@ static_assert(test(std::ranges::none_of, a, odd));
 //static_assert(test(std::ranges::pop_heap, a));
 //static_assert(test(std::ranges::prev_permutation, a));
 //static_assert(test(std::ranges::push_heap, a));
-//static_assert(test(std::ranges::remove, a, 42));
+static_assert(test(std::ranges::remove, a, 42));
 //static_assert(test(std::ranges::remove_copy, a, a, 42));
 //static_assert(test(std::ranges::remove_copy_if, a, a, odd));
-//static_assert(test(std::ranges::remove_if, a, odd));
+static_assert(test(std::ranges::remove_if, a, odd));
 static_assert(test(std::ranges::replace, a, 42, 43));
 //static_assert(test(std::ranges::replace_copy, a, a, 42, 43));
 //static_assert(test(std::ranges::replace_copy_if, a, a, odd, 43));


        


More information about the libcxx-commits mailing list