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