ericniebler / range-v3

Range library for C++14/17/20, basis for C++20's std::ranges
Other
4.08k stars 440 forks source link

How to differentiate two types of SinglePass ranges #258

Closed gnzlbg closed 8 years ago

gnzlbg commented 8 years ago

Some SinglePass ranges that can be copied always produce the same sequence when the copies are iterated upon.

Is there a way to differentiate these types of SinglePass ranges from those that can produce different results on each iteration? E.g. is there a guarantee that Copyable + SinglePass implies same results on each iteration?

I'm asking because I have a multi pass algorithm that does also work with some SinglePass ranges (and I need it to work with these) if I make a copy of the range before each usage. So ForwardRange would over constrain my algorithm, but InputRange is not strong enough. Still I ended up using InputRange + the following utility function to copy the range before using it, which kind of feels like a hack:

template <typename Rng, CONCEPT_REQUIRES_(InputRange<Rng>{} and !ForwardRange<Rng>{})>
auto use_copy_if_single_pass(Rng&& rng) {
  return uncvref_t<Rng>(std::forward<Rng>(rng));
}

template <typename Rng, CONCEPT_REQUIRES_(ForwardRange<Rng>{})>
auto use_copy_if_single_pass(Rng&& rng) -> Rng&& {
  return std::forward<Rng>(rng);
}
CaseyCarter commented 8 years ago

The semantics of ranges generally follow from the semantics of iterator / sentinel pairs, i.e., tagged_pair<begin(I), end(S)> is intended to satisfy View when I and S satisfy Sentinel (IteratorRange in range-v3 terminology). That tagged_pair is Copyable, so requiring Copyable + SinglePass to imply the same results on each iteration would effectively require all iterators to be multi-pass. There obviously are single-pass ranges that meet your requirement, but it's not something that can be required of all single-pass ranges.

gnzlbg commented 8 years ago

That tagged_pair is Copyable, so requiring Copyable + SinglePass to imply the same results on each iteration would effectively require all iterators to be multi-pass. There obviously are single-pass ranges that meet your requirement, but it's not something that can be required of all single-pass ranges.

Good example, constraining on Copyable doesn't make any sense then.

ericniebler commented 8 years ago

I'm not convinced this distinction needs to be surfaced in the range hierarchy. Can you give some concrete examples where this would make a difference?

ericniebler commented 8 years ago

Closing.

gnzlbg commented 8 years ago

I thought about this some more and looked at my algorithms. Basically they needed to estimate the distance of a range before using the range (e.g. for building an unstructured mesh from an octree). They require ForwardRange for this, but I am using them with a range composition that uses view::join. The result is single pass but is cheap to copy, so I "worked around this" by lowering the requirement to InputRange and just copy the range before each usage if the range is SinglePass.

This felt wrong, and I'm inclined to think "don't do that". One should not work around SinglePass ranges like this. Still, some useful views are SinglePass because they need to maintain some state, but they remain cheap to copy (e.g. a couple of pointers) and can in practice be iterated multiple times.

At the end I just changed the API of these algorithms so that the caller needs to pass the distance to them. But a different solution could have been to require InputRange and Sized, such that the caller needs to make the range Sized. I guess this is what view::take_exactly is there for.