Should the "front" range primitive be "const" ?

H. S. Teoh hsteoh at quickfur.ath.cx
Wed Jan 31 01:45:57 UTC 2018


On Tue, Jan 30, 2018 at 06:05:47PM -0700, Jonathan M Davis via Digitalmars-d-learn wrote:
> On Tuesday, January 30, 2018 07:49:28 H. S. Teoh via Digitalmars-d-learn 
> wrote:
[...]
> > Simen has had some ideas recently about "head mutable" aka
> > tail-const, which could be a first step towards making const ranges,
> > or rather tail-const ranges, actually usable:
> >
> >   https://forum.dlang.org/post/cpxfgdmklgusodqouqdr@forum.dlang.org
> >
> > tl;dr summary:
> >
> > (1) Ranges implement a standard method, tentatively called
> >     opHeadMutable, that returns a head-mutable version of
> >     themselves.  For example, const(MyRange!T).opHeadMutable would
> >     return MyRange!(const(T)).
> >
> > (2) Standard library functions would recognize opHeadMutable and use
> >     it where needed, e.g., when you hand them a const range.
> >
> > (3) Profit. :-P
> 
> I still need to look over what he's proposing in more detail - it's
> been proposed before (by Andrei IIRC) that one possible solution would
> be to add an operator for returning a tail-const version of a type,
> but no one has ever taken that idea anywhere.

Well, that's essentially what Simen has done, and he has code to show
for it.


> Personally, I'm getting to the point that I'd rather just avoid const
> than deal with any further complications for ranges. In principle, I
> like the idea of const, but in practice, it just constantly gets in
> the way, and I've rarely actually seen any benefit from it in either
> C++ or D. I can think of one time in my entire life where const has
> prevented a bug for me - which was when I got the arguments backwards
> to C++'s std::copy function.

I have been saved by const (in D) a few times when I by mistake tried
mutating something that shouldn't be mutated.  But yeah, more often than
not it just gets in the way.  However, my hope is that if Simen's
proposal gets somewhere, it will reduce the annoyance of const and
(hopefully) increase its benefits.

Note that while Simen's code example uses ranges, since that's a common
blocker for using const, it extends beyond that.  For example, consider
a const(RefCounted!Object).  Right now, this is unusable because you
cannot update the reference count of a const object without casting
const away and treading into UB territory.  But if
const(RefCounted!Object).opHeadConst returned a
RefCounted!(const(Object)) instead, then this could be made usable: the
payload can now become const while keeping the reference count mutable.

Of course, as currently designed, the API is kinda awkward. But that's
just a syntactic issue.  We could call it instead .headConst, and you'd
have:

	// mutable refcount, mutable payload
	RefCounted!Object

	// mutable refcount, const payload (useful)
	RefCounted!Object.headConst --> RefCounted!(const(Object))

	// const refcount, const payload (useless)
	const(RefCounted!Object)

Standardizing .headConst means that we now have a reliable way to
construct a RefCounted!(const(Object)) from a RefCounted!Object, whereas
currently we can only construct const(RefCounted!Object), which is
unusable.  In general, this lets us construct a Template!(const(T)) from
a Template!T without needing to know what Template is.

For example, Template could take multiple parameters, like
Template!(x,T), such that the correct head-const is actually
Template!(x, const(T)). Generic code can't know this, but if .headConst
is standardized, then it provides a way for generic code to create a
Template!(x, const(T)) from a Template(x,T) without needing special
knowledge of Template's implementation.

We could even put a generic .headConst in druntime that implements the
conversion for built-in types like int* -> const(int)*.  Then .headConst
becomes the standard idiom to go from any type T to a head-const version
of T.  Generic code that relies on .headConst would work for both
built-in types and custom user types without any change.

Best of all, this doesn't even require a language change, which is a big
plus.


> At least with immutable, you get implicit sharing and some
> optimization opportunities. In principle, const can get you some of
> the optimization opportunities but only in really restricted
> circumstances or circumstances where you could have used immutable and
> the code would have been the same (e.g. with int).
[...]

I haven't thought through it carefully, but if .headConst is a viable
solution to the head-const problem, then conceivably we could also
extend it to deal with immutable payloads too.  Then we could go from,
say, RefCounted!(immutable(T)) to RefCounted!(const(T)) generically,
without any casting or breaking the type system.  We could potentially
expand the scope of usefulness of immutable this way, if this approach
turns out to be workable.


T

-- 
If Java had true garbage collection, most programs would delete themselves upon execution. -- Robert Sewell


More information about the Digitalmars-d-learn mailing list