[gcc r16-8389] libstdc++: Implement P3725R3 Filter View Extensions for Safer Use

Patrick Palka ppalka@gcc.gnu.org
Wed Apr 1 15:35:09 GMT 2026


https://gcc.gnu.org/g:d60b69a2eb798001a589c6de0f5fb9d25a5d0d9e

commit r16-8389-gd60b69a2eb798001a589c6de0f5fb9d25a5d0d9e
Author: Patrick Palka <ppalka@redhat.com>
Date:   Wed Apr 1 11:34:24 2026 -0400

    libstdc++: Implement P3725R3 Filter View Extensions for Safer Use
    
    This implements the recently approved C++26 paper P3725R3, which
    we treat as a Defect Report against C++20.
    
    libstdc++-v3/ChangeLog:
    
            * include/bits/version.def (ranges_filter): Define for C++20.
            * include/bits/version.h: Regenerate.
            * include/std/ranges: Provide __cpp_lib_ranges_filter.
            (filter_view::_Iterator): Add _Const template parameter.
            (filter_view::_Iterator::_S_iter_concept): Return
            input_iterator_tag if _Const.
            (filter_view::_Iterator::_Parent): New.
            (filter_view::_Iterator::_Base): New.
            (filter_view::_Iterator::_Vp_iter): Replace with ...
            (filter_view::_Iterator::_Base_iter): ... this.
            (filter_view::_Iterator::_M_current): Adjust to consider _Const.
            (filter_view::_Iterator::_M_parent): Likewise.
            (filter_view::_Iterator::value_type): Likewise.
            (filter_view::_Iterator::difference_type): Likewise.
            (filter_view::_Iterator::_Iterator): Likewise.  Add
            const-converting overload.
            (filter_view::_Iterator::base): Adjust to consider _Const.
            (filter_view::_Iterator::operator*): Likewise.
            (filter_view::_Iterator::operator->): Likewise.
            (filter_view::_Iterator::operator++): Likewise.
            (filter_view::_Iterator::operator--): Likewise.
            (filter_view::_Iterator::iter_move): Likewise.
            (filter_view::_Iterator::iter_swap): Likewise.
            (filter_view::_Sentinel): Add _Const template parameter.
            (filter_view::_Sentinel::_Parent): New.
            (filter_view::_Sentinel::_Base): New.
            (filter_view::_Sentinel::_M_end): Adjust to consider _Const.
            (filter_view::_Sentinel::_Sentinel): Likewise.  Add
            const-converting overload.
            (filter_view::_Sentinel::base): Adjust to consider _Const.
            (filter_view::_Sentinel::operator==): Likewise.  Inline the
            helper member function __equal.
            (filter_view::begin): Adjust return type of non-const overload.
            New const overload.
            (filter_view::end): Likewise.
            * testsuite/std/ranges/adaptors/filter.cc: Verify value of
            __cpp_lib_ranges_filter.
            (test08): New test.
    
    Reviewed-by: Jonathan Wakely <jwakely@redhat.com>

Diff:
---
 libstdc++-v3/include/bits/version.def              |   9 ++
 libstdc++-v3/include/bits/version.h                |  10 ++
 libstdc++-v3/include/std/ranges                    | 107 +++++++++++++++------
 .../testsuite/std/ranges/adaptors/filter.cc        |  38 +++++++-
 4 files changed, 131 insertions(+), 33 deletions(-)

diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 434d38c8fbdf..b7d4b3849acb 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -2181,6 +2181,15 @@ ftms = {
   };
 };
 
+ftms = {
+  name = ranges_filter;
+  values = {
+    // This is P3725R3, a C++26 change, but we treat it as a DR against C++20.
+    v = 202603;
+    cxxmin = 20;
+  };
+};
+
 ftms = {
   name = ranges_to_input;
   values = {
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index bb2475087f1f..c2219bedb5f8 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -2446,6 +2446,16 @@
 #endif /* !defined(__cpp_lib_text_encoding) */
 #undef __glibcxx_want_text_encoding
 
+#if !defined(__cpp_lib_ranges_filter)
+# if (__cplusplus >= 202002L)
+#  define __glibcxx_ranges_filter 202603L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_ranges_filter)
+#   define __cpp_lib_ranges_filter 202603L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_ranges_filter) */
+#undef __glibcxx_want_ranges_filter
+
 #if !defined(__cpp_lib_ranges_to_input)
 # if (__cplusplus >  202302L)
 #  define __glibcxx_ranges_to_input 202502L
diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index 95e0109a10cf..249cc466db4f 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -65,6 +65,7 @@
 #define __glibcxx_want_ranges_chunk
 #define __glibcxx_want_ranges_chunk_by
 #define __glibcxx_want_ranges_enumerate
+#define __glibcxx_want_ranges_filter
 #define __glibcxx_want_ranges_indices
 #define __glibcxx_want_ranges_join_with
 #define __glibcxx_want_ranges_repeat
@@ -1738,15 +1739,19 @@ namespace views::__adaptor
     class filter_view : public view_interface<filter_view<_Vp, _Pred>>
     {
     private:
+      template<bool _Const>
       struct _Sentinel;
 
+      template<bool _Const>
       struct _Iterator : __detail::__filter_view_iter_cat<_Vp>
       {
       private:
 	static constexpr auto
 	_S_iter_concept()
 	{
-	  if constexpr (bidirectional_range<_Vp>)
+	  if constexpr (_Const)
+	    return input_iterator_tag{};
+	  else if constexpr (bidirectional_range<_Vp>)
 	    return bidirectional_iterator_tag{};
 	  else if constexpr (forward_range<_Vp>)
 	    return forward_iterator_tag{};
@@ -1755,42 +1760,52 @@ namespace views::__adaptor
 	}
 
 	friend filter_view;
+	friend _Iterator<!_Const>;
 
-	using _Vp_iter = iterator_t<_Vp>;
+	using _Parent = __maybe_const_t<_Const, filter_view>;
+	using _Base = __maybe_const_t<_Const, _Vp>;
+	using _Base_iter = iterator_t<_Base>;
 
-	_Vp_iter _M_current = _Vp_iter();
-	filter_view* _M_parent = nullptr;
+	_Base_iter _M_current = _Base_iter();
+	_Parent* _M_parent = nullptr;
 
       public:
 	using iterator_concept = decltype(_S_iter_concept());
 	// iterator_category defined in __filter_view_iter_cat
-	using value_type = range_value_t<_Vp>;
-	using difference_type = range_difference_t<_Vp>;
+	using value_type = range_value_t<_Base>;
+	using difference_type = range_difference_t<_Base>;
 
-	_Iterator() requires default_initializable<_Vp_iter> = default;
+	_Iterator() requires default_initializable<_Base_iter> = default;
 
 	constexpr
-	_Iterator(filter_view* __parent, _Vp_iter __current)
+	_Iterator(_Parent* __parent, _Base_iter __current)
 	  : _M_current(std::move(__current)),
 	    _M_parent(__parent)
 	{ }
 
-	constexpr const _Vp_iter&
+	constexpr
+	_Iterator(_Iterator<!_Const> __i)
+	  requires _Const && convertible_to<iterator_t<_Vp>, iterator_t<_Base>>
+	  : _M_current(std::move(__i._M_current)),
+	    _M_parent(std::move(__i._M_parent))
+	{ }
+
+	constexpr const _Base_iter&
 	base() const & noexcept
 	{ return _M_current; }
 
-	constexpr _Vp_iter
+	constexpr _Base_iter
 	base() &&
 	{ return std::move(_M_current); }
 
-	constexpr range_reference_t<_Vp>
+	constexpr range_reference_t<_Base>
 	operator*() const
 	{ return *_M_current; }
 
-	constexpr _Vp_iter
+	constexpr _Base_iter
 	operator->() const
-	  requires __detail::__has_arrow<_Vp_iter>
-	    && copyable<_Vp_iter>
+	  requires __detail::__has_arrow<_Base_iter>
+	    && copyable<_Base_iter>
 	{ return _M_current; }
 
 	constexpr _Iterator&
@@ -1807,7 +1822,7 @@ namespace views::__adaptor
 	{ ++*this; }
 
 	constexpr _Iterator
-	operator++(int) requires forward_range<_Vp>
+	operator++(int) requires forward_range<_Base>
 	{
 	  auto __tmp = *this;
 	  ++*this;
@@ -1815,7 +1830,7 @@ namespace views::__adaptor
 	}
 
 	constexpr _Iterator&
-	operator--() requires bidirectional_range<_Vp>
+	operator--() requires bidirectional_range<_Base>
 	{
 	  do
 	    --_M_current;
@@ -1824,7 +1839,7 @@ namespace views::__adaptor
 	}
 
 	constexpr _Iterator
-	operator--(int) requires bidirectional_range<_Vp>
+	operator--(int) requires bidirectional_range<_Base>
 	{
 	  auto __tmp = *this;
 	  --*this;
@@ -1833,10 +1848,10 @@ namespace views::__adaptor
 
 	friend constexpr bool
 	operator==(const _Iterator& __x, const _Iterator& __y)
-	  requires equality_comparable<_Vp_iter>
+	  requires equality_comparable<_Base_iter>
 	{ return __x._M_current == __y._M_current; }
 
-	friend constexpr range_rvalue_reference_t<_Vp>
+	friend constexpr range_rvalue_reference_t<_Base>
 	iter_move(const _Iterator& __i)
 	  noexcept(noexcept(ranges::iter_move(__i._M_current)))
 	{ return ranges::iter_move(__i._M_current); }
@@ -1844,34 +1859,44 @@ namespace views::__adaptor
 	friend constexpr void
 	iter_swap(const _Iterator& __x, const _Iterator& __y)
 	  noexcept(noexcept(ranges::iter_swap(__x._M_current, __y._M_current)))
-	  requires indirectly_swappable<_Vp_iter>
+	  requires indirectly_swappable<_Base_iter>
 	{ ranges::iter_swap(__x._M_current, __y._M_current); }
       };
 
+      template<bool _Const>
       struct _Sentinel
       {
       private:
-	sentinel_t<_Vp> _M_end = sentinel_t<_Vp>();
+	using _Parent = __maybe_const_t<_Const, filter_view>;
+	using _Base = __maybe_const_t<_Const, _Vp>;
+	sentinel_t<_Base> _M_end = sentinel_t<_Base>();
 
-	constexpr bool
-	__equal(const _Iterator& __i) const
-	{ return __i._M_current == _M_end; }
+	friend _Sentinel<!_Const>;
 
       public:
 	_Sentinel() = default;
 
 	constexpr explicit
-	_Sentinel(filter_view* __parent)
+	_Sentinel(_Parent* __parent)
 	  : _M_end(ranges::end(__parent->_M_base))
 	{ }
 
-	constexpr sentinel_t<_Vp>
+	constexpr
+	_Sentinel(_Sentinel<!_Const> __i)
+	  requires _Const && convertible_to<sentinel_t<_Vp>, sentinel_t<_Base>>
+	  : _M_end(std::move(__i._M_end))
+	{ }
+
+	constexpr sentinel_t<_Base>
 	base() const
 	{ return _M_end; }
 
-	friend constexpr bool
-	operator==(const _Iterator& __x, const _Sentinel& __y)
-	{ return __y.__equal(__x); }
+	template<bool _Const2>
+	  requires sentinel_for<sentinel_t<_Base>,
+		     iterator_t<__maybe_const_t<_Const2, _Vp>>>
+	  friend constexpr bool
+	  operator==(const _Iterator<_Const2>& __x, const _Sentinel& __y)
+	  { return __x._M_current == __y._M_end; }
       };
 
       _Vp _M_base = _Vp();
@@ -1900,7 +1925,7 @@ namespace views::__adaptor
       pred() const
       { return *_M_pred; }
 
-      constexpr _Iterator
+      constexpr _Iterator<false>
       begin()
       {
 	if (_M_cached_begin._M_has_value())
@@ -1914,14 +1939,32 @@ namespace views::__adaptor
 	return {this, std::move(__it)};
       }
 
+      constexpr _Iterator<true>
+      begin() const
+	requires (input_range<const _Vp> && !forward_range<const _Vp>
+		  && indirect_unary_predicate<const _Pred, iterator_t<const _Vp>>)
+      {
+	__glibcxx_assert(_M_pred.has_value());
+	auto __it = ranges::find_if(ranges::begin(_M_base),
+				    ranges::end(_M_base),
+				    std::ref(*_M_pred));
+	return {this, std::move(__it)};
+      }
+
       constexpr auto
       end()
       {
 	if constexpr (common_range<_Vp>)
-	  return _Iterator{this, ranges::end(_M_base)};
+	  return _Iterator<false>{this, ranges::end(_M_base)};
 	else
-	  return _Sentinel{this};
+	  return _Sentinel<false>{this};
       }
+
+      constexpr _Sentinel<true>
+      end() const
+	requires (input_range<const _Vp> && !forward_range<const _Vp>
+		  && indirect_unary_predicate<const _Pred, iterator_t<const _Vp>>)
+      { return _Sentinel<true>{this}; }
     };
 
   template<typename _Range, typename _Pred>
diff --git a/libstdc++-v3/testsuite/std/ranges/adaptors/filter.cc b/libstdc++-v3/testsuite/std/ranges/adaptors/filter.cc
index 7595e43d4e60..5b1e1ad05bc2 100644
--- a/libstdc++-v3/testsuite/std/ranges/adaptors/filter.cc
+++ b/libstdc++-v3/testsuite/std/ranges/adaptors/filter.cc
@@ -17,12 +17,18 @@
 
 // { dg-do run { target c++20 } }
 
-#include <algorithm>
 #include <ranges>
+
+#if __cpp_lib_ranges_filter != 202603L
+# error "Feature-test macro for ranges_filter has wrong value in <ranges>."
+#endif
+
+#include <algorithm>
 #include <testsuite_hooks.h>
 #include <testsuite_iterators.h>
 
 using __gnu_test::test_range;
+using __gnu_test::input_iterator_wrapper;
 using __gnu_test::bidirectional_iterator_wrapper;
 using __gnu_test::forward_iterator_wrapper;
 using __gnu_test::random_access_iterator_wrapper;
@@ -166,6 +172,35 @@ test07()
 
 static_assert( test07() );
 
+void
+test08()
+{
+  // P3725R3 Filter View Extensions for Safer Use
+  int x[] = {1,2,3,4,5,6};
+  __gnu_test::test_range<int, input_iterator_wrapper> rx(x);
+  auto v = rx | views::filter([](int i) { return (i % 2) == 0; });
+  using R = decltype(v);
+  static_assert( ranges::input_range<R> && !ranges::forward_range<R> );
+  static_assert( ranges::input_range<const R> && !ranges::forward_range<const R> );
+  const auto& cv = v;
+  auto it = v.begin();
+  decltype(cv.begin()) cit = it;
+  auto sent = v.end();
+  decltype(cv.end()) csent = sent;
+  VERIFY( cit == cit && it == cit && cit == it );
+  VERIFY( cit != csent && it != csent && cit != sent );
+  VERIFY( ranges::equal(cv, (int[]){2,4,6}) );
+
+  [](auto&& rx) {
+    // filter_view of forward+ range is still not const-iterable.
+    static_assert( ranges::forward_range<decltype(rx)> );
+    auto v = rx | views::filter([](int) { return true; });
+    using R = decltype(v);
+    static_assert( ranges::forward_range<R> );
+    static_assert( ! ranges::range<const R> );
+  }(__gnu_test::test_range<int, forward_iterator_wrapper>(x));
+}
+
 int
 main()
 {
@@ -177,4 +212,5 @@ main()
   test05<random_access_iterator_wrapper>();
   test06();
   test07();
+  test08();
 }


More information about the Libstdc++-cvs mailing list