[libc++] Implement ranges::for_each{, _n}
authorNikolas Klauser <nikolasklauser@berlin.de>
Wed, 4 May 2022 18:27:07 +0000 (20:27 +0200)
committerNikolas Klauser <nikolasklauser@berlin.de>
Wed, 4 May 2022 18:28:01 +0000 (20:28 +0200)
Reviewed By: var-const, #libc

Spies: libcxx-commits, mgorny

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

libcxx/docs/Status/RangesAlgorithms.csv
libcxx/include/CMakeLists.txt
libcxx/include/__algorithm/ranges_for_each.h [new file with mode: 0644]
libcxx/include/__algorithm/ranges_for_each_n.h [new file with mode: 0644]
libcxx/include/algorithm
libcxx/include/module.modulemap
libcxx/test/libcxx/private_headers.verify.cpp
libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each.pass.cpp [new file with mode: 0644]
libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each_n.pass.cpp [new file with mode: 0644]

index 419da0b..0023d2b 100644 (file)
@@ -34,8 +34,8 @@ Read-only,is_heap,Not assigned,n/a,Not started
 Read-only,is_heap_until,Not assigned,n/a,Not started
 Read-only,clamp,Not assigned,n/a,Not started
 Read-only,is_permutation,Not assigned,n/a,Not started
-Read-only,for_each,Not assigned,n/a,Not started
-Read-only,for_each_n,Not assigned,n/a,Not started
+Read-only,for_each,Nikolas Klauser,n/a,✅
+Read-only,for_each_n,Nikolas Klauser,n/a,✅
 Write,copy,Nikolas Klauser,`D122982 <https://llvm.org/D122982>`_,✅
 Write,copy_if,Nikolas Klauser,`D122982 <https://llvm.org/D122982>`_,✅
 Write,copy_n,Nikolas Klauser,`D122982 <https://llvm.org/D122982>`_,✅
index 423a1ff..257e9d5 100644 (file)
@@ -75,6 +75,8 @@ set(files
   __algorithm/ranges_find.h
   __algorithm/ranges_find_if.h
   __algorithm/ranges_find_if_not.h
+  __algorithm/ranges_for_each.h
+  __algorithm/ranges_for_each_n.h
   __algorithm/ranges_max.h
   __algorithm/ranges_max_element.h
   __algorithm/ranges_min.h
diff --git a/libcxx/include/__algorithm/ranges_for_each.h b/libcxx/include/__algorithm/ranges_for_each.h
new file mode 100644 (file)
index 0000000..f284c0e
--- /dev/null
@@ -0,0 +1,78 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_FOR_EACH_H
+#define _LIBCPP___ALGORITHM_RANGES_FOR_EACH_H
+
+#include <__algorithm/in_fun_result.h>
+#include <__config>
+#include <__functional/identity.h>
+#include <__functional/invoke.h>
+#include <__iterator/concepts.h>
+#include <__iterator/projected.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/dangling.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 _Func>
+using for_each_result = in_fun_result<_Iter, _Func>;
+
+namespace __for_each {
+struct __fn {
+private:
+  template <class _Iter, class _Sent, class _Proj, class _Func>
+  _LIBCPP_HIDE_FROM_ABI constexpr static
+  for_each_result<_Iter, _Func> __for_each_impl(_Iter __first, _Sent __last, _Func& __func, _Proj& __proj) {
+    for (; __first != __last; ++__first)
+      std::invoke(__func, std::invoke(__proj, *__first));
+    return {std::move(__first), std::move(__func)};
+  }
+
+public:
+  template <input_iterator _Iter, sentinel_for<_Iter> _Sent,
+            class _Proj = identity,
+            indirectly_unary_invocable<projected<_Iter, _Proj>> _Func>
+  _LIBCPP_HIDE_FROM_ABI constexpr
+  for_each_result<_Iter, _Func> operator()(_Iter __first, _Sent __last, _Func __func, _Proj __proj = {}) const {
+    return __for_each_impl(std::move(__first), std::move(__last), __func, __proj);
+  }
+
+  template <input_range _Range,
+            class _Proj = identity,
+            indirectly_unary_invocable<projected<iterator_t<_Range>, _Proj>> _Func>
+  _LIBCPP_HIDE_FROM_ABI constexpr
+  for_each_result<borrowed_iterator_t<_Range>, _Func> operator()(_Range&& __range,
+                                                                 _Func __func,
+                                                                 _Proj __proj = {}) const {
+    return __for_each_impl(ranges::begin(__range), ranges::end(__range), __func, __proj);
+  }
+
+};
+} // namespace __for_each
+
+inline namespace __cpo {
+  inline constexpr auto for_each = __for_each::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+#endif // _LIBCPP___ALGORITHM_RANGES_FOR_EACH_H
diff --git a/libcxx/include/__algorithm/ranges_for_each_n.h b/libcxx/include/__algorithm/ranges_for_each_n.h
new file mode 100644 (file)
index 0000000..f40f849
--- /dev/null
@@ -0,0 +1,66 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_FOR_EACH_N_H
+#define _LIBCPP___ALGORITHM_RANGES_FOR_EACH_N_H
+
+#include <__algorithm/in_fun_result.h>
+#include <__config>
+#include <__functional/identity.h>
+#include <__functional/invoke.h>
+#include <__iterator/concepts.h>
+#include <__iterator/incrementable_traits.h>
+#include <__iterator/projected.h>
+#include <__ranges/concepts.h>
+#include <__ranges/dangling.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 _Func>
+using for_each_n_result = in_fun_result<_Iter, _Func>;
+
+namespace __for_each_n {
+struct __fn {
+
+  template <input_iterator _Iter,
+            class _Proj = identity,
+            indirectly_unary_invocable<projected<_Iter, _Proj>> _Func>
+  _LIBCPP_HIDE_FROM_ABI constexpr
+  for_each_n_result<_Iter, _Func> operator()(_Iter __first,
+                                             iter_difference_t<_Iter> __count,
+                                             _Func __func,
+                                             _Proj __proj = {}) const {
+    while (__count-- > 0) {
+      std::invoke(__func, std::invoke(__proj, *__first));
+      ++__first;
+    }
+    return {std::move(__first), std::move(__func)};
+  }
+
+};
+} // namespace __for_each_n
+
+inline namespace __cpo {
+  inline constexpr auto for_each_n = __for_each_n::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+#endif // _LIBCPP___ALGORITHM_RANGES_FOR_EACH_N_H
index 2c1474e..13b3064 100644 (file)
@@ -238,6 +238,24 @@ namespace ranges {
     requires indirectly_copyable<iterator_t<R>, I>
     constexpr ranges::copy_backward_result<borrowed_iterator_t<R>, I>
       ranges::copy_backward(R&& r, I result);                                               // since C++20
+
+  template<class I, class F>
+    using for_each_result = in_fun_result<I, F>;                                            // since C++20
+
+  template<input_iterator I, sentinel_for<I> S, class Proj = identity,
+           indirectly_unary_invocable<projected<I, Proj>> Fun>
+    constexpr ranges::for_each_result<I, Fun>
+      ranges::for_each(I first, S last, Fun f, Proj proj = {});                             // since C++20
+
+  template<input_range R, class Proj = identity,
+           indirectly_unary_invocable<projected<iterator_t<R>, Proj>> Fun>
+    constexpr ranges::for_each_result<borrowed_iterator_t<R>, Fun>
+      ranges::for_each(R&& r, Fun f, Proj proj = {});                                       // since C++20
+
+  template<input_iterator I, class Proj = identity,
+           indirectly_unary_invocable<projected<I, Proj>> Fun>
+    constexpr ranges::for_each_n_result<I, Fun>
+      ranges::for_each_n(I first, iter_difference_t<I> n, Fun f, Proj proj = {});           // since C++20
 }
 
     constexpr bool     // constexpr in C++20
@@ -963,6 +981,8 @@ template <class BidirectionalIterator, class Compare>
 #include <__algorithm/ranges_find.h>
 #include <__algorithm/ranges_find_if.h>
 #include <__algorithm/ranges_find_if_not.h>
+#include <__algorithm/ranges_for_each.h>
+#include <__algorithm/ranges_for_each_n.h>
 #include <__algorithm/ranges_max.h>
 #include <__algorithm/ranges_max_element.h>
 #include <__algorithm/ranges_min.h>
index f6a7179..c027bdc 100644 (file)
@@ -303,6 +303,8 @@ module std [system] {
       module ranges_find              { private header "__algorithm/ranges_find.h" }
       module ranges_find_if           { private header "__algorithm/ranges_find_if.h" }
       module ranges_find_if_not       { private header "__algorithm/ranges_find_if_not.h" }
+      module ranges_for_each          { private header "__algorithm/ranges_for_each.h" }
+      module ranges_for_each_n        { private header "__algorithm/ranges_for_each_n.h" }
       module ranges_max               { private header "__algorithm/ranges_max.h" }
       module ranges_max_element       { private header "__algorithm/ranges_max_element.h" }
       module ranges_min               { private header "__algorithm/ranges_min.h" }
index f98e7ec..f8b7ea5 100644 (file)
@@ -112,6 +112,8 @@ END-SCRIPT
 #include <__algorithm/ranges_find.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_find.h'}}
 #include <__algorithm/ranges_find_if.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_find_if.h'}}
 #include <__algorithm/ranges_find_if_not.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_find_if_not.h'}}
+#include <__algorithm/ranges_for_each.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_for_each.h'}}
+#include <__algorithm/ranges_for_each_n.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_for_each_n.h'}}
 #include <__algorithm/ranges_max.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_max.h'}}
 #include <__algorithm/ranges_max_element.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_max_element.h'}}
 #include <__algorithm/ranges_min.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_min.h'}}
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each.pass.cpp
new file mode 100644 (file)
index 0000000..1a662fb
--- /dev/null
@@ -0,0 +1,157 @@
+//===----------------------------------------------------------------------===//
+//
+// 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<input_iterator I, sentinel_for<I> S, class Proj = identity,
+//          indirectly_unary_invocable<projected<I, Proj>> Fun>
+//   constexpr ranges::for_each_result<I, Fun>
+//     ranges::for_each(I first, S last, Fun f, Proj proj = {});
+// template<input_range R, class Proj = identity,
+//          indirectly_unary_invocable<projected<iterator_t<R>, Proj>> Fun>
+//   constexpr ranges::for_each_result<borrowed_iterator_t<R>, Fun>
+//     ranges::for_each(R&& r, Fun f, Proj proj = {});
+
+#include <algorithm>
+#include <ranges>
+
+#include "almost_satisfies_types.h"
+#include "test_iterators.h"
+
+struct Callable {
+  void operator()(int);
+};
+
+template <class Iter, class Sent = Iter>
+concept HasForEachIt = requires (Iter iter, Sent sent) { std::ranges::for_each(iter, sent, Callable{}); };
+
+static_assert(HasForEachIt<int*>);
+static_assert(!HasForEachIt<InputIteratorNotDerivedFrom>);
+static_assert(!HasForEachIt<InputIteratorNotIndirectlyReadable>);
+static_assert(!HasForEachIt<InputIteratorNotInputOrOutputIterator>);
+static_assert(!HasForEachIt<int*, SentinelForNotSemiregular>);
+static_assert(!HasForEachIt<int*, SentinelForNotWeaklyEqualityComparableWith>);
+
+template <class Func>
+concept HasForEachItFunc = requires(int* a, int* b, Func func) { std::ranges::for_each(a, b, func); };
+
+static_assert(HasForEachItFunc<Callable>);
+static_assert(!HasForEachItFunc<IndirectUnaryPredicateNotPredicate>);
+static_assert(!HasForEachItFunc<IndirectUnaryPredicateNotCopyConstructible>);
+
+template <class Range>
+concept HasForEachR = requires (Range range) { std::ranges::for_each(range, Callable{}); };
+
+static_assert(HasForEachR<UncheckedRange<int*>>);
+static_assert(!HasForEachR<InputRangeNotDerivedFrom>);
+static_assert(!HasForEachR<InputRangeNotIndirectlyReadable>);
+static_assert(!HasForEachR<InputRangeNotInputOrOutputIterator>);
+static_assert(!HasForEachR<InputRangeNotSentinelSemiregular>);
+static_assert(!HasForEachR<InputRangeNotSentinelEqualityComparableWith>);
+
+template <class Func>
+concept HasForEachRFunc = requires(UncheckedRange<int*> a, Func func) { std::ranges::for_each(a, func); };
+
+static_assert(HasForEachRFunc<Callable>);
+static_assert(!HasForEachRFunc<IndirectUnaryPredicateNotPredicate>);
+static_assert(!HasForEachRFunc<IndirectUnaryPredicateNotCopyConstructible>);
+
+template <class Iter, class Sent = Iter>
+constexpr void test_iterator() {
+  { // simple test
+    {
+      auto func = [i = 0](int& a) mutable { a += i++; };
+      int a[] = {1, 6, 3, 4};
+      std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> decltype(auto) ret =
+          std::ranges::for_each(Iter(a), Sent(Iter(a + 4)), func);
+      assert(a[0] == 1);
+      assert(a[1] == 7);
+      assert(a[2] == 5);
+      assert(a[3] == 7);
+      assert(base(ret.in) == a + 4);
+      int i = 0;
+      ret.fun(i);
+      assert(i == 4);
+    }
+    {
+      auto func = [i = 0](int& a) mutable { a += i++; };
+      int a[] = {1, 6, 3, 4};
+      auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 4)));
+      std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> decltype(auto) ret =
+          std::ranges::for_each(range, func);
+      assert(a[0] == 1);
+      assert(a[1] == 7);
+      assert(a[2] == 5);
+      assert(a[3] == 7);
+      assert(base(ret.in) == a + 4);
+      int i = 0;
+      ret.fun(i);
+      assert(i == 4);
+    }
+  }
+
+  { // check that an empty range works
+    {
+      int a[] = {};
+      std::ranges::for_each(Iter(a), Sent(Iter(a)), [](auto&) { assert(false); });
+    }
+    {
+      int a[] = {};
+      auto range = std::ranges::subrange(Iter(a), Sent(Iter(a)));
+      std::ranges::for_each(range, [](auto&) { assert(false); });
+    }
+  }
+}
+
+constexpr bool test() {
+  test_iterator<cpp17_input_iterator<int*>, sentinel_wrapper<cpp17_input_iterator<int*>>>();
+  test_iterator<cpp20_input_iterator<int*>, sentinel_wrapper<cpp20_input_iterator<int*>>>();
+  test_iterator<forward_iterator<int*>>();
+  test_iterator<bidirectional_iterator<int*>>();
+  test_iterator<random_access_iterator<int*>>();
+  test_iterator<contiguous_iterator<int*>>();
+  test_iterator<int*>();
+
+  { // check that std::invoke is used
+    struct S {
+      int check;
+      int other;
+    };
+    {
+      S a[] = {{1, 2}, {3, 4}, {5, 6}};
+      std::ranges::for_each(a, a + 3, [](int& i) { i = 0; }, &S::check);
+      assert(a[0].check == 0);
+      assert(a[0].other == 2);
+      assert(a[1].check == 0);
+      assert(a[1].other == 4);
+      assert(a[2].check == 0);
+      assert(a[2].other == 6);
+    }
+    {
+      S a[] = {{1, 2}, {3, 4}, {5, 6}};
+      std::ranges::for_each(a, [](int& i) { i = 0; }, &S::check);
+      assert(a[0].check == 0);
+      assert(a[0].other == 2);
+      assert(a[1].check == 0);
+      assert(a[1].other == 4);
+      assert(a[2].check == 0);
+      assert(a[2].other == 6);
+    }
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each_n.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.foreach/ranges.for_each_n.pass.cpp
new file mode 100644 (file)
index 0000000..f3e37d1
--- /dev/null
@@ -0,0 +1,100 @@
+//===----------------------------------------------------------------------===//
+//
+// 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<input_iterator I, class Proj = identity,
+//          indirectly_unary_invocable<projected<I, Proj>> Fun>
+//   constexpr ranges::for_each_n_result<I, Fun>
+//     ranges::for_each_n(I first, iter_difference_t<I> n, Fun f, Proj proj = {});
+
+#include <algorithm>
+#include <ranges>
+
+#include "almost_satisfies_types.h"
+#include "test_iterators.h"
+
+struct Callable {
+  void operator()(int);
+};
+
+template <class Iter>
+concept HasForEachN = requires (Iter iter) { std::ranges::for_each_n(iter, 0, Callable{}); };
+
+static_assert(HasForEachN<int*>);
+static_assert(!HasForEachN<InputIteratorNotDerivedFrom>);
+static_assert(!HasForEachN<InputIteratorNotIndirectlyReadable>);
+static_assert(!HasForEachN<InputIteratorNotInputOrOutputIterator>);
+
+template <class Func>
+concept HasForEachItFunc = requires(int* a, int b, Func func) { std::ranges::for_each_n(a, b, func); };
+
+static_assert(HasForEachItFunc<Callable>);
+static_assert(!HasForEachItFunc<IndirectUnaryPredicateNotPredicate>);
+static_assert(!HasForEachItFunc<IndirectUnaryPredicateNotCopyConstructible>);
+
+template <class Iter>
+constexpr void test_iterator() {
+  { // simple test
+    auto func = [i = 0](int& a) mutable { a += i++; };
+    int a[] = {1, 6, 3, 4};
+    std::same_as<std::ranges::for_each_result<Iter, decltype(func)>> auto ret =
+        std::ranges::for_each_n(Iter(a), 4, func);
+    assert(a[0] == 1);
+    assert(a[1] == 7);
+    assert(a[2] == 5);
+    assert(a[3] == 7);
+    assert(base(ret.in) == a + 4);
+    int i = 0;
+    ret.fun(i);
+    assert(i == 4);
+  }
+
+  { // check that an emptry range works
+    int a[] = {};
+    std::ranges::for_each_n(Iter(a), 0, [](auto&) { assert(false); });
+  }
+}
+
+constexpr bool test() {
+  test_iterator<cpp17_input_iterator<int*>>();
+  test_iterator<cpp20_input_iterator<int*>>();
+  test_iterator<forward_iterator<int*>>();
+  test_iterator<bidirectional_iterator<int*>>();
+  test_iterator<random_access_iterator<int*>>();
+  test_iterator<contiguous_iterator<int*>>();
+  test_iterator<int*>();
+
+  { // check that std::invoke is used
+    struct S {
+      int check;
+      int other;
+    };
+
+    S a[] = {{1, 2}, {3, 4}, {5, 6}};
+    std::ranges::for_each_n(a, 3, [](int& i) { i = 0; }, &S::check);
+    assert(a[0].check == 0);
+    assert(a[0].other == 2);
+    assert(a[1].check == 0);
+    assert(a[1].other == 4);
+    assert(a[2].check == 0);
+    assert(a[2].other == 6);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}