On Thu, Aug 31, 2017 at 05:37:20PM -0600, Jonathan M Davis via Digitalmars-d-learn wrote: > On Thursday, August 31, 2017 14:09:55 H. S. Teoh via Digitalmars-d-learn [...] > I know. We've had this argument before.
I know. Let's not rehash that. :-) [...] > Even copying ranges in generic code does not have defined behavior, > because different types have different semantics even if they follow > the range API and pass isInputRange, isForwardRange, etc. This is actually one of the prime reasons for my stance that we ought to always use .save instead of assigning .front to a local variable. Consider the case where .front returns a subrange. As you state above, copying this subrange does not have defined behaviour. One reason is the difference in semantics between reference types and value types: the assignment operator `=` means different things for each kind of type. `x=y;` for a value type makes a new copy of the value in x, but `x=y;` for a reference type only copies the reference, not the value. So how do you ensure that after assigning .front to a local variable, it will continue to be valid after .popFront is called on the outer range? You can't. If you simply assign it, there's no guarantee it isn't just copying a reference to a buffer reused by .popFront. But if you try to copy it, the result is not defined, as you said. Worse yet, the user can overload opAssign() to do something with side-effects. So this code: auto e = r.front; r.popFront(); userCallback(e); may potentially have a different result from: userCallback(r.front); r.popFront(); The only safe approach is to make as few assumptions as possible, i.e., don't assume that `=` will produce the right result, so avoid saving anything in local variables completely and always use .save instead if you need to refer to a previous value of .front after calling .popFront. Yes this greatly complicates generic code, and I wouldn't impose it on user code. But one would expect that Phobos, at the very least, ought to be of a high enough standard to be able to handle such things correctly. Taking a step back from these nitpicky details, though: this seems to be symptomic of the underlying difficulty of nailing exact range semantics in an imperative language. In a pure functional language without mutation, you wouldn't have such issues, so there you could compose arbitrarily complex ranges of arbitrarily complex behaviours with impunity and not have to worry that something might break if one of the subranges was transient. We can't kill mutation in D, though, so unfortunately we have to live with these complications. T -- Life begins when you can spend your spare time programming instead of watching television. -- Cal Keegan