DIP 1018--The Copy Constructor--Formal Review

H. S. Teoh hsteoh at quickfur.ath.cx
Mon Feb 25 16:22:33 UTC 2019


On Sun, Feb 24, 2019 at 08:59:49PM -0800, Walter Bright via Digitalmars-d-announce wrote:
[...]
> An interesting manifestation of this uselessness in C++ is the notion
> of "logical const", where a supposedly "const" value is lazily set to
> a value upon first use. I.e. it isn't const, it's just pretend const.

I disagree.  Logical const means the outside world can't tell that the
object has changed, because only a constant value is ever seen from
outside.  This is the basis of lazy initialization (which is part of the
concept of lazy evaluation), an important feature of FP style code, and
something that D does not support.

D's relaxed version of purity -- a function is pure if the outside world
can't see any impure semantics -- makes its scope much more widely
applicable than a strict interpretation of purity as in a functional
language.  Logical const is the same idea in the realm of mutability --
though I don't necessarily agree with C++'s anemic implementation of it.
What D could really benefit from was a statically-verifiable way of
lazily initializing something that is const to the outside world.

A derived problem with D's const is the inability to express a cached
const object.  You can't cache the object because it's const, which
greatly limits the scope of usability of const in large data structures.

The same limitation makes ref-counting such a huge challenge to
implement in D.  There is simply no way to associate a refcount with a
const object without jumping through hoops and/or treading into UB
territory by casting away const. There is no way to express that the
refcount is mutable but the rest of the object isn't. Well, you *can*
express this if you use circumlocutions like:

	struct RefCounted(T) {
		int refcount;
		const(T) payload;
	}

but that's worthless in generic code because const(RefCounted!T) !=
RefCounted!(const T). So you have to special-case every generic function
that needs to work with this type, and the special cases percolate
through the entire codebase, uglifying the code and forcing generic
functions that shouldn't need to know about RefCounted to have to know
about it so that they can work with it.


Because of these limitations, const is really only useful in low-level
modules of limited scope, in simple, self-contained data structures.
Higher-level, larger data structures are basically unusable with D's
const because lazy initialization and caching are not possible without
treading into UB territory by casting.  I'm not going to argue that
C++'s version of const is any better -- because non-enforceable const is
worthless, like you said -- but let's not kid ourselves that D's const
is that much better.  D's const is really only usable in very limited
situations, and there are many things for which it's unusable even
though logically it *could* have been applicable.


T

-- 
Two wrongs don't make a right; but three rights do make a left...


More information about the Digitalmars-d-announce mailing list