isInputRange is false for non-copyable types

Shachar Shemesh shachar at weka.io
Sun Apr 15 12:32:37 UTC 2018


On 15/04/18 09:39, Jonathan M Davis wrote:
> 
> It's extremely common for range-based functions to copy front. Even foreach
> does it. e.g.
> 

Not necessarily:

foreach(ref e; [S(0), S(1), S(2)]){}

While that would work with foreach, it will not work with anything that 
expects an inputRange (and since D defines a forward range as an 
extension, this is also not a forward range).

> If non-copyable types were allowed, then no function which accepted a range
> would be allowed to copy front without first checking that front was
> copyable (something that most code simply isn't going to do)

No, that's not correct.

If non-copyable types were allowed, then functions that accept a range 
would fail to compile on non-copyable types if they do a copy.

But the flip side is that if non-copyable would be allowed, a lot of 
functions that current copy would not. There are far too many 
unnecessary copies in the code that serve no purpose, but they are 
invisible, so they are not fixed.

> 
> Remember also that a large percentage of ranges that don't wrap dynamic
> arrays put their elements on the stack, which means that whenever the range
> is copied, the elements are copied.

If I am guaranteed to be able to copy an input range, what's the 
difference between it and a forward range?

Strike that, I have a better one: If I am allowed to copy an input 
range, what does the "save" function in forward range even mean?


> I'm not sure why it ends up printing each of them 3 times rather than 2,

Because some code somewhere does an unnecessary copy?

> the fact that foreach copies any range that it's given means that in order
> to have ranges allow non-copyable types, we'd have to change how foreach
> worked, which would break a lot of code. Right now,

Like I said above, foreach(ref) works with "input ranges" that define 
ref return from front to uncopyable objects. Foreach without ref 
doesn't, but that's mandatory from the interface: You cannot iterate 
copies of a range returning uncopyable objects.

As for ranges that store the values inside them: you REALLY don't want 
to copy those around. They may get quite big (not to mention that I 
don't see how you can define one over a non-copyable type).

> 
> foreach(e; range)
> {
> }
> 
> is lowered to something like
> 
> for(auto __range = range; !__range.empty; __range.popFront())
> {
>      auto e = __range.front;
> }

And that indeed generates a lot of confusion regarding what running two 
foreaches in a row does. This is a bug that already affects more than 
just uncopyable objects.

> 
> That requires both copying the range and copying front. We could
> theoretically change it so that everywhere that e was used, it would be
> replaced with __range.front

Like I said, already solved for foreach(ref)

> Now, generic range-based code really shouldn't ever use a range after it's
> been copied, since whether mutating the copy affects the original depends on
> the implementation of the range (and thus generic code should assume that
> foreach may have consumed the range and call save if it doesn't want that to
> happen), but lots of code does it anyway,

And allowing ranges with uncopyable members would turn this potential 
deadly run-time bugs into easy to find compile time errors. Sound like a 
great reason to allow it, to me.

> and if the code isn't generic, it
> can work just fine, since then the code can depend on the semantics of that
> specific range type

Such as it being copyable? Non-generic code is of no relevance to this 
discussion, as far as I can tell.

> Also, in order for a range to work with a non-copyable type, either front
> would have to return by ref or it would have to construct a new object to
> return so that it could be moved rather than copied.

Correct

> I expect that quite a
> few ranges would have problems with such a restriction,

Then those ranges will not work with non-copyable objects. That's no 
reason to make it impossible for *any* range to work with non-copyable.

> In addition, it's quite possible that forcing functions to not copy front
> would hurt performance.

I'm going to stop responding now, because, frankly, I think you and I 
are having very different discussions.

You seem to be advocating against making *all* ranges support 
non-copyable types, while I'm merely asking that *any* range be allowed 
to support such types.

Current situation is that a range with uncopyable types is not 
considered an input range, and cannot use any phobos range functions, 
including some that it should be able to use without a problem.

There is no reason to block such ranges from using "map" or "any", 
except the fact they require that the type answer "isInputRange" with true.

> IIRC, when this has come up previously, Andrei ruled that supporting
> non-copyable types simply wasn't worth the extra complication, though a
> quick search doesn't turn up anything where he talked about it. So, I can't
> verify that at the moment.

I will trust Andrei to weigh in on this point if he thinks he should. 
Until he does, please feel free to only bring up points you are willing 
to argue yourself.

Shachar


More information about the Digitalmars-d mailing list