Range.init does not imply Range.empty==true
H. S. Teoh
hsteoh at quickfur.ath.cx
Thu Dec 20 14:35:15 PST 2012
On Thu, Dec 20, 2012 at 11:04:05PM +0100, Jonathan M Davis wrote:
> On Thursday, December 20, 2012 13:08:46 H. S. Teoh wrote:
> > > However, any range which is a struct whose init value _isn't_ empty is
> > > wrong. We should be be able to rely on that at least.
> >
> > [...]
> >
> > No we can't. For example:
> >
> > auto someRangeFunction(RoR)(RoR ror) {
> > struct Helper {
> > RoR _ror;
> > ElementType!R _current;
> >
> > this(RoR r) {
> > _ror = r;
> > assert(_current.empty);
> > }
> > }
> > return Helper(ror);
> > }
> >
> > If RoR is an array of class objects, the assert line will segfault
> > because _current.init is null. Conceptually speaking, I agree that a
> > null object is an empty range, but in code, .empty is undefined
> > because you can't invoke a method through a null reference.
>
> If _current is an array, then it won't segfault. A null array returns true for
> empty. Only classes have problems with null.
Right.
> But regardless, my point is that Range.init.empty must be true _for
> structs_. A struct range should be written such that its init value
> is empty. That's not true for classes, because they'll be null.
>
> Your assertion should pass as long as the type of _current is a
> struct.
The problem with generic code is that you can't assume this. The type
could be literally _anything_ (as long as it conforms to the range API).
Unless you protect it with static ifs or something similar, of course.
> If you want to get an empty range from an existing range, then use
> takeNone. If it returns the same type (which it tries to do but can't
> always do - e.g. with non-sliceable ranges which are classes or
> non-sliceable Voldemort types), then you can reassign it to the
> original. If it doesn't, then there's no generic way to get an empty
> range of the same type from that range other than calling popFront
> until it's empty (which is obviously ineffecient in the finite case
> and impossible in the infinite case).
[...]
Which is why I'm leaning towards rewriting algorithms so that they don't
rely on ranges being emptiable. (Like you said, it's easy to have a
range class that's infinite, in which case there is no empty state for
ranges of that type at all -- .empty is an enum set to false.) I find
that the code often becomes cleaner when written in a way that minimizes
extraneous assumptions. It's harder to write, for sure, but I think it's
more rewarding in the end.
Anyway, coming back to takeNone: in many cases takeNone won't return the
exact same type as the original range, so code like this won't work
anymore:
auto myRangeFunc(R)(R range) {
struct Wrapper {
R _src;
R _helper;
this(R src) {
_src = src;
...
// Works:
_helper = _src;
...
// Won't work: R != typeof(takeNone!R)
// in general.
_helper = takeNone(src);
}
}
}
To allow both _helper=_src and _helper=takeNone(src) to work, one would
have to make _helper a wrapper type that can be assigned both R and
typeof(takeNone!R). Is this possible with the current implementation of
takeNone? I glanced at the code briefly but couldn't tell for sure.
T
--
Marketing: the art of convincing people to pay for what they didn't need
before which you can't deliver after.
More information about the Digitalmars-d
mailing list