This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4482. adjacent_view::{begin,end} const overloads are under-constrained

Section: 25.7.27.2 [range.adjacent.view] Status: New Submitter: Hui Xie Opened: 2025-11-23 Last modified: 2025-11-26

Priority: Not Prioritized

View all other issues in [range.adjacent.view].

View all issues with New status.

Discussion:

Currently adjacent_view::begin and end const overloads are constrained as follows

constexpr auto begin() const requires range<const V>
constexpr auto end() const requires range<const V>

However, adjacent_view itself requires forward_range:

template<forward_range V, size_t N>
  requires view<V> && (N > 0)
class adjacent_view;

This means that if the underlying range's const begin/end returns input-only iterator, the adjacent_view's const begin/end are invocable, but they will result in some hard error (demo):

#include <ranges>

struct input_iter 
{
  const int* iter;

  input_iter(const int* ii) : iter(ii) {}
  input_iter(const input_iter&) = delete;
  input_iter(input_iter&&) = default;
  input_iter& operator=(input_iter const&) = delete;
  input_iter& operator=(input_iter&&) = default;

  using iterator_concept = std::input_iterator_tag;
  using difference_type = std::ptrdiff_t;
  using value_type = int;

  const int& operator*() const { return *iter; }
  input_iter& operator++() { ++iter; return *this; }
  void operator++(int) { ++iter; }
};

struct sent 
{
  const int* iter;
  
  friend bool operator==(const input_iter& i, const sent& s) {
    return i.iter == s.iter;
  }

};

static_assert(std::input_iterator<input_iter>);
static_assert(!std::forward_iterator<input_iter>);
static_assert(std::sentinel_for<sent, input_iter>);

struct r : std::ranges::view_base 
{
  int* iter;
  size_t size;

  template<std::size_t N>
  r(int (&i)[N]) : iter(i), size(N){}

  auto begin() { return iter; }
  auto end() { return iter + size; }

  auto begin() const { return input_iter{iter}; }
  auto end() const { return sent{iter + size}; }

};

static_assert(std::ranges::range<r>);
static_assert(std::ranges::range<const r>);

int main() {
  int input[] = { 1, 2, 3, 4, 5 };
  auto v = r{input} | std::views::adjacent<2>;
  for (auto&& t : v) {} // ok
  auto it = std::as_const(v).begin(); // ill-formed
}

Daniel:

This issue has considerable wording overlap with LWG 3731(i).

Proposed resolution:

This wording is relative to N5014.

  1. Modify 25.7.27.2 [range.adjacent.view], class template adjacent_view synopsis, as indicated:

    namespace std::ranges {
      template<forward_range V, size_t N>
        requires view<V> && (N > 0)
      class adjacent_view : public view_interface<adjacent_view<V, N>> {
        […]
      public:
        […]
        constexpr auto begin() requires (!simple-view<V>) {
          return iterator<false>(ranges::begin(base_), ranges::end(base_));
        }
        
        constexpr auto begin() const requires forward_range<const V> {
          return iterator<true>(ranges::begin(base_), ranges::end(base_));
        }
        
        constexpr auto end() requires (!simple-view<V>) {
          if constexpr (common_range<V>) {
            return iterator<false>(as-sentinel{}, ranges::begin(base_), ranges::end(base_));
          } else {    
            return sentinel<false>(ranges::end(base_));
          }
        }
        
        constexpr auto end() const requires forward_range<const V> {
          if constexpr (common_range<const V>) {
            return iterator<true>(as-sentinel{}, ranges::begin(base_), ranges::end(base_));
          } else {
            return sentinel<true>(ranges::end(base_));
          }
        }
        […]
      };
    }