This paper proposes the
range adaptor family, which takes a range and a function that takes the current element and the current state as parameters. Basically,
is a lazy view version of
, or
with a stateful function.
To make common usage of this adaptor easier, this paper also proposes two additional adaptor that further arguments
’s functionality:
-
, which isviews :: partial_sum
with the binary function defaulted toviews :: scan
.+ -
, which isviews :: prescan
with an initial state seed.views :: scan
The
adaptor is classified as a Tier 1 item in the Ranges plan for C++26 ([P2760R1]).
1. Revision History
1.1. R1 (2024-10 pre-Wrocław Mailing)
-
Several wording fixes:
-
Added the missing
tonoexcept
.end () -
Refactored the constraints of
out into its own concept, such that it tests for both assignability fromscan_view
and the invoke result ofrange_reference_t < R >
.f -
Replace
withregular_invocable
.invocable -
Pass by move in
’s constructor and when invoking the function.scan_view
-
-
Rebase onto latest draft [N4988].
1.2. R0 (2024-07 post-St. Louis Mailing)
-
Initial revision.
2. Motivation
The motivation for this view is given in [P2760R1] and quoted below for convenience:
If you want to take a range of elements and get a new range that is applying
to every element, that’s
f . But there are many cases where you need a
transform ( f ) to that is stateful. That is, rather than have the input to
transform be the current element (and require that
f be
f ), have the input to
regular_invocable be both the current element and the current state.
f For instance, given the range
, if you want to produce the range
[ 1 , 2 , 3 , 4 , 5 ] - you can’t get there with
[ 1 , 3 , 6 , 10 , 15 ] . Instead, you need to use
transform using
scan as the binary operator. The special case of
+ over
scan is partial_sum.
+ One consideration here is how to process the first element. You might want
and you might want
[ 1 , 3 , 6 , 10 , 15 ] (with one extra element), the latter could be called a
[ 0 , 1 , 3 , 6 , 10 , 15 ] .
prescan
This adaptor is also present in ranges-v3, where it is called
with the function parameter defaulted to
. However, as [P2760R1] rightfully pointed out,
is probably not suitable for a generic operation like this that can do much more than just calculating partial sum, similar to how
is not a suitable name for a general fold. Therefore, the more generic
name is chosen to reflect the nature of this operation. (More discussion on the naming are present in the later sections.)
3. Design
3.1. Why Three Adaptors?
An immediately obvious question is why choose three names, instead of opting for a single
adaptor with overloads that take initial seed and/or function parameter? Such a design will look like this (greatly simplified):
struct scan_closure { template < ranges :: input_range Rng , typename T , std :: copy_constructible Fun = std :: plus > requires /* ... */ constexpr operator ()( Rng && rng , const T & init , Func func = {}) { /* ... */ } template < ranges :: input_range Rng , std :: copy_constructible Fun = std :: plus > requires /* ... */ constexpr operator ()( Rng && rng , Func func = {}) { /* ... */ } }; inline constexpr scan_closure scan {};
First of all, this will definitely cause some confusion to the users, due to the fact that a generic name like
defaults to
as its function parameter:
vec | views :: scan // what should this mean?
This is similar to the scenario encountered by
([P2322R6]), where despite the old algorithm
took
as the default function parameter,
still choose to not have a default. The author feels that the same should be done for
(and introduce a separate
alias that more clearly convey the intent).
However, even put aside the function parameter, why cannot the with-initial-seed version overloads with the use-first-element version?
Unfortunately, this still does not work due to the same kind of ambiguity that caused
([P2441R2]) to choose a different name instead of overload with
. Specifically, imagine someone writes a custom
that replicates all the original interface, but introduced
to mean range concatenation and broadcast:
template < typename T > struct my_vector : public std :: vector < T > { using std :: vector < T >:: vector ; // broadcast: [1, 2, 3] + 10 = [11, 12, 13] friend my_vector operator + ( my_vector vec , const T & value ) { for ( auto & elem : vec ) elem += value ; return vec ; } friend my_vector operator + ( const T & value , my_vector vec ) { /* Same */ } // range concatenation: [1, 2, 3] + [4, 5] = [1, 2, 3, 4, 5] friend my_vector operator + ( my_vector vec , const my_vector & vec2 ) { vec . append_range ( vec2 ); return vec ; } // operator+= implementation omitted };
Although one could argue that this is a misuse of
overloading, this is definitely plausible code one could write. Now consider:
my_vector < int > vec { 1 , 2 , 3 }, vec2 { 4 , 5 }; views :: partial_sum ( vec ); // [1, 3, 6] vec2 | views :: partial_sum ( vec ); // [[1, 2, 3], [5, 6, 7], [10, 11, 12]] (!!)
The second invocation,
, is equivalent to
, therefore interpreted as "using
as the initial seed, and add each element of
to it". Unfortunately, we cannot differentiate the two cases, since they both invoke
. This ambiguity equally affects
, since
and
are also ambiguous.
There are several approaches we can adopt to handle this ambiguity:
Option 1: Bail out. Simply don’t support the initial seed case, or just declare that anything satisfy
that comes in the first argument will be treated as the input range.
-
Pros: No need to decide on new names.
-
Cons: Losing a valuable use case that was accustomed by users since
exists. If the "declare" option is chosen, then potentially lose more use case likestd :: exclusive_scan
and cause some confusion.my_vector
Option 2: Reorder arguments and bail out. We can switch the function and the initial seed argument, such that the signature is
, and declare that anything satisfy
that comes in the first argument will be treated as the input range.
-
Pros: No need to decide on new names.
-
Cons:
-
Still have potential of conflict if a class that is both a range and a binary functor is passed in
-
Cannot support
with initial seed (discard or need new name)partial_sum -
Inconsistent argument order with
ranges :: fold
-
Option 3: Choose separate name for
with and without initial seed.
-
Pros: No potential of conflicts
-
Cons: Need to decide on 1-2 new names and more wording effort
The author prefers Option 3 as it is the least surprising option that has no potential of conflicts. For now, the author decides that
with an initial seed should be called
, as suggested in [P2760R1], and
should not support initial seed at all. The rationale for this decision is that people who want
with initial seed can simply call
, so instead of coming up a name that is potentially longer and harder to memorize the author felt that this is the best approach.
More alternative names are suggested in later sections.
3.2. Prior Art
The scan adaptor/algorithm had made an appearance in many different libraries and languages:
-
range-v3 has a
adaptor that don’t take initial seeds, but takes arbitrary function parameter (defaults toviews :: partial_sum
).+ -
also has an implementation oftl :: ranges
.views :: partial_sum -
Python has an
algorithm that optionally takes initial seeds (with a named argument), and takes arbitrary function parameter (defaults toitertools . accumulate
).+ -
Furthermore, NumPy provides
algorithm that returns a partial sum (without function parameter), and ancumsum
algorithm that performs arbitrary scans with arbitrary function parameter that have no default. Neither of those algorithms takes an initial seed.ufunc . accumulate
-
-
Rust has an
adaptor that takes initial seeds and arbitrary function parameters (no defaults provided).iter . scan
Summarized in a table:
Library | Signature | Function | With Default | Initial Seed |
range-v3 |
| ✅ | ✅ | ❌ |
Python
|
| ✅ | ✅ | ✅ |
NumPy |
| ❌ | N/A | ❌ |
| ✅ | ❌ | ❌ | |
Rust |
| ✅ | ❌ | ✅ |
Proposed |
| ✅ | ❌ | ❌ |
| ✅ | ❌ | ✅ | |
| ❌ | N/A | ❌ |
3.3. Alternative Names
The author thinks
and
are pretty good names. The former have prior example in
and in Rust, and is a generic enough name that will not cause confusion. The latter also have prior example in
, and partial sum is definitely one of the canonical terms of describing this operation.
Alternative names considered for
: (in order of decreasing preference)
-
(suggested by [P2214R2], and makes the connection withviews :: partial_fold
clear): Pretty good name, sinceranges :: fold
is just ascan
with intermediate state saved. However, the correct analogy is actuallyfold
, andranges :: fold_left_first
actually corresponds toranges :: fold_left
, which the author felt may cause some confusion.views :: prescan -
,views :: accumulate
,views :: fold
: The output is a range instead of a number, so using the same name probably is not accurate. The latter two also suffer from the same displaced correspondence problem.views :: fold_left
Alternative names considered for
: (in order of decreasing preference)
-
: Another canonical term for this operation, but doesn’t have prior examples.views :: prefix_sum -
orviews :: cumsum
: Have prior example in NumPy, but in the spirit of Ranges naming we probably need to choose the latter which is a bit long.views :: cumulative_sum
Alternative naming schemes for all 3 or 4 adaptors proposed:
Option A: Name
as
, and
as
. This will make the three adaptors have the same name as the three algorithms already existed in
, which may seems a good idea at first glance. However,
, despite having a generic name, actually requires its function parameter to be associative (or, more precisely, allow for arbitrary order of executing the function on elements), which
or
does not. So the author felt that reusing the same name may cause some misuse of algorithms.
Option B: Name
as
, and
as
. This will make the naming consistent with the
family, but penalize the more common form of without-initial-seed by making it longer and harder to type. This option also have the advantage of being able to spell
and
as the two form of partial sums, instead of being forced to discard one.
Option C: Keep
, but name
as
(meaning providing an init parameter). Does not penalize the common case, but also inconsistent with
. This option also have the advantage of being able to spell
and
as the two form of partial sums, instead of being forced to discard one.
Overall, the author don’t feel any of these options as particularly intriguing, and opt to propose the original naming of
,
and
.
3.4. Left or Right Fold?
Theoretically, there are two possible direction of scanning a range:
// rng = [x1, x2, x3, ...] prescan_left ( rng , i , f ) // [i, f(i, x1), f(f(i, x1), x2), ...] prescan_right ( rng , i , f ) // [i, f(x1, i), f(x2, f(x1, i)), ...]
Both are certainly viable, which begs the question: Should we provide both?
On the one hand,
provided both the left and the right fold version, despite the fact that right fold can be simulated by reversing the range and the function parameter order. However, here, the simulation is even easier: just reversing the order of the function parameters will turn a left scan to a right scan.
Furthermore, all of the mentioned prior arts perform left scan, and it is hard to come up with a valid use case of right scan that cannot be easily covered by left scan. Therefore, the author only proposes left scan in this proposal.
3.5. More Convenience Aliases
Obviously, there are more aliases that can be provided besides
. The three most useful aliases are:
std :: vector < int > vec { 3 , 4 , 6 , 2 , 1 , 9 , 0 , 7 , 5 , 8 } partial_sum ( vec ) // [3, 7, 13, 15, 16, 25, 25, 32, 37, 45] partial_product ( vec ) // [3, 12, 72, 144, 144, 1296, 0, 0, 0, 0] running_min ( vec ) // [3, 3, 3, 2, 1, 1, 0, 0, 0, 0] running_max ( vec ) // [3, 4, 6, 6, 6, 9, 9, 9, 9, 9]
Which are the results of applying
,
,
, and
.
Looking at the results, these are certainly very useful aliases, even as useful as
. However, as stated above, all of these three aliases can be achieved by simply passing the corresponding function object (currently,
/
doesn’t have a function object, but
and
will be useable after [P3136R0] landed) as the function parameter, so I’m not sure it is worth the hassle of specification. Therefore, currently this proposal do not include the other three aliases, but the author is happy to add them should SG9/LEWG request.
As for
itself, there are several reasons why this alias should be added, that is certainly stronger than the case for
,
and
:
-
All existing implementation of scan-like algorithm defaults the function argument to
whenever there is a default.+ -
existsstd :: partial_sum -
Partial sum is one of the most studied and used concept in programming, arguably even more useful than running min/max.
3.6. Range Properties
All three proposed views are range adaptors, i.e. can be piped into.
is just an alias for
, so it will not be mentioned in the below analysis.
3.6.1. Reference and Value Type
Consider the following:
std :: vector < double > vec { 1.0 , 1.5 , 2.0 }; vec | views :: prescan ( 1 , std :: plus {});
Obviously, we expect the result to be
, not
, therefore for
we cannot just use the initial seed’s type as the resulting range’s reference/value type.
There are two choices we can make: (for input range type
, function type
and initial seed type
)
-
Just use the reference/value type of the input range. This is consistent with
. (range-v3 also chose this approach, using the input range’s value type as the reference type ofstd :: partial_sum
)scan_view -
Be a bit clever, and use
. In other words, the return type ofremove_cvref_t < invoke_result_t < F & , T & , ranges :: range_reference_t < Rng >>>
.func ( init , * rng . begin ())
Note that the second option don’t really covers all kind of functions, since it is entirely plausible that
will change its return type in every invocation. However, this should be enough for nearly all normal cases, and is the decision chosen by [P2322R6] for
. For
, this approach can also work, by returning the return type of
.
Although the second choice is a bit complex in design, it also avoids the following footgun:
std :: vector < int > vec { 1 , 4 , 2147483647 , 3 }; vec | views :: prescan ( 0L , std :: plus {});
With the first choice, the resulting range’s value type will be
, which would result in UB due to overflow. With the second choice the resulting value type will be
which is fine. (Unfortunately, if you used
here it will still be UB, and that cannot be fixed.)
The second choice also enables the following use case:
// Assumes that std::to_string also has an overload for std::string that just returns the argument std :: vector < int > vec { 1 , 2 , 3 }; vec | views :: prescan ( "2" , []( const auto & a , const auto & b ) { return std :: to_string ( a ) + std :: to_string ( b ); }) // ["2", "21", "212", "2123"]
Given that
chose the second approach, the author thinks that the second approach is the correct one to pursue.
3.6.2. Category
At most forward. (In other words, forward if the input range is forward, otherwise input.)
The resulting range cannot be bidirectional, since the function parameter cannot be applied in reverse. A future extension may enable a
with both forward and backward function parameter, but that is outside the scope of this paper.
3.6.3. Common
Never.
This is consistent with range-v3’s implementation. The reasoning is that each iterator needs to store both the current position and the current partial sum (generalized), so that the
position’s iterator is not readily available in O(1) time.
3.6.4. Sized
If and only if the input range is sized.
For
, the size is always equal to the input range’s size. For
, the size is always equal to the input range’s size plus one.
3.6.5. Const-Iterable
Similar to
, if and only if the input range is const-iterable and
is
-invocable.
3.6.6. Borrowed
Never.
At least for now. Currently,
is never borrowed, but after [P3117R0] determined suitable criteria for storing the function parameter in the iterator, it can be conditionally borrowed.
Theoretically, the same can be applied to
to make it conditionally borrowed by storing the function and the initial value inside the iterator. However, the author would like to wait until [P3117R0] lands to make this change.
3.7. Feature Test Macro
This proposal added a new feature test macro
, which signals the availability of all three adaptors.
An alternate design is to introduce 3 macros, but the author felt such granularity is not needed.
3.8. Freestanding
[P1642R11] included nearly everything in
into freestanding, except
and the corresponding view types. However, range adaptors added after [P1642R11], like
and
did not include themselves in freestanding.
The author assumes that this is an oversight, since I cannot see any reason for those views to not be in freestanding. As a result, the range adaptor proposed by this paper will be included in freestanding. (Adding freestanding markers to the two views mentioned above is probably out of scope for this paper, but if LWG decides that this paper should also solve that oversight, the author is happy to comply.)
4. Implementation Experience
The author implemented this proposal in Compiler Explorer. No significant obstacles are observed.
Note that to save on implementation effort,
is simply aliased to
, so they both shares a single underlying view.
5. Wording
The wording below is based on [N4988].
Wording notes for LWG and editor:
-
The wording currently reflects Option 3 from the why three adaptors section, with name as
,views :: scan
andprescan
.partial_sum -
andviews :: scan
use the same underlyingviews :: prescan
to save on complexity and duplication of wording.ranges :: scan_view -
Currently, the three views' definition reside just after
’s definition and synopsis, due to the author’s perception that they are pretty similar.views :: transform -
The exposition-only concept
andscannable
is basically the same asscannable - impl
andindirectly - binary - left - foldable
; the only difference is thatindirectly - binary - left - foldable - impl
is required to be move constructible instead of copy constructible.F
Wording questions to be resolved:
-
Should the view be called
orscan_view
?prescan_view -
Currently, the current sum is cached in the iterator object (as implemented in range-v3). An alternative is to cache the current sum in the view object (as implemented in
), such that each iterator only needs to hold a pointer to the parent view and an iterator to the current position. Which one should be chosen?tl :: ranges -
Two strategy exist to defer
toscan
. First (used currently) is to define two constructors forprescan
that takes two and three arguments, and have one constructor delegates to the other. The other approach is to specify thatscan_view
is expression-equivalent toscan ( E , F )
.scan_view ( E , * ranges :: begin ( E ), F ) -
Due to never being a common range,
simply have a singlescan_view
member that returnsend () const
. Is that the correct way to do this, or should I write two differentdefault_sentinel
function for both cases, with different constraints, but both returnsend ()
? Or should I actually write adefault_sentinel
subclass that wraps the iterator?sentinel < Const > -
The extra
parameter seems a bit clunky.bool IsInit -
orregular_invocable
? Currently the wording uses the latter (following range-v3), butinvocable
used the former.transform_view -
The return type of
seems unorthodox.operator * ()
5.1. 17.3.2 Header < version >
synopsis [version.syn]
In this clause’s synopsis, insert a new macro definition in a place that respects the current alphabetical order of the synopsis, and substituting
by the date of adoption.
#define __cpp_lib_ranges_scan 20XXYYL // freestanding, also in <ranges>
5.2. 26.2 Header < ranges >
synopsis [ranges.syn]
Modify the synopsis as follows:
// [...] namespace std :: ranges { // [...] // [range.transform], transform view template < input_range V , move_constructible F , bool IsInit > requires view < V > && is_object_v < F > && regular_invocable < F & , range_reference_t < V >> && can - reference < invoke_result_t < F & , range_reference_t < V >>> class transform_view ; // freestanding namespace views { inline constexpr unspecified transform = unspecified ; } // freestanding // [range.scan], scan view template < input_range V , typename T , move_constructible F > requires see below class scan_view ; // freestanding namespace views { inline constexpr unspecified scan = unspecified ; // freestanding inline constexpr unspecified prescan = unspecified ; // freestanding inline constexpr unspecified prefix_sum = unspecified ; // freestanding } // [range.take], take view template < view > class take_view ; // freestanding template < class T > constexpr bool enable_borrowed_range < take_view < T >> = enable_borrowed_range < T > ; // freestanding namespace views { inline constexpr unspecified take = unspecified ; } // freestanding // [...] }
Editor’s Note: Add the following subclause to 26.7 Range adaptors [range.adaptors], after 26.7.9 Transform view [range.transform]
5.3. 26.7.� Scan view [range.scan]
5.4. 26.7.�.1 Overview [range.scan.overview]
-
presents a view that accumulates the results of applying a transformation function to the current state and each element.scan_view -
The name
denotes a range adaptor object ([range.adaptor.object]). Given subexpressionsviews :: scan
andE
, the expressionF
is expression-equivalent toviews :: scan ( E , F )
.scan_view ( E , F )
[Example 1:
vector < int > vec { 1 , 2 , 3 , 4 , 5 }; for ( auto && i : std :: views :: scan ( vec , std :: plus {})) { std :: ( "{} " , i ); // prints 1 3 6 10 15 }
-- end example]
-
The name
denotes a range adaptor object ([range.adaptor.object]). Given subexpressionsviews :: prescan
,E
andF
, the expressionG
is expression-equivalent toviews :: prescan ( E , F , G )
.scan_view ( E , F , G )
[Example 2:
vector < int > vec { 1 , 2 , 3 , 4 , 5 }; for ( auto && i : std :: views :: prescan ( vec , 10 , std :: plus {})) { std :: ( "{} " , i ); // prints 10 11 13 16 20 25 }
-- end example]
-
The name
denotes a range adaptor object ([range.adaptor.object]). Given subexpressionviews :: partial_sum
, the expressionE
is expression-equivalent toviews :: partial_sum ( E )
.scan_view ( E , std :: plus {})
5.5. 26.7.�.2 Class template scan_view
[range.scan.view]
namespace std :: ranges { template < typename V , typename T , typename F , typename U > concept scannable - impl = // exposition only movable < U > && convertible_to < T , U > && invocable < F & , U , range_reference_t < V >> && assignable_from < U & , invoke_result_t < F & , U , range_reference_t < V >>> ; template < typename V , typename T , typename F > concept scannable = // exposition only invocable < F & , T , range_reference_t < V >> && convertible_to < invoke_result_t < F & , T , range_reference_t < V >> , decay_t < invoke_result_t < F & , T , range_reference_t < V >>>> && scannable - impl < V , T , F , decay_t < invoke_result_t < F & , T , range_reference_t < V >>>> ; template < input_range V , move_constructible T , move_constructible F , bool IsInit = false> requires view < V > && is_object_v < T > && is_object_v < F > && scannable < V , T , F > class scan_view : public view_interface < scan_view < V , T , F , IsInit >> { private : // [range.scan.iterator], class template scan_view::iterator template < bool > struct iterator ; // exposition only V base_ = V (); // exposition only movable - box < T > init_ ; // exposition only movable - box < F > fun_ ; // exposition only public : scan_view () requires default_initializable < V > && default_initializable < F > = default ; constexpr explicit scan_view ( V base , F fun ) requires ( ! IsInit ); constexpr explicit scan_view ( V base , T init , F fun ) requires IsInit ; constexpr V base () const & requires copy_constructible < V > { return base_ ; } constexpr V base () && { return std :: move ( base_ ); } constexpr iterator < false> begin (); constexpr iterator < true> begin () const requires range < const V > && scannable < const V , T , const F > ; constexpr default_sentinel_t end () const noexcept { return default_sentinel ; } constexpr auto size () requires sized_range < V > { return ranges :: size ( base_ ) + ( IsInit ? 1 : 0 ); } constexpr auto size () const requires sized_range < const V > { return ranges :: size ( base_ ) + ( IsInit ? 1 : 0 ); } }; template < class R , class F > scan_view ( R && , F ) -> scan_view < views :: all_t < R > , range_value_t < R > , F , false> ; template < class R , class T , class F > scan_view ( R && , T , F ) -> scan_view < views :: all_t < R > , T , F , true> ; }
constexpr explicit scan_view ( V base , F fun ) requires ( ! IsInit );
-
Effects: Initializes
withbase_
andstd :: move ( base )
withfun_
.std :: move ( fun )
constexpr explicit scan_view ( V base , T init , F fun ) requires IsInit ;
-
Effects: Initializes
withbase_
,std :: move ( base )
withinit_
, andstd :: move ( init )
withfun_
.std :: move ( fun )
constexpr iterator < false> begin ();
-
Effects: Equivalent to:
return iterator < false> { * this , ranges :: begin ( base_ )};
constexpr iterator < true> begin () const requires range < const V > && scannable < const V , T , const F > ;
-
Effects: Equivalent to:
return iterator < true> { * this , ranges :: begin ( base_ )};
5.6. 26.7.�.3 Class template scan_view :: iterator
[range.scan.iterator]
namespace std :: ranges { template < input_range V , move_constructible T , move_constructible F , bool IsInit > requires view < V > && is_object_v < T > && is_object_v < F > && scannable < V , T , F > template < bool Const > class scan_view < V , T , F , IsInit >:: iterator { private : using Parent = maybe - const < Const , scan_view > ; // exposition only using Base = maybe - const < Const , V > ; // exposition only using RefType = invoke_result_t < maybe - const < Const , F >& , T , range_reference_t < Base >> ; // exposition only using SumType = decay_t < RefType > ; // exposition only iterator_t < Base > current_ = iterator_t < Base > (); // exposition only Parent * parent_ = nullptr ; // exposition only movable - box < SumType > sum_ ; // exposition only bool is_init_ = IsInit ; // exposition only public : using iterator_concept = conditional_t < forward_range < Base > , forward_iterator_tag , input_iterator_tag > ; using iterator_category = see below ; // present only if Base models forward_range using value_type = SumType ; using difference_type = range_difference_t < Base > ; iterator () requires default_initializable < iterator_t < Base >> = default ; constexpr iterator ( Parent & parent , iterator_t < Base > current ); constexpr iterator ( iterator <! Const > i ) requires Const && convertible_to < iterator_t < V > , iterator_t < Base >> ; constexpr const iterator_t < Base >& base () const & noexcept ; constexpr iterator_t < Base > base () && ; constexpr const SumType & operator * () const { return * sum_ ; } constexpr iterator & operator ++ (); constexpr void operator ++ ( int ); constexpr iterator operator ++ ( int ) requires forward_range < Base > ; friend constexpr bool operator == ( const iterator & x , const iterator & y ) requires equality_comparable < iterator_t < Base >> ; friend constexpr bool operator == ( const iterator & x , default_sentinel_t ); }; }
-
If
does not modelBase
there is no memberforward_range
. Otherwise, the typedef-nameiterator_category
denotes:iterator_category
-
ifforward_iterator_tag
modelsiterator_traits < iterator_t < Base >>:: iterator_category
andderived_from < forward_iterator_tag >
isis_reference_v < invoke_result_t < maybe - const < Const , F >& , maybe - const < Const , T >& , range_reference_t < Base >>> true
; -
otherwise,
.input_iterator_tag
constexpr iterator ( Parent & parent , iterator_t < Base > current );
-
Effects: Initializes
withcurrent_
andstd :: move ( current )
withparent_
. Then, equivalent to:addressof ( parent )
if constexpr ( IsInit ) { sum_ = * parent_ -> init_ ; } else { if ( current_ != ranges :: end ( parent_ -> base_ )) { sum_ = * current_ ; } }
constexpr iterator ( iterator <! Const > i ) requires Const && convertible_to < iterator_t < V > , iterator_t < Base >> ;
-
Effects: Initializes
withcurrent_
,std :: move ( i . current_ )
withparent_
,i . parent_
withsum_
, andstd :: move ( i . sum_ )
withis_init_
.i . is_init_
constexpr const iterator_t < Base >& base () const & noexcept ;
-
Effects: Equivalent to:
return current_ ;
constexpr iterator_t < Base > base () && ;
-
Effects: Equivalent to:
return std :: move ( current_ );
constexpr iterator & operator ++ ();
-
Effects: Equivalent to:
if ( ! is_init_ ) { ++ current_ ; } else { is_init_ = false; } if ( current_ != ranges :: end ( parent_ -> base_ )) { sum_ = invoke ( * parent_ -> fun_ , std :: move ( * sum_ ), * current_ ); } return * this ;
constexpr void operator ++ ( int );
-
Effects: Equivalent to
.++* this
constexpr iterator operator ++ ( int ) requires forward_range < Base > ;
-
Effects: Equivalent to:
auto tmp = * this ; ++* this ; return tmp ;
friend constexpr bool operator == ( const iterator & x , const iterator & y ) requires equality_comparable < iterator_t < Base >> ;
-
Effects: Equivalent to:
return x . current_ == y . current_ ;
friend constexpr bool operator == ( const iterator & x , default_sentinel_t );
-
Effects: Equivalent to:
return x . current_ == ranges :: end ( x . parent_ -> base_ );