Title
`adjacent_view::{begin,end}` const overloads are under-constrained
Status
new
Section
[range.adjacent.view]
Submitter
Hui Xie

Created on 2025-11-23.00:00:00 last changed 2 weeks ago

Messages

Date: 2025-11-26.19:34:35

Proposed resolution:

This wording is relative to N5014.

  1. Modify [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_));
          }
        }
        […]
      };
    }
    
Date: 2025-11-23.00:00:00

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.

History
Date User Action Args
2025-11-26 19:34:35adminsetmessages: + msg15765
2025-11-23 00:00:00admincreate