Why in Phobos is empty() sometimes const and sometimes not

Jonathan M Davis newsgroup.d at jmdavisprog.com
Mon Jul 29 19:38:34 UTC 2019


On Monday, July 29, 2019 11:32:58 AM MDT Matt via Digitalmars-d-learn wrote:
> I've noticed that for some ranges in Phobos empty is marked const
> (e.g. iota) but for other ranges (e.g. multiwayMerge) it is not
> const. Is there a reason why? Isn't empty guaranteed not to alter
> the data of the range and so should be const?
>
> This is causing me considerable headaches as I try to write my
> own ranges that accept other ranges and have it all work for the
> general case. Any advice would be welcome.

empty is most definitely _not_ guaranteed to not mutate the range. No, it
must not change whether the range is actually empty or not, consume
elements, etc., but stuff like caching or delayed calculation can affect
what empty does in ways that would require mutation. For instance, filter
avoids doing any real work in its constructor, but that means that in order
for empty to work if it's called before front or popFront (as would be
typical), it has to do the work to get the range to its starting point so
that it's known whether it's actually empty or not. So, filter's empty can't
be const.

In general, generic code can't assume that a range-based function is const,
because that varies wildly across ranges, because they're frequently
wrapping other ranges. Even if a range that's being wrapped could
theoretically be const, it's frequently the case that it isn't, because
there isn't much point.

Ranges are pretty much useless if they're const. The best that you'd be able
to do with any range API functions would be to call empty, front, back (if
it's a bidirectional range), opIndex (if it's a random-access range), and
maybe opSlice (if it has slicing). But pretty much the only range types that
will give you a tail-const slice if you slice them is dynamic arrays,
because the compiler understands them and knows that it's safe to const(T)[]
instead of const(T[]), whereas with templated types, not only is there no
way for it to know what the tail-const equivalent of const(R) or const(R!E)
would be, but with how templates work, there's no guarantee that a different
instantiation of the same template would be equivalent even if the only
difference is const. Stuff like static if could be used to make them
completely different. So, once you have a const range, your essentially
stuck and can't mutate it or get a tail-const copy to mutate.

Because const ranges are basically useless, there really isn't much point in
putting const on any range functions even if it would work for that
particular range, and if a range is a wrapper range, the only way that it
could do it would be if it used static if to make the code differ depending
on whether the range it's wrapping will work if that function is const,
which essentially means duplicating a bunch of code for little to no
benefit.

- Jonathan M Davis





More information about the Digitalmars-d-learn mailing list