Constness and delegates
Mafi
mafi at example.org
Thu Jan 9 23:11:48 UTC 2020
Regarding the work and comments on a pull request about delegate
constness (https://github.com/dlang/dmd/pull/10644) I think there
is some things we need to discuss.
I think the only sane way to analyze the soundness of delegates
is to equate:
R delegate(P) qualifier f;
with:
interface I { R f(P) qualifier; }
I f;
Therefore given some class:
class C { R f(P) const; }
C c;
The delegate &c.f is of type 'R f(P) const'! The const qualifier
does *not* apply to the this-Pointer of the delegate but to the
contract of the invocation. Therefore the qualifier should be
handled in a contravariant manner (and not covariant) because it
describes the implicit this-Parameter of the referenced method
and not the stored this-Pointer. The const-ness of the
this-Pointer is the one "outside" the delegate.
qualifer1( R delegate(P) qualifier2 ) f;
Is always a well-formed type / variable. But f can only be called
iff qualifier1 is implicitly convertible to qualifier2. This
solves the soundness of delegates in const classes and structs:
auto s = S(3);
struct S {
int x = 0;
void delegate() f;
this(int x) { this.x = x; this.f = &this.incX; }
void incX() { x++; }
void const_method() const { f(); } // HERE
}
The line marked HERE compiles currently but f (in this const
context) is of type 'const void delegate()' and therefore cannot
be invoked (because const does not convert to mutable). If you
change the type of f to "void delegate() const" it can be invoked
but "incX" cannot be assigned to it! Soundness recovered. Const
references cannot change any (implicit) state and immutable
objects cannot observably change at all.
So in general:
struct S { void f() qualifier2; }
qualifier1 S s;
auto f = &s.f;
Is of type qualifier1(void delegate() qualifier2). Note the
additional qualifier1 around the type. It (and not qualifier2)
makes sure we respect the constness of the instance.
So what about implicit conversions? Well as always T -> const(T)
<- immutable(T). Additionally qualifer(R delegate(P) const) ->
qualifier(R delegate(P)), that is, we drop the const! This is
because we loose power, the delegate cannot be invoked in const
contexts anymore. This makes simple 'R delegate(P)' the goto-type
for callbacks, as is probably the case anyways in most D code. Of
course the inverse cannot be allowed, otherwise we lose the
soundness again.
Additionally qualifier(R delegate(P) const) -> qualifier(R
delegate(P) immtuble). This way immutable R delegate(P) immutable
can be initialized from a delegate to const method on an
immutable object.
Inline delegates that want to be const (either explicitely or
maybe implicitely(?)) have to treat every referenced stack
variable as const, like going through a const this-Pointer, which
is actually what happens anyways.
I am not sure exactly how to treat inout. And I don't know in
what state shared is in general. So what do you think? Does this
sound reasonable? Please discuss.
More information about the Digitalmars-d
mailing list