ericniebler/range-v3

generate(foo) | take(n) calls foo n+1 times

RobertEBrown opened this issue · 3 comments

I noticed that generate_n(foo, n) calls foo n times while generate(foo) | take(n) calls it n+1 times. Is this by design? If so, what is the rationale?

In some situations I would like to guarantee that foo is called exactly n times, but it seems that I can't always use generate_n. For example, say that I want to do something like this:

for_each(generate(foo), [](auto x) { return yield_if(bar(x), x); }) | take(n);

In this case, foo might be called more than n times depending on how often bar(x) returns false, but I would like to ensure that it is called exactly as many times as it takes to generate n elements and no more. I don't see how I could do it with generate_n. I guess I would need something like for_each_n, which (as far as I can see) only exists as an algorithm and not as a view.

The root cause of these generate issues is that generate_view updates its cached value on construction and iterator increment, instead of invalidating on increment and updating on dereference. We need to evaluate the perf impact of the latter semantic, and give a think to changing generate_view and/or providing a different view with that semantic.

And we should try to be consistent with other views that do similar things, like view::istream.

I also hope that generate(f) | take(n) invokes f n times, and actually I once played with a modified version of generate/istream that implements this behavior

invalidating on increment and updating on dereference

P.S.
IIRC, the rationale for range-v3's behavior is this (p0738)

istream_iterator<int> i1(cin);
auto i2 = i1;
assert(*i1 == *i2);

We claim that this program does not assert.