From d2ea2b6af63445fb327aba56f3f483f7cda92e91 Mon Sep 17 00:00:00 2001 From: Jens Maurer Date: Fri, 3 Apr 2026 09:39:54 +0200 Subject: [PATCH 1/2] P3725R3 Filter View Extensions for Safer Use, Rev 3 Fixes NB AT 9-249, RU 250, DE 251 (C++26 CD). Editorial note: * Omit template parameter name for iterator to harmonize with subclause style. --- source/ranges.tex | 170 ++++++++++++++++++++++++++++++++------------- source/support.tex | 1 + 2 files changed, 123 insertions(+), 48 deletions(-) diff --git a/source/ranges.tex b/source/ranges.tex index 0de1d2fcdb..faab821a6e 100644 --- a/source/ranges.tex +++ b/source/ranges.tex @@ -4795,10 +4795,10 @@ @\exposidnc{movable-box}@ @\exposid{pred_}@; // \expos // \ref{range.filter.iterator}, class \tcode{filter_view::\exposid{iterator}} - class @\exposid{iterator}@; // \expos + template class @\exposid{iterator}@; // \expos // \ref{range.filter.sentinel}, class \tcode{filter_view::\exposid{sentinel}} - class @\exposid{sentinel}@; // \expos + template class @\exposid{sentinel}@; // \expos public: filter_view() requires @\libconcept{default_initializable}@ && @\libconcept{default_initializable}@ = default; @@ -4809,12 +4809,21 @@ constexpr const Pred& pred() const; - constexpr @\exposid{iterator}@ begin(); + constexpr @\exposid{iterator}@ begin(); + constexpr @\exposid{iterator}@ begin() const + requires (@\libconcept{input_range}@ && !@\libconcept{forward_range}@ && + @\libconcept{indirect_unary_predicate}@>); + constexpr auto end() { if constexpr (@\libconcept{common_range}@) - return @\exposid{iterator}@{*this, ranges::end(@\exposid{base_}@)}; + return @\exposid{iterator}@{*this, ranges::end(@\exposid{base_}@)}; else - return @\exposid{sentinel}@{*this}; + return @\exposid{sentinel}@{*this}; + } + constexpr @\exposid{sentinel}@ end() const + requires (@\libconcept{input_range}@ && !@\libconcept{forward_range}@ && + @\libconcept{indirect_unary_predicate}@>) { + return @\exposid{sentinel}@{*this}; } }; @@ -4848,7 +4857,7 @@ \indexlibrarymember{begin}{filter_view}% \begin{itemdecl} -constexpr @\exposid{iterator}@ begin(); +constexpr @\exposid{iterator}@ begin(); \end{itemdecl} \begin{itemdescr} @@ -4869,7 +4878,27 @@ \tcode{filter_view} for use on subsequent calls. \end{itemdescr} -\rSec3[range.filter.iterator]{Class \tcode{filter_view::\exposid{iterator}}} +\indexlibrarymember{begin}{filter_view}% +\begin{itemdecl} +constexpr @\exposid{iterator}@ begin() const + requires (@\libconcept{input_range}@ && !@\libconcept{forward_range}@ && + @\libconcept{indirect_unary_predicate}@>); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\expects +\tcode{\exposid{pred_}.has_value()} is \tcode{true}. + +\pnum +\returns +\tcode{\{*this, ranges::find_if(\exposid{base_}, ref(*\exposid{pred_}))\}}. +\begin{note} +This function does not cache the result within the \tcode{filter_view}. +\end{note} +\end{itemdescr} + +\rSec3[range.filter.iterator]{Class template \tcode{filter_view::\exposid{iterator}}} \indexlibraryglobal{filter_view::\exposid{iterator}}% \indexlibrarymember{iterator}{filter_view}% @@ -4877,55 +4906,67 @@ namespace std::ranges { template<@\libconcept{input_range}@ V, @\libconcept{indirect_unary_predicate}@> Pred> requires @\libconcept{view}@ && is_object_v + template class filter_view::@\exposid{iterator}@ { private: - iterator_t @\exposid{current_}@ = iterator_t(); // \expos - filter_view* @\exposid{parent_}@ = nullptr; // \expos - constexpr @\exposid{iterator}@(filter_view& parent, iterator_t current); // \expos + using @\exposid{Parent}@ = @\exposid{maybe-const}@; // \expos + using @\exposid{Base}@ = @\exposid{maybe-const}@; // \expos + iterator_t @\exposid{current_}@ = iterator_t(); // \expos + @\exposid{Parent}@* @\exposid{parent_}@ = nullptr; // \expos + constexpr @\exposid{iterator}@(filter_view& parent, iterator_t current); // \expos public: using iterator_concept = @\seebelownc@; using iterator_category = @\seebelownc@; // not always present - using value_type = range_value_t; - using difference_type = range_difference_t; + using value_type = range_value_t; + using difference_type = range_difference_t; - @\exposid{iterator}@() requires @\libconcept{default_initializable}@> = default; + @\exposid{iterator}@() requires @\libconcept{default_initializable}@> = default; + constexpr @\exposid{iterator}@(iterator i) + requires Const && @\libconcept{convertible_to}@, iterator_t>; - constexpr const iterator_t& base() const & noexcept; - constexpr iterator_t base() &&; - constexpr range_reference_t operator*() const; - constexpr iterator_t operator->() const - requires @\exposconcept{has-arrow}@> && @\libconcept{copyable}@>; + constexpr const iterator_t& base() const & noexcept; + constexpr iterator_t base() &&; + constexpr range_reference_t operator*() const; + constexpr iterator_t operator->() const + requires @\exposconcept{has-arrow}@> && @\libconcept{copyable}@>; constexpr @\exposid{iterator}@& operator++(); constexpr void operator++(int); - constexpr @\exposid{iterator}@ operator++(int) requires @\libconcept{forward_range}@; + constexpr @\exposid{iterator}@ operator++(int) requires @\libconcept{forward_range}@; - constexpr @\exposid{iterator}@& operator--() requires @\libconcept{bidirectional_range}@; - constexpr @\exposid{iterator}@ operator--(int) requires @\libconcept{bidirectional_range}@; + constexpr @\exposid{iterator}@& operator--() requires @\libconcept{bidirectional_range}@; + constexpr @\exposid{iterator}@ operator--(int) requires @\libconcept{bidirectional_range}@; friend constexpr bool operator==(const @\exposid{iterator}@& x, const @\exposid{iterator}@& y) - requires @\libconcept{equality_comparable}@>; + requires @\libconcept{equality_comparable}@>; - friend constexpr range_rvalue_reference_t iter_move(const @\exposid{iterator}@& i) + friend constexpr range_rvalue_reference_t iter_move(const @\exposid{iterator}@& i) noexcept(noexcept(ranges::iter_move(i.@\exposid{current_}@))); friend constexpr void iter_swap(const @\exposid{iterator}@& x, const @\exposid{iterator}@& y) noexcept(noexcept(ranges::iter_swap(x.@\exposid{current_}@, y.@\exposid{current_}@))) - requires @\libconcept{indirectly_swappable}@>; + requires @\libconcept{indirectly_swappable}@>; }; } \end{codeblock} \pnum -Modification of the element a \tcode{filter_view::\exposid{iterator}} denotes is -permitted, but results in undefined behavior if the resulting value does not -satisfy the filter predicate. +\begin{note} +Modification of the element a \tcode{filter_view::\exposid{iterator}} denotes +can result in undefined behavior +if the underlying range is a \libconcept{forward_range} and +the resulting value does not satisfy the filter predicate +when the predicate is next evaluated for that element\iref{concepts.equality}. +\end{note} \pnum \tcode{\exposid{iterator}::iterator_concept} is defined as follows: \begin{itemize} -\item If \tcode{V} models \libconcept{bidirectional_range}, then +\item If \tcode{Const} is \tcode{true}, +then \tcode{iterator_concept} denotes \tcode{input_iterator_tag}. + +\item Otherwise, if \tcode{V} models \libconcept{bidirectional_range}, then \tcode{iterator_concept} denotes \tcode{bidirectional_iterator_tag}. \item Otherwise, if \tcode{V} models \libconcept{forward_range}, then @@ -4936,12 +4977,12 @@ \pnum The member \grammarterm{typedef-name} \tcode{iterator_category} is declared -if and only if \tcode{V} models \libconcept{forward_range}. +if and only if \tcode{Base} models \libconcept{forward_range}. In that case, \tcode{\exposid{iterator}::iterator_category} is defined as follows: \begin{itemize} \item Let \tcode{C} denote the type -\tcode{iterator_traits>::iterator_category}. +\tcode{iterator_traits>::iterator_category}. \item If \tcode{C} models \tcode{\libconcept{derived_from}}, @@ -4956,7 +4997,7 @@ \indexlibraryctor{filter_view::\exposid{iterator}}% \begin{itemdecl} -constexpr @\exposid{iterator}@(filter_view& parent, iterator_t current); +constexpr @\exposid{iterator}@(filter_view& parent, iterator_t current); \end{itemdecl} \begin{itemdescr} @@ -4966,9 +5007,22 @@ \exposid{parent_} with \tcode{addressof(parent)}. \end{itemdescr} +\indexlibraryctor{filter_view::\exposid{iterator}}% +\begin{itemdecl} +constexpr @\exposid{iterator}@(@\exposid{iterator}@ i) + requires Const && @\libconcept{convertible_to}@, iterator_t>; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Initializes \exposid{parent_} with \tcode{i.\exposid{parent_}} and +\exposid{current_} with \tcode{std::move(i.\exposid{current_})}. +\end{itemdescr} + \indexlibrarymember{base}{filter_view::\exposid{iterator}}% \begin{itemdecl} -constexpr const iterator_t& base() const & noexcept; +constexpr const iterator_t& base() const & noexcept; \end{itemdecl} \begin{itemdescr} @@ -4979,7 +5033,7 @@ \indexlibrarymember{base}{filter_view::\exposid{iterator}}% \begin{itemdecl} -constexpr iterator_t base() &&; +constexpr iterator_t base() &&; \end{itemdecl} \begin{itemdescr} @@ -4990,7 +5044,7 @@ \indexlibrarymember{operator*}{filter_view::\exposid{iterator}}% \begin{itemdecl} -constexpr range_reference_t operator*() const; +constexpr range_reference_t operator*() const; \end{itemdecl} \begin{itemdescr} @@ -5001,8 +5055,8 @@ \indexlibrarymember{operator->}{filter_view::\exposid{iterator}}% \begin{itemdecl} -constexpr iterator_t operator->() const - requires @\exposconcept{has-arrow}@> && @\libconcept{copyable}@>; +constexpr iterator_t operator->() const + requires @\exposconcept{has-arrow}@> && @\libconcept{copyable}@>; \end{itemdecl} \begin{itemdescr} @@ -5040,7 +5094,7 @@ \indexlibrarymember{operator++}{filter_view::\exposid{iterator}}% \begin{itemdecl} -constexpr @\exposid{iterator}@ operator++(int) requires @\libconcept{forward_range}@; +constexpr @\exposid{iterator}@ operator++(int) requires @\libconcept{forward_range}@; \end{itemdecl} \begin{itemdescr} @@ -5056,7 +5110,7 @@ \indexlibrarymember{operator--}{filter_view::\exposid{iterator}}% \begin{itemdecl} -constexpr @\exposid{iterator}@& operator--() requires @\libconcept{bidirectional_range}@; +constexpr @\exposid{iterator}@& operator--() requires @\libconcept{bidirectional_range}@; \end{itemdecl} \begin{itemdescr} @@ -5073,7 +5127,7 @@ \indexlibrarymember{operator--}{filter_view::\exposid{iterator}}% \begin{itemdecl} -constexpr @\exposid{iterator}@ operator--(int) requires @\libconcept{bidirectional_range}@; +constexpr @\exposid{iterator}@ operator--(int) requires @\libconcept{bidirectional_range}@; \end{itemdecl} \begin{itemdescr} @@ -5090,7 +5144,7 @@ \indexlibrarymember{operator==}{filter_view::\exposid{iterator}}% \begin{itemdecl} friend constexpr bool operator==(const @\exposid{iterator}@& x, const @\exposid{iterator}@& y) - requires @\libconcept{equality_comparable}@>; + requires @\libconcept{equality_comparable}@>; \end{itemdecl} \begin{itemdescr} @@ -5101,7 +5155,7 @@ \indexlibrarymember{iter_move}{filter_view::\exposid{iterator}}% \begin{itemdecl} -friend constexpr range_rvalue_reference_t iter_move(const @\exposid{iterator}@& i) +friend constexpr range_rvalue_reference_t iter_move(const @\exposid{iterator}@& i) noexcept(noexcept(ranges::iter_move(i.@\exposid{current_}@))); \end{itemdecl} @@ -5115,7 +5169,7 @@ \begin{itemdecl} friend constexpr void iter_swap(const @\exposid{iterator}@& x, const @\exposid{iterator}@& y) noexcept(noexcept(ranges::iter_swap(x.@\exposid{current_}@, y.@\exposid{current_}@))) - requires @\libconcept{indirectly_swappable}@>; + requires @\libconcept{indirectly_swappable}@>; \end{itemdecl} \begin{itemdescr} @@ -5124,7 +5178,7 @@ Equivalent to \tcode{ranges::iter_swap(x.\exposid{current_}, y.\exposid{current_})}. \end{itemdescr} -\rSec3[range.filter.sentinel]{Class \tcode{filter_view::\exposid{sentinel}}} +\rSec3[range.filter.sentinel]{Class template \tcode{filter_view::\exposid{sentinel}}} \indexlibraryglobal{filter_view::\exposid{sentinel}}% \indexlibrarymember{sentinel}{filter_view}% @@ -5132,21 +5186,39 @@ namespace std::ranges { template<@\libconcept{input_range}@ V, @\libconcept{indirect_unary_predicate}@> Pred> requires @\libconcept{view}@ && is_object_v + template class filter_view::@\exposid{sentinel}@ { private: - sentinel_t @\exposid{end_}@ = sentinel_t(); // \expos + using @\exposid{Base}@ = @\exposid{maybe-const}@; // \expos + sentinel_t @\exposid{end_}@ = sentinel_t(); // \expos constexpr explicit @\exposid{sentinel}@(filter_view& parent); // \expos public: @\exposid{sentinel}@() = default; + constexpr @\exposid{sentinel}@(@\exposid{sentinel}@ other) + requires Const && @\libconcept{convertible_to}@, sentinel_t> - constexpr sentinel_t base() const; + constexpr sentinel_t base() const; - friend constexpr bool operator==(const @\exposid{iterator}@& x, const @\exposid{sentinel}@& y); + template + requires @\libconcept{sentinel_for}@, iterator_t<@\exposid{maybe-const}@>> + friend constexpr bool operator==(const @\exposid{iterator}@& x, const @\exposid{sentinel}@& y); }; } \end{codeblock} +\indexlibraryctor{filter_view::\exposid{sentinel}}% +\begin{itemdecl} +constexpr @\exposid{sentinel}@(@\exposid{sentinel}@ other) + requires Const && @\libconcept{convertible_to}@, sentinel_t>; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Initializes \exposid{end_} with \tcode{std::move(other.\exposid{end_})}. +\end{itemdescr} + \indexlibraryctor{filter_view::\exposid{sentinel}}% \begin{itemdecl} constexpr explicit @\exposid{sentinel}@(filter_view& parent); @@ -5160,7 +5232,7 @@ \indexlibrarymember{base}{filter_view::\exposid{sentinel}}% \begin{itemdecl} -constexpr sentinel_t base() const; +constexpr sentinel_t base() const; \end{itemdecl} \begin{itemdescr} @@ -5171,7 +5243,9 @@ \indexlibrarymember{operator==}{filter_view::\exposid{sentinel}}% \begin{itemdecl} -friend constexpr bool operator==(const @\exposid{iterator}@& x, const @\exposid{sentinel}@& y); +template + requires @\libconcept{sentinel_for}@, iterator_t<@\exposid{maybe-const}@ >> + friend constexpr bool operator==(const @\exposid{iterator}@& x, const @\exposid{sentinel}@& y); \end{itemdecl} \begin{itemdescr} diff --git a/source/support.tex b/source/support.tex index 4663d69ba2..8dc0ebbad3 100644 --- a/source/support.tex +++ b/source/support.tex @@ -797,6 +797,7 @@ #define @\defnlibxname{cpp_lib_ranges_concat}@ 202403L // freestanding, also in \libheader{ranges} #define @\defnlibxname{cpp_lib_ranges_contains}@ 202207L // freestanding, also in \libheader{algorithm} #define @\defnlibxname{cpp_lib_ranges_enumerate}@ 202302L // freestanding, also in \libheader{ranges} +#define @\defnlibxname{cpp_lib_ranges_filter}@ 202603L // freestanding, also in \libheader{ranges} #define @\defnlibxname{cpp_lib_ranges_find_last}@ 202207L // freestanding, also in \libheader{algorithm} #define @\defnlibxname{cpp_lib_ranges_fold}@ 202207L // freestanding, also in \libheader{algorithm} #define @\defnlibxname{cpp_lib_ranges_generate_random}@ 202403L // also in \libheader{random} From 0ecbed99d6b1e43a0df5871214de7dcae9de32d5 Mon Sep 17 00:00:00 2001 From: Jens Maurer Date: Fri, 3 Apr 2026 23:17:53 +0200 Subject: [PATCH 2/2] [range.filter.iterator,range.filter.sentinel] Replace 'filter_view' with 'Parent' This is an oversight in P3725R3; without this change, the code shown in the specification would not compile. --- source/ranges.tex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/ranges.tex b/source/ranges.tex index faab821a6e..2d5388f897 100644 --- a/source/ranges.tex +++ b/source/ranges.tex @@ -4913,7 +4913,7 @@ using @\exposid{Base}@ = @\exposid{maybe-const}@; // \expos iterator_t @\exposid{current_}@ = iterator_t(); // \expos @\exposid{Parent}@* @\exposid{parent_}@ = nullptr; // \expos - constexpr @\exposid{iterator}@(filter_view& parent, iterator_t current); // \expos + constexpr @\exposid{iterator}@(@\exposid{Parent}@& parent, iterator_t current); // \expos public: using iterator_concept = @\seebelownc@; @@ -4997,7 +4997,7 @@ \indexlibraryctor{filter_view::\exposid{iterator}}% \begin{itemdecl} -constexpr @\exposid{iterator}@(filter_view& parent, iterator_t current); +constexpr @\exposid{iterator}@(@\exposid{Parent}@& parent, iterator_t current); \end{itemdecl} \begin{itemdescr} @@ -5191,7 +5191,7 @@ private: using @\exposid{Base}@ = @\exposid{maybe-const}@; // \expos sentinel_t @\exposid{end_}@ = sentinel_t(); // \expos - constexpr explicit @\exposid{sentinel}@(filter_view& parent); // \expos + constexpr explicit @\exposid{sentinel}@(@\exposid{Parent}@& parent); // \expos public: @\exposid{sentinel}@() = default; @@ -5221,7 +5221,7 @@ \indexlibraryctor{filter_view::\exposid{sentinel}}% \begin{itemdecl} -constexpr explicit @\exposid{sentinel}@(filter_view& parent); +constexpr explicit @\exposid{sentinel}@(@\exposid{Parent}@& parent); \end{itemdecl} \begin{itemdescr}