1. Revision History
r1: Improving many parts, following feedback from Inbal Levi and from Reddit users
r0: initial revision
2. Intro
Project Euler is a project with many mathematical-related questions that are intended to encourage the reader to write a small program to compute the result. In this case, one of the problems there, no. 37 [EULER], helped reveal a pitfall coming from the definition ofstd :: counted_iterator 3. Problem description
3.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 
The problem mentioned in the intro is one that actually describes a filter that
return exactly 11 elements, so trying to use 
It means 
3.2. input_iterator 
    Even more common problem is when using input ranges, e.g. basic_istream_view #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 means that one can’t use ranges when parsing input, for example.
Seems like this was discussed in [range-v3-issue57], and there was no decision what is the right solution.
4. 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 
5. Desired behavior
As long as 
6. Naive design of the solution
Basically, what is required to implement the desired behavior is changing the
behavior of 
7. Designs that were considered (and rejected)
7.1. Don’t increment the underlying iterator until really required
We could change 
7.2. Fix counted_iterator 
   Instead of introducing new type, 
Please note that 
The options we considered and rejected are:
Option 1: Require that if 
This option assumes the only reason to create such an 
Option 2: Require that if 
Option 3: Mark this case internally (e.g. with 
Another problem is that keeping the current behavior of 
Our conclusion is that 
8. P2578 and this paper
We propose a fix constructed from 2 parts:
- 
     Block the obviously wrong usages from counted_iterator 
- 
     Create new tools that behave better in these cases 
To make it easier to discuss and adopt each part by itself, if needed, we
separated them to 2 papers. [P2578] introduces 
9. Design of the proposed solution
9.1. The main changes
We propose adding a new iterator type, 
Additionally, this requires adding 
9.2. 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 
9.3. random_access_iterator 
   To reduce the amount of changes required, we keep the current behavior for 
9.4. Discussing base () 
   To keep 
As mentioned, for 
To rectify this, tools built on 
9.5. Constructor with count
Following the discussion above about the c-tor that takes count, we disallow
creating 
10. Proposed Wording
Duplicate "Counted iterators" section (25.5.6 [iterators.counted]), renaming it to "Lazy counted iterators" (adding 25.5.x [iterators.lazy.counted]), with the following differences (the wording here is done against the suggested changes in P2578R0):
Every occurence of 
Every stable reference is prefixed with "lazy."
Under 25.5.x.1 [lazy.counted.iterator]:
 
 
Under 25.5.x.2 [lazy.counted.iter.const]:
 Preconditions: 
Under 25.5.x.3 [lazy.counted.iter.access]:
 Effects: Equivalent to: 
constexpr  I  base ()  const  & ; requires  random_ access_ iterator < I > ; Effects: Equivalent to:
return  length  ?  current  :  std :: ranges :: prev ( current ); 
 Returns: 
constexpr  I  base ()  && ; requires  random_ access_ iterator < I > ; Returns:
length  ?  std :: move ( current )  :  std :: ranges :: prev ( current ) Under 23.5.6.5 [counted.iter.nav]:
constexpr  lazy_counted_iterator &  operator ++ (); Preconditions:
length  >  0 Effects: Equivalent to:
if  ( length  >  1 )  ++ current ; -- length ; return  * this ; 
 
 Preconditions: 
 Effects: Equivalent to:
 
 
 
 Preconditions: 
 Effects: Equivalent to:
 
 
 
 
 
 Effects: Equivalent to:
 
 
 
constexpr  lazy_counted_iterator &  operator -- () requires  bidirectional_ iterator < I > ; Effects: Equivalent to:
if  ( length )  -- current ; ++ length ; return  * this ; 
 
 Effects: Equivalent to:
 
 
 
11. TODO
Missing wording for 
12. Note about optimization
It’s interesting to note that with any level of optimization enabled (including- Og input_iterator 13. Acknowledgements
Many thanks to the Israeli NB members for their feedback and support, in particular Inbal Levi, Dvir Yitzchaki and Dan Raviv. Thanks r/cpp Reddit users for their feedback on P2406R0.