[libcxx-commits] [libcxx] [libc++][ranges][enumerate_view] Update sentinel `equal` test (PR #222331)

Lucas Mellone via libcxx-commits libcxx-commits at lists.llvm.org
Mon Oct 5 08:51:16 PDT 2026


================
@@ -0,0 +1,219 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++23
+
+// <ranges>
+
+// class enumerate_view
+
+// class enumerate_view::sentinel
+
+// template<bool OtherConst>
+//   requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
+// friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
+
+#include <cassert>
+#include <concepts>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+
+#include "../../range_adaptor_types.h"
+#include "../types.h"
+#include "test_iterators.h"
+#include "test_range.h"
+
+using Iterator      = random_access_iterator<int*>;
+using ConstIterator = contiguous_iterator<const int*>;
+
+template <bool Const>
+struct ComparableSentinel {
+  using Iter = std::conditional_t<Const, ConstIterator, Iterator>;
+  Iter iter_;
+
+  explicit ComparableSentinel() = default;
+  constexpr explicit ComparableSentinel(const Iter& it) : iter_(it) {}
+
+  constexpr friend bool operator==(const Iterator& i, const ComparableSentinel& s) { return base(i) == base(s.iter_); }
+
+  constexpr friend bool operator==(const ConstIterator& i, const ComparableSentinel& s) {
+    return base(i) == base(s.iter_);
+  }
+};
+
+struct ComparableView : IntBufferView {
+  using IntBufferView::IntBufferView;
+
+  constexpr auto begin() { return Iterator(buffer_); }
+  constexpr auto begin() const { return ConstIterator(buffer_); }
+  constexpr auto end() { return ComparableSentinel<false>(Iterator(buffer_ + size_)); }
+  constexpr auto end() const { return ComparableSentinel<true>(ConstIterator(buffer_ + size_)); }
+};
+
+struct ConstIncompatibleView : IntBufferView {
+  using IntBufferView::IntBufferView;
+
+  constexpr random_access_iterator<int*> begin() { return random_access_iterator<int*>(buffer_); }
+  constexpr contiguous_iterator<const int*> begin() const { return contiguous_iterator<const int*>(buffer_); }
+  constexpr sentinel_wrapper<random_access_iterator<int*>> end() {
+    return sentinel_wrapper<random_access_iterator<int*>>(random_access_iterator<int*>(buffer_ + size_));
+  }
+  constexpr sentinel_wrapper<contiguous_iterator<const int*>> end() const {
+    return sentinel_wrapper<contiguous_iterator<const int*>>(contiguous_iterator<const int*>(buffer_ + size_));
+  }
+};
+
+constexpr bool test() {
+  int buffer[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+  {
+    // simple-view: const and non-const have the same iterator/sentinel type
+    using View = std::ranges::enumerate_view<SimpleNonCommon>;
+    static_assert(!std::ranges::common_range<View>);
+    static_assert(simple_view<View>);
+
+    static_assert(weakly_equality_comparable_with<std::ranges::iterator_t<View>, std::ranges::sentinel_t<View>>);
+
+    View ev{SimpleNonCommon(buffer)};
+
+    assert(ev.begin() != ev.end());
+    assert(ev.begin() + 1 != ev.end());
+    assert(ev.begin() + 2 != ev.end());
+    assert(ev.begin() + 3 != ev.end());
+    assert(ev.begin() + 10 == ev.end());
+  }
+
+  {
+    // !simple-view: const and non-const have different iterator/sentinel types
+    using View = std::ranges::enumerate_view<NonSimpleNonCommon>;
+    static_assert(!std::ranges::common_range<View>);
+    static_assert(!simple_view<View>);
+
+    using Iter      = std::ranges::iterator_t<View>;
+    using ConstIter = std::ranges::iterator_t<const View>;
+    static_assert(!std::is_same_v<Iter, ConstIter>);
+
+    using Sentinel      = std::ranges::sentinel_t<View>;
+    using ConstSentinel = std::ranges::sentinel_t<const View>;
+    static_assert(!std::is_same_v<Sentinel, ConstSentinel>);
+
+    static_assert(weakly_equality_comparable_with<Iter, Sentinel>);
+    static_assert(!weakly_equality_comparable_with<ConstIter, Sentinel>);
+    static_assert(weakly_equality_comparable_with<Iter, ConstSentinel>);
+    static_assert(weakly_equality_comparable_with<ConstIter, ConstSentinel>);
+
+    View ev{NonSimpleNonCommon(buffer)};
+
+    assert(ev.begin() != ev.end());
+    assert(ev.begin() + 10 == ev.end());
+
+    assert(ev.begin() != std::as_const(ev).end());
+    assert(ev.begin() + 10 == std::as_const(ev).end());
+    // the above works because
+    static_assert(std::convertible_to<Iter, ConstIter>);
+
+    assert(std::as_const(ev).begin() != std::as_const(ev).end());
+    assert(std::as_const(ev).begin() + 10 == std::as_const(ev).end());
+  }
+
+  {
+    // underlying const/non-const sentinel can be compared with both const/non-const iterator
+    using View = std::ranges::enumerate_view<ComparableView>;
+    static_assert(!std::ranges::common_range<View>);
+    static_assert(!simple_view<View>);
+
+    using Iter      = std::ranges::iterator_t<View>;
+    using ConstIter = std::ranges::iterator_t<const View>;
+    static_assert(!std::is_same_v<Iter, ConstIter>);
+
+    using Sentinel      = std::ranges::sentinel_t<View>;
+    using ConstSentinel = std::ranges::sentinel_t<const View>;
+    static_assert(!std::is_same_v<Sentinel, ConstSentinel>);
+
+    static_assert(weakly_equality_comparable_with<Iter, Sentinel>);
+    static_assert(weakly_equality_comparable_with<ConstIter, Sentinel>);
+    static_assert(weakly_equality_comparable_with<Iter, ConstSentinel>);
+    static_assert(weakly_equality_comparable_with<ConstIter, ConstSentinel>);
+
+    View ev{ComparableView(buffer)};
+
+    assert(ev.begin() != ev.end());
+    assert(ev.begin() + 10 == ev.end());
+
+    static_assert(!std::convertible_to<Iter, ConstIter>);
+
+    assert(ev.begin() != std::as_const(ev).end());
+    assert(ev.begin() + 10 == std::as_const(ev).end());
+
+    assert(std::as_const(ev).begin() != ev.end());
+    assert(std::as_const(ev).begin() + 10 == ev.end());
+
+    assert(std::as_const(ev).begin() != std::as_const(ev).end());
+    assert(std::as_const(ev).begin() + 10 == std::as_const(ev).end());
+  }
+
+  {
+    // underlying const/non-const sentinel cannot be compared with non-const/const iterator
+    using View = std::ranges::enumerate_view<ComparableView>;
+    static_assert(!std::ranges::common_range<View>);
+    static_assert(!simple_view<View>);
+
+    using Iter      = std::ranges::iterator_t<View>;
+    using ConstIter = std::ranges::iterator_t<const View>;
+    static_assert(!std::is_same_v<Iter, ConstIter>);
+
+    using Sentinel      = std::ranges::sentinel_t<View>;
+    using ConstSentinel = std::ranges::sentinel_t<const View>;
+    static_assert(!std::is_same_v<Sentinel, ConstSentinel>);
+
+    static_assert(weakly_equality_comparable_with<Iter, Sentinel>);
+    static_assert(weakly_equality_comparable_with<ConstIter, Sentinel>);
+    static_assert(weakly_equality_comparable_with<Iter, ConstSentinel>);
+    static_assert(weakly_equality_comparable_with<ConstIter, ConstSentinel>);
+
+    View ev{ComparableView(buffer)};
+
+    assert(ev.begin() != ev.end());
+    assert(ev.begin() + 10 == ev.end());
+
+    assert(std::as_const(ev).begin() != std::as_const(ev).end());
+    assert(std::as_const(ev).begin() + 10 == std::as_const(ev).end());
+  }
+
+  {
+    // sentinel comparison with input move-only iterator must work without copying the iterator
+    using InputIterator = cpp20_input_iterator<int*>;
+    using Sentinel      = sentinel_wrapper<InputIterator>;
+    using View          = MinimalView<InputIterator, Sentinel>;
+    static_assert(simple_view<View>);
+
+    View mv{InputIterator(buffer), Sentinel(InputIterator(buffer + 10))};
+    std::ranges::enumerate_view ev(std::move(mv));
+
+    auto it = ev.begin();
+    assert(it != ev.end());
+    assert(ev.end() != it);
+
+    // enumerate_view iterator only has operator+ when the underlying range is random-access.
+    // for input_iterator we increment it explicitly.
+    for (int i = 0; i != 10; ++i)
+      ++it;
----------------
lknknm wrote:

Please check, I replaced it to `std::ranges::advance(it, 5)`. I saw the original comments for unrolling. Performancewise I don't think it brings any benefit as these loops are likely to be unrolled anyway. If it's for readability, `std::ranges::advance(it, n);` is fairly expressive.

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


More information about the libcxx-commits mailing list