1. Revision History
r4: Integrate SG9 feedback (Issaquah)
- 
     Fix missing change from input_or_output_iterator to input_iterator in the synopsis 
- 
     Simplify the definition of when two iterators refer to the same sequence 
- 
     Simplify the definition of void operator ++ ( int ) 
- 
     Add opens section for additional design suggestions/questions raised 
- 
     Add implementation experience section 
r3: Itegreating LEWG feedback:
- 
     Define iterator_concept iterator_category counted_iterator 
- 
     Require input_iterator operator ++ ( int ) output_iterator 
- 
     General cleanup 
r2: Integrating SG9 feedback:
- 
     Removing references to p2578, after SG9 vote against it 
- 
     Fix design suggested 
- 
     Add design alternatives 
r1: Improving many parts, following feedback from Inbal Levi and from Reddit users
r0: initial revision
2. Problem description
2.1. Range with the exact number of items
Look at this example code [CE-FILTER]:
#include <ranges>#include <iostream>namespace rv = std :: views ; int main () { for ( auto i : rv :: iota ( 0 ) | rv :: filter ([]( auto i ) { return i < 11 ; }) | rv :: take ( 11 )) std :: cout << i << '\n' ; } 
Compiler explorer gets a timeout when trying to run this simple example, instead
of printing the numbers from 0 to 10. Running the same code locally, it runs for
very long time. Tracking the roots of the issue, the problem is that 
The example above is just for illustration, but we can think about cases where
it isn’t clear for the user how many items the filter is expected to return, so
limiting the output count with 
It means 
2.2. input_iterator 
   Even more common problem is when using input ranges, e.g. 
#include <ranges>#include <iostream>#include <sstream>#include <cassert>namespace rn = std :: ranges ; namespace rv = rn :: views ; int main () { auto iss = std :: istringstream ( "0 1 2" ); for ( auto i : rn :: istream_view < int > ( iss ) | rv :: take ( 1 )) std :: cout << i << '\n' ; auto i = 0 ; iss >> i ; std :: cout << i << std :: endl ; // flush it in case the assert fails assert ( i == 1 ); // FAILS, i == 2 } 
It makes it harder to use ranges for things like parsing input, if the rest of the stream is still to be used or we aren’t sure there is any additional element in the stream.
Seems like this was discussed in [range-v3-issue57], and there was no decision what is the right solution.
3. Current behavior is what the standard mandates
Under 23.5.6.5 [counted.iter.nav], the standard defines the behavior of 
Effects: Equivalent to:
 
 
 
It means that even when 
4. Desired behavior
As long as 
5. High-level design of the proposed solution
We propose adding a new iterator type, 
Additionally, this requires adding 
6. Design points for discussion
6.1. Consructing with 0 count
Similarly to 
Please note that 
The solution accepted in SG9 is to:
- 
     Cap lazy_counted_iterator forward_iterator 
- 
     Don’t provide base () 
This also simplifies the implementation, as there is no requirement to to differentiate between these two states of the underlying iterator. (Implementations might still decide to track it for providing additional diagnostics for violations of the precondition of iterator comparison.)
6.2. Return type of operator ++ ( int ) 
   For non-forward iterators, today counted_iterator::operator++(int) is defined
with 
6.3. Why lazy_take take 
   We could have change 
We aren’t happy with the additional burden on teachability, but we believe in
most cases users can just use 
7. Wording
7.1. Wording for lazy_counted_iterator 
   Under Header 
// [iterators.counted], counted iterators template < input_or_output_iterator I > class counted_iterator ; // freestanding template < input_iterator I > requires see below struct iterator_traits < counted_iterator < I >> ; // freestanding 
// [iterators.lazy.counted], lazy counted iterators template < input_iterator I > class lazy_counted_iterator ; // freestanding 
In Iterator adaptors [predef.iterators], after 25.5.7 Counted iterators [iterators.counted] add new section:
25.5.x Lazy counted iterators [iterators.lazy.counted]Under this section add:
7.1.1. x.1 Class template lazy_counted_iterator 
   Class template 
[Example 1:
— end example]list < string > s ; // populate the list s with at least 10 strings vector < string > v ; // copies 10 strings into v: ranges :: copy ( lazy_counted_iterator ( s . begin (), 10 ), default_sentinel , back_inserter ( v )); 
Two values 
namespace std { template < input_iterator I > class lazy_counted_iterator { public : using iterator_type = I ; using value_type = iter_value_t < I > ; using difference_type = iter_difference_t < I > ; using iterator_concept = see below ; using iterator_category = see below ; // not always present constexpr lazy_counted_iterator () requires default_initializable < I > = default ; constexpr lazy_counted_iterator ( I x , iter_difference_t < I > n ); template < class I2 > requires convertible_to < const I2 & , I > constexpr lazy_counted_iterator ( const lazy_counted_iterator < I2 >& x ); template < class I2 > requires assignable_from < I & , const I2 &> constexpr lazy_counted_iterator & operator = ( const lazy_counted_iterator < I2 >& x ); constexpr iter_difference_t < I > count () const noexcept ; constexpr decltype ( auto ) operator * (); constexpr decltype ( auto ) operator * () const requires dereferenceable < const I > ; constexpr lazy_counted_iterator & operator ++ (); constexpr void operator ++ ( int ); constexpr lazy_counted_iterator operator ++ ( int ) requires forward_iterator < I > ; template < common_with < I > I2 > friend constexpr iter_difference_t < I2 > operator - ( const lazy_counted_iterator & x , const lazy_counted_iterator < I2 >& y ); friend constexpr iter_difference_t < I > operator - ( const lazy_counted_iterator & x , default_sentinel_t ); friend constexpr iter_difference_t < I > operator - ( default_sentinel_t , const lazy_counted_iterator & y ); template < common_with < I > I2 > friend constexpr bool operator == ( const lazy_counted_iterator & x , const lazy_counted_iterator < I2 >& y ); friend constexpr bool operator == ( const lazy_counted_iterator & x , default_sentinel_t ); template < common_with < I > I2 > friend constexpr strong_ordering operator <=> ( const lazy_counted_iterator & x , const lazy_counted_iterator < I2 >& y ); friend constexpr iter_rvalue_reference_t < I > iter_move ( const lazy_counted_iterator & i ) noexcept ( noexcept ( ranges :: iter_move ( i . current ))); template < indirectly_swappable < I > I2 > friend constexpr void iter_swap ( const lazy_counted_iterator & x , const lazy_counted_iterator < I2 >& y ) noexcept ( noexcept ( ranges :: iter_swap ( x . current , y . current ))); private : I current = I (); // exposition only iter_difference_t < I > length = 0 ; // exposition only }; } 
The member typedef-name 
- 
     forward_iterator_tag Iterator forward_iterator 
- 
     input_iterator_tag 
The member typedef-name 
- 
     forward_iterator_tag iterator_traits < Iterator >:: iterator_category derived_from < forward_iterator_tag > 
- 
     iterator_traits < Iterator >:: iterator_category 
7.1.2. x.2 Constructors and conversions [lazy.counted.iter.const]
Preconditions: n >= 0.
Effects: Initializes 
template < class I2 > requires convertible_to < const I2 & , I > constexpr lazy_counted_iterator ( const lazy_counted_iterator < I2 >& x ); 
Effects: Initializes 
template < class I2 > requires assignable_from < I & , const I2 &> constexpr lazy_counted_iterator & operator = ( const lazy_counted_iterator < I2 >& x ); 
Effects: Assigns 
Returns: 
7.1.3. x.3 Accessors [lazy.counted.iter.access]
Effects: Equivalent to: 
7.1.4. x.4 Element access [lazy.counted.iter.elem]
constexpr decltype ( auto ) operator * (); constexpr decltype ( auto ) operator * () const requires dereferenceable < const I > ; 
Preconditions: true.
Effects: Equivalent to: 
7.1.5. x.5 Navigation [lazy.counted.iter.nav]
Preconditions: 
Effects: Equivalent to:
if ( length > 1 ) ++ current ; -- length ; return * this ; 
Preconditions: 
Effects: Equivalent to:
++* this ; 
Effects: Equivalent to:constexpr lazy_counted_iterator operator ++ ( int ) requires forward_iterator < I > ; 
lazy_counted_iterator tmp = * this ; ++* this ; return tmp ; 
Preconditions:template < common_with < I > I2 > friend constexpr iter_difference_t < I2 > operator - ( const lazy_counted_iterator & x , const lazy_counted_iterator < I2 >& y ); 
x y Effects: Equivalent to: 
Effects: Equivalent to:friend constexpr iter_difference_t < I > operator - ( const lazy_counted_iterator & x , default_sentinel_t ); 
return  - x . length ; Effects: Equivalent to:friend constexpr iter_difference_t < I > operator - ( default_sentinel_t , const lazy_counted_iterator & y ); 
return  y . length ; 7.1.6. x.6 Comparisons [lazy.counted.iter.cmp]
Preconditions:template < common_with < I > I2 > friend constexpr bool operator == ( const lazy_counted_iterator & x , const lazy_counted_iterator < I2 >& y ); 
x y Effects: Equivalent to: 
Effects: Equivalent to:friend constexpr bool operator == ( const lazy_counted_iterator & x , default_sentinel_t ); 
return  x . length  ==  0 ; Preconditions:template < common_with < I > I2 > friend constexpr strong_ordering operator <=> ( const lazy_counted_iterator & x , const lazy_counted_iterator < I2 >& y ); 
x y Effects: Equivalent to: 
[Note 1: The argument order in the Effects: element is reversed because 
7.1.7. x.7 Customizations [lazy.counted.iter.cust]
Preconditions:friend constexpr iter_rvalue_reference_t < I > iter_move ( const lazy_counted_iterator & i ) noexcept ( noexcept ( ranges :: iter_move ( i . current ))); 
i . length  >  0 true. 
   Effects: Equivalent to: 
Preconditions: Bothtemplate < indirectly_swappable < I > I2 > friend constexpr void iter_swap ( const lazy_counted_iterator & x , const lazy_counted_iterator < I2 >& y ) noexcept ( noexcept ( ranges :: iter_swap ( x . current , y . current ))); 
x . length  >  0 y . length  >  0 true. 
   Effects: Equivalent to 
7.2. Wording for views :: lazy_counted lazy_take_view 
   Under Header 
// [range.counted], counted view namespace views { inline constexpr unspecified counted = unspecified ; } // freestanding 
// [range.lazy.counted], lazy counted view namespace views { inline constexpr unspecified lazy_counted = unspecified ; } // freestanding 
// [range.take], take view template < view > class take_view ; // freestanding template < class T > constexpr bool enable_borrowed_range < take_view < T >> = // freestanding enable_borrowed_range < T > ; namespace views { inline constexpr unspecified take = unspecified ; } // freestanding 
// [range.lazy.take], lazy take view template < view > class lazy_take_view ; // freestanding template < class T > constexpr bool enable_borrowed_range < lazy_take_view < T >> = // freestanding enable_borrowed_range < T > ; namespace views { inline constexpr unspecified lazy_take = unspecified ; } // freestanding 
7.3. Wording for views :: lazy_counted 
   In Range adaptors [range.adaptors], after 26.7.18 Counted view [range.counted] add new section:
7.3.1. 26.7.x Lazy counted view [range.lazy.counted]
A lazy counted view presents a view of the elements of the counted range
([iterator.requirements.general]) 
The name 
[Note 1: This case can result in substitution failure when 
Otherwise, 
- 
     If T contiguous_iterator span ( to_address ( E ), static_cast < size_t > ( static_ - cast < D > ( F ))) 
- 
     Otherwise, if T random_access_iterator subrange ( E , E + static_cast < D > ( F )) E 
- 
     Otherwise, subrange ( lazy_counted_iterator ( E , F ), default_sentinel ) 
7.4. Wording for lazy_take_view 
   After 26.7.10 Take view [range.take] add new section:
26.7.x Lazy take view [range.lazy.take]Under this section add:
7.4.1. x.1 Overview [range.lazy.take.overview]
The name 
- 
     If T ranges :: empty_view (( void ) F , decay - copy ( E )) E F 
- 
     Otherwise, if T random_access_range sized_range span basic_string_view ranges :: subrange U ( ranges :: begin ( E ), ranges :: begin ( E ) + std :: min < D > ( ranges :: distance ( E ), F )) E U - 
       if T U span < typename T :: element_type > 
- 
       otherwise, if T basic_string_view U T 
- 
       otherwise, T ranges :: subrange U ranges :: subrange < iterator_t < T >> 
- 
       otherwise, if T ranges :: iota_view random_access_range sized_range ranges :: iota_view ( * ranges :: begin ( E ), * ( ranges :: begin ( E ) + std :: min < D > ( ranges :: distance ( E ), F ))) E 
 
- 
       
- 
     Otherwise, if T ranges :: repeat_view - 
       if T sized_range views :: repeat ( * E . value_ , std :: min < D > ( ranges :: distance ( E ), F )) E 
- 
       otherwise, views :: repeat ( * E . value_ , static_cast < D > ( F )) 
 
- 
       
- 
     Otherwise, ranges :: lazy_take_view ( E , F ) 
[Example 1:
— end example]vector < int > is { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }; for ( int i : is | views :: lazy_take ( 5 )) cout << i << ' ' ; // prints 0 1 2 3 4 
7.4.2. x.2 Class template lazy_take_view 
namespace std :: ranges { template < view V > class lazy_take_view : public view_interface < lazy_take_view < V >> { private : V base_ = V (); // exposition only range_difference_t < V > count_ = 0 ; // exposition only // [range.lazy.take.sentinel], class template lazy_take_view::sentinel template < bool > class sentinel ; // exposition only public : lazy_take_view () requires default_initializable < V > = default ; constexpr lazy_take_view ( V base , range_difference_t < V > count ); constexpr V base () const & requires copy_constructible < V > { return base_ ; } constexpr V base () && { return std :: move ( base_ ); } constexpr auto begin () requires ( ! simple - view < V > ) { if constexpr ( sized_range < V > ) { if constexpr ( random_access_range < V > ) { return ranges :: begin ( base_ ); } else { auto sz = range_difference_t < V > ( size ()); return lazy_counted_iterator ( ranges :: begin ( base_ ), sz ); } } else if constexpr ( sized_sentinel_for < sentinel_t < V > , iterator_t < V >> ) { auto it = ranges :: begin ( base_ ); auto sz = std :: min ( count_ , ranges :: end ( base_ ) - it ); return lazy_counted_iterator ( std :: move ( it ), sz ); } else { return lazy_counted_iterator ( ranges :: begin ( base_ ), count_ ); } } constexpr auto begin () const requires range < const V > { if constexpr ( sized_range < const V > ) { if constexpr ( random_access_range < const V > ) { return ranges :: begin ( base_ ); } else { auto sz = range_difference_t < const V > ( size ()); return lazy_counted_iterator ( ranges :: begin ( base_ ), sz ); } } else if constexpr ( sized_sentinel_for < sentinel_t < const V > , iterator_t < const V >> ) { auto it = ranges :: begin ( base_ ); auto sz = std :: min ( count_ , ranges :: end ( base_ ) - it ); return lazy_counted_iterator ( std :: move ( it ), sz ); } else { return lazy_counted_iterator ( ranges :: begin ( base_ ), count_ ); } } constexpr auto end () requires ( ! simple - view < V > ) { if constexpr ( sized_range < V > ) { if constexpr ( random_access_range < V > ) return ranges :: begin ( base_ ) + range_difference_t < V > ( size ()); else return default_sentinel ; } else if constexpr ( sized_sentinel_for < sentinel_t < V > , iterator_t < V >> ) { return default_sentinel ; } else { return sentinel < false> { ranges :: end ( base_ )}; } } constexpr auto end () const requires range < const V > { if constexpr ( sized_range < const V > ) { if constexpr ( random_access_range < const V > ) return ranges :: begin ( base_ ) + range_difference_t < const V > ( size ()); else return default_sentinel ; } else if constexpr ( sized_sentinel_for < sentinel_t < const V > , iterator_t < const V >> ) { return default_sentinel ; } else { return sentinel < true> { ranges :: end ( base_ )}; } } constexpr auto size () requires sized_range < V > { auto n = ranges :: size ( base_ ); return ranges :: min ( n , static_cast < decltype ( n ) > ( count_ )); } constexpr auto size () const requires sized_range < const V > { auto n = ranges :: size ( base_ ); return ranges :: min ( n , static_cast < decltype ( n ) > ( count_ )); } }; template < class R > lazy_take_view ( R && , range_difference_t < R > ) -> lazy_take_view < views :: all_t < R >> ; } 
Preconditions: true.
Effects: Initializes 
7.4.3. x.3 Class template lazy_take_view :: sentinel 
namespace std :: ranges { template < view V > template < bool Const > class lazy_take_view < V >:: sentinel { private : using Base = maybe - const < Const , V > ; // exposition only template < bool OtherConst > using CI = lazy_counted_iterator < iterator_t < maybe - const < OtherConst , V >>> ; // exposition only sentinel_t < Base > end_ = sentinel_t < Base > (); // exposition only public : sentinel () = default ; constexpr explicit sentinel ( sentinel_t < Base > end ); constexpr sentinel ( sentinel <! Const > s ) requires Const && convertible_to < sentinel_t < V > , sentinel_t < Base >> ; constexpr sentinel_t < Base > base () const ; friend constexpr bool operator == ( const CI < Const >& y , const sentinel & x ); template < bool OtherConst = ! Const > requires sentinel_for < sentinel_t < Base > , iterator_t < maybe - const < OtherConst , V >>> friend constexpr bool operator == ( const CI < OtherConst >& y , const sentinel & x ); }; } 
Effects: Initializes 
constexpr sentinel ( sentinel <! Const > s ) requires Const && convertible_to < sentinel_t < V > , sentinel_t < Base >> ; 
Effects: Initializes 
Effects: Equivalent to: 
friend constexpr bool operator == ( const CI < Const >& y , const sentinel & x ); template < bool OtherConst = ! Const > requires sentinel_for < sentinel_t < Base > , iterator_t < maybe - const < OtherConst , V >>> friend constexpr bool operator == ( const CI < OtherConst >& y , const sentinel & x ); 
Effects: Equivalent to: 
8. Opens
- 
     basic_const_iterator iterator_category forward_iterator move_iterator input_iterator move_iterator basic_const_iterator 
- 
     views :: lazy_counted lazy_take count random_access_iterator lazy_counted_iterator span - 
       These types don’t promise to never increment the underlying iterator for such cases, they only promise it implicitly for the cases that use lazy_counted_iterator 
- 
       We don’t think that difference is directly observable (especially as lazy_counted_iterator base () 
- 
       We could’ve define these types to always use lazy_counted_iterator lazy_take 
 
- 
       
- 
     There was a suggestion to combine operator * const 
9. Implementation experience
The current wording has been implemented over Microsoft STL [MSFT-STL].
There is also a partial implementation, that is based as much as possible directly on the wording of this paper [YB-IMPL].
10. Note about optimization
It’s interesting to note that with any level of optimization enabled (including 
11. Acknowledgements
Many thanks to the Israeli NB members for their feedback and support, in particular Inbal Levi, Dvir Yitzchaki, Dan Raviv and Andrei Zissu. Thanks r/cpp Reddit users for their feedback on P2406R0 [reddit-cpp]. Thanks SG9 members for their feedback and guidance.