[libcxx-commits] [libcxx] [libc++][ranges] P1223R5: `find_last` (PR #99312)

Christopher Di Bella via libcxx-commits libcxx-commits at lists.llvm.org
Wed Jul 17 14:45:58 PDT 2024


================
@@ -0,0 +1,294 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// ADDITIONAL_COMPILE_FLAGS(gcc-style-warnings): -Wno-sign-compare
+// MSVC warning C4242: 'argument': conversion from 'const _Ty' to 'ElementT', possible loss of data
+// MSVC warning C4244: 'argument': conversion from 'const _Ty' to 'ElementT', possible loss of data
+// ADDITIONAL_COMPILE_FLAGS(cl-style-warnings): /wd4242 /wd4244
+
+// template<forward_iterator 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::find_last(I first, S last, const T& value, Proj proj = {});
+// template<forward_range R, class T, class Proj = identity>
+//   requires indirect_binary_predicate<ranges::equal_to, projected<iterator_t<R>, Proj>, const T*>
+//   constexpr borrowed_subrange_t<R> ranges::find_last(R&& r, const T& value, Proj proj = {});
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <deque>
+#include <ranges>
+#include <vector>
+
+#include "almost_satisfies_types.h"
+#include "test_iterators.h"
+
+struct NotEqualityComparable {};
+
+template <class It, class Sent = It>
+concept HasFindLastIt = requires(It it, Sent sent) { std::ranges::find_last(it, sent, *it); };
+static_assert(HasFindLastIt<int*>);
+static_assert(HasFindLastIt<forward_iterator<int*>>);
+static_assert(!HasFindLastIt<cpp20_input_iterator<int*>>);
+static_assert(!HasFindLastIt<NotEqualityComparable*>);
+static_assert(!HasFindLastIt<ForwardIteratorNotDerivedFrom>);
+static_assert(!HasFindLastIt<ForwardIteratorNotIncrementable>);
+static_assert(!HasFindLastIt<forward_iterator<int*>, SentinelForNotSemiregular>);
+static_assert(!HasFindLastIt<forward_iterator<int*>, InputRangeNotSentinelEqualityComparableWith>);
+
+static_assert(!HasFindLastIt<int*, int>);
+static_assert(!HasFindLastIt<int, int*>);
+
+template <class Range, class ValT>
+concept HasFindLastR = requires(Range r) { std::ranges::find_last(r, ValT{}); };
+static_assert(HasFindLastR<std::array<int, 1>, int>);
+static_assert(!HasFindLastR<int, int>);
+static_assert(!HasFindLastR<std::array<NotEqualityComparable, 1>, NotEqualityComparable>);
+static_assert(!HasFindLastR<ForwardRangeNotDerivedFrom, int>);
+static_assert(!HasFindLastR<ForwardRangeNotIncrementable, int>);
+static_assert(!HasFindLastR<ForwardRangeNotSentinelSemiregular, int>);
+static_assert(!HasFindLastR<ForwardRangeNotSentinelEqualityComparableWith, int>);
+
+static std::vector<int> comparable_data;
+
+template <class It, class Sent = It>
+constexpr void test_iterators() {
+  using ValueT = std::iter_value_t<It>;
+  { // simple test
+    {
+      ValueT a[] = {1, 2, 3, 4};
+
+      std::same_as<std::ranges::subrange<It>> auto ret = std::ranges::find_last(It(a), Sent(It(a + 4)), 2);
+      assert(base(ret.begin()) == a + 1);
+      assert(*ret.begin() == 2);
+    }
+    {
+      ValueT a[] = {1, 2, 3, 4};
+      auto range = std::ranges::subrange(It(a), Sent(It(a + 4)));
+
+      std::same_as<std::ranges::subrange<It>> auto ret = std::ranges::find_last(range, 2);
+      assert(base(ret.begin()) == a + 1);
+      assert(*ret.begin() == 2);
+    }
+  }
+
+  { // check that an empty range works
+    {
+      std::array<ValueT, 0> a = {};
+
+      auto ret = std::ranges::find_last(It(a.data()), Sent(It(a.data())), 1).begin();
+      assert(base(ret) == a.data());
+    }
+    {
+      std::array<ValueT, 0> a = {};
+
+      auto range = std::ranges::subrange(It(a.data()), Sent(It(a.data())));
+      auto ret   = std::ranges::find_last(range, 1).begin();
+      assert(base(ret) == a.data());
+    }
+  }
+
+  { // check that last is returned with no match
+    {
+      ValueT a[] = {1, 1, 1};
+
+      auto ret = std::ranges::find_last(a, a + 3, 0).begin();
+      assert(ret == a + 3);
+    }
+    {
+      ValueT a[] = {1, 1, 1};
+
+      auto ret = std::ranges::find_last(a, 0).begin();
+      assert(ret == a + 3);
+    }
+  }
+
+  if (!std::is_constant_evaluated())
+    comparable_data.clear();
+}
+
+template <class ElementT>
+class TriviallyComparable {
+  ElementT el_;
+
+public:
+  TEST_CONSTEXPR TriviallyComparable(ElementT el) : el_(el) {}
+  bool operator==(const TriviallyComparable&) const = default;
+};
+
+constexpr bool test() {
+  types::for_each(types::type_list<char, wchar_t, int, long, TriviallyComparable<char>, TriviallyComparable<wchar_t>>{},
+                  []<class T> {
+                    types::for_each(types::forward_iterator_list<T*>{}, []<class Iter> {
+                      if constexpr (std::forward_iterator<Iter>)
+                        test_iterators<Iter>();
+                      test_iterators<Iter, sentinel_wrapper<Iter>>();
+                      test_iterators<Iter, sized_sentinel<Iter>>();
+                    });
+                  });
+
+  {
+    std::vector<std::vector<int>> vec = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
+    auto view                         = vec | std::views::join;
+    assert(std::ranges::find_last(view.begin(), view.end(), 4).begin() == std::next(view.begin(), 3));
+    assert(std::ranges::find_last(view, 4).begin() == std::next(view.begin(), 3));
+  }
+
+  { // check that the last element is returned
+    {
+      struct S {
+        int comp;
+        int other;
+      };
+      S a[]    = {{0, 0}, {0, 2}, {0, 1}};
+      auto ret = std::ranges::find_last(a, 0, &S::comp).begin();
+      assert(ret == a + 2);
+      assert(ret->comp == 0);
+      assert(ret->other == 1);
+    }
+    {
+      struct S {
+        int comp;
+        int other;
+      };
+      S a[]    = {{0, 0}, {0, 2}, {0, 1}};
+      auto ret = std::ranges::find_last(a, a + 3, 0, &S::comp).begin();
+      assert(ret == a + 2);
+      assert(ret->comp == 0);
+      assert(ret->other == 1);
+    }
+  }
+
+  {
+    // check that an iterator is returned with a borrowing range
+    int a[]                                            = {1, 2, 3, 4};
+    std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(std::views::all(a), 1);
+    assert(ret.begin() == a);
+    assert(*ret.begin() == 1);
+  }
+
+  {
+    // count invocations of the projection
+    {
+      int a[]              = {1, 2, 3, 4};
+      int projection_count = 0;
+      auto ret             = std::ranges::find_last(a, a + 4, 2, [&](int i) {
+                   ++projection_count;
+                   return i;
+                 }).begin();
+      assert(ret == a + 1);
+      assert(*ret == 2);
+      assert(projection_count == 3);
+    }
+    {
+      int a[]              = {1, 2, 3, 4};
+      int projection_count = 0;
+      auto ret             = std::ranges::find_last(a, 2, [&](int i) {
+                   ++projection_count;
+                   return i;
+                 }).begin();
+      assert(ret == a + 1);
+      assert(*ret == 2);
+      assert(projection_count == 3);
+    }
+  }
+
+  return true;
+}
+
+template <class IndexT>
+class Comparable {
+  IndexT index_;
+
+public:
+  Comparable(IndexT i)
+      : index_([&]() {
+          IndexT size = static_cast<IndexT>(comparable_data.size());
+          comparable_data.push_back(i);
+          return size;
+        }()) {}
+
+  bool operator==(const Comparable& other) const { return comparable_data[other.index_] == comparable_data[index_]; }
+
+  friend bool operator==(const Comparable& lhs, long long rhs) { return comparable_data[lhs.index_] == rhs; }
+};
+
+void test_deque() {
----------------
cjdb wrote:

@EricWF once made an argument that testing that this works with all of our containers is good for ensuring all of our containers can use all of our algorithms without issue (I think the idea is that different algorithms will expose different usage patterns and potentially expose latent bugs). It's not integration-level, but it does achieve something more than generating heat.

https://github.com/llvm/llvm-project/pull/99312


More information about the libcxx-commits mailing list