&& same_as<iterator_t<_Range>, iterator_t<const _Range>>
&& same_as<sentinel_t<_Range>, sentinel_t<const _Range>>;
+ // _GLIBCXX_RESOLVE_LIB_DEFECTS
+ // 4112. has-arrow should required operator->() to be const-qualified
template<typename _It>
concept __has_arrow = input_iterator<_It>
- && (is_pointer_v<_It> || requires(_It __it) { __it.operator->(); });
+ && (is_pointer_v<_It>
+ || requires(const _It __it) { __it.operator->(); });
using std::__detail::__different_from;
} // namespace __detail
--- /dev/null
+// { dg-do compile { target c++20 } }
+
+// LWG 4112. has-arrow should required operator->() to be const-qualified
+
+// The issue resolution means that range adaptors which use has-arrow to
+// constrain their iterator's operator-> should require a const-qualified
+// operator-> on the underlying view's iterator.
+
+#include <ranges>
+
+struct Int { int i = 0; };
+
+struct Iter
+{
+ using value_type = Int;
+ using difference_type = int;
+
+ mutable Int val;
+
+ Int& operator*() const { return val; }
+ Int* operator->() /* non-const */ { return &val; }
+ Iter& operator++() { ++val.i; return *this; }
+ void operator++(int) { ++val.i; }
+ bool operator==(const Iter& j) const { return val.i == j.val.i; }
+};
+
+template<typename T>
+concept has_op_arrow = requires (T t) { t.operator->(); };
+
+static_assert( has_op_arrow<Iter> );
+static_assert( ! has_op_arrow<const Iter> );
+
+using Range = std::ranges::subrange<Iter>;
+using Pred = bool(*)(Int);
+using FilterView = std::ranges::filter_view<Range, Pred>;
+using FilterIterator = std::ranges::iterator_t<FilterView>;
+
+static_assert( ! has_op_arrow<FilterIterator> );
+static_assert( ! has_op_arrow<FilterIterator&> );
+static_assert( ! has_op_arrow<FilterIterator const> );
+static_assert( ! has_op_arrow<FilterIterator const&> );