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

H. S. Teoh hsteoh at quickfur.ath.cx
Thu Feb 1 18:58:15 UTC 2018


On Thu, Feb 01, 2018 at 07:52:32AM +0000, Simen Kjærås via Digitalmars-d-learn wrote:
> On Wednesday, 31 January 2018 at 01:45:57 UTC, H. S. Teoh wrote:
> > 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.
> 
> Going from RefCounted!(immutable(T)) to RefCounted!(const(T)) is nice,
> but I'd like to see a conversion from, const(RefCounted!T) to
> RefCounted!(const(T)). While this cannot be done without casts, the
> logic can be put inside .headMutable(), and include relevant checks.
> This will make it much safer than having the programmer cast manually.
[...]

Hmm. I experimented with this for a bit, and found that if we limit
ourselves to head-mutable, then these casts are inescapable.

The problem is that if we're handed a const object like
const(RefCounted!T), then there's no way we can back out to
RefCounted!(const(T)) without casting, because the latter contains a
mutable refcount whereas the former, due to const transitivity, must be
entirely unmodifiable.  If the refcount were stored in the RefCount
struct itself, then we could get away with a by-value copy into
RefCounted!(const(T)), but unfortunately we can't do that without
breaking the refcounting semantics; the refcount must be on the payload
itself, and RefCounted is basically just a smart pointer. So
const(RefCounted!T), by const transitivity, can only contain a const
pointer to the payload, so we're forced to use a cast to get a
RefCounted!(const(T)) out of it.

And on that note, this casting is NOT safe; for example, if you start
with an immutable(RefCounted!T) and implicitly convert it to
const(RefCounted!T), then if you cast the latter to
RefCounted!(const(T)), you're now violating immutable.  And there's no
way you can tell from inside .headMutable whether it's safe to cast,
because by the time it gets to .headMutable, the original immutable type
is already "forgotten".

However, if we go back to the idea of tail-const, we could potentially
eliminate the need for casts and also avoid breaking immutable.
Basically, the problem with writing const(RefCounted!T) is that it's a
one-way street: on the scale of increasing restrictiveness, we have the
series:

	RefCounted!T < RefCounted!(const(T)) < const(RefCounted!T)

Once you've gone all the way down to const(RefCounted!T), you can no
longer safely back out to RefCounted!(const(T)).

So that means we want to avoid const(RefCounted!T) completely. Instead,
if we standardize on a way to produce a RefCounted!(const(T)) from a
RefCounted!T, then we can stop halfway down the one-way street and never
need to back up.  Let's say we standardize on an operation .tailConst
that does these conversions:

	Container!T.tailConst		--> Container!(const(T))
	Container!(const(T)).tailConst	--> Container!(const(T))
	const(Container!T).tailConst	--> const(Container!T)

	// This is allowed because it's possible to implicitly convert
	// immutable(T) to const(T) internally:
	Container!(immutable(T)).tailConst --> Container!(const(T))

	immutable(Container!T).tailConst --> const(Container!T)

where Container can be any template that might want to support
tail-const semantics.

Essentially, .tailConst becomes the mid-way stand-in for the language's
built-in implicit conversions from mutable/immutable to const. So
instead of passing around const(Container!T), we'd construct a
Container!(const(T)) by calling .tailConst on the original container,
and pass that around instead.

We can also generalize this via UFCS to built-in reference types:

	tailConst(T*)		--> const(T)*
	tailConst((const(T))*)	--> const(T)*
	tailConst(const(T*))	--> const(T*)

	tailConst(immutable(T)*) --> const(T)*
	tailConst(immutable(T*)) --> const(T*)

Then .tailConst becomes a standard way of constructing a tail-const type
in the language.  No explicit language support is needed.


T

-- 
Claiming that your operating system is the best in the world because more people use it is like saying McDonalds makes the best food in the world. -- Carl B. Constantine


More information about the Digitalmars-d-learn mailing list