Constness and delegates

Mafi mafi at example.org
Fri Jan 10 13:54:00 UTC 2020


On Friday, 10 January 2020 at 02:50:16 UTC, Timon Gehr wrote:
> On 10.01.20 00:11, Mafi wrote:
>> 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;
>> ...
>
> No, this is not exactly the right way to think about this. A 
> delegate is not an interface, because it *includes* an _opaque_ 
> context pointer. The qualifier on a delegate qualifies _both_ 
> the function and the context pointer and the reason why 
> qualifiers can be dropped anyway is that `qualified(void*)` can 
> implicitly convert to `void*` (even though DMD does not know it 
> yet) and the delegate maintains an invariant that says that the 
> function pointer and the context pointer are compatible.

I see. So the additional opacity is that an interface type I, 
that is qualified mutable, could be upcast to some class C and 
you would expect C to mutable as well. Therefore you may not 
convert an interface consisting of only const methods from const 
to mutable. But a delegate is different, it can never be 
inspected. Correct? That's interesting!

> ...
>> 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.
>> ...
>
> Yes, exactly.
>
> ...
>
> Yes. And it's not only `const`. You can lose *all* qualifiers.
>

So the qualifier on the right of the delegate is not actually 
contravariant in the type-way but rather the inside-out direction 
of transitive constness (mutable data can reference const data 
can reference immutable data). Thus delegate() immutable -> 
delegate() const -> delegate(). This is also a nice symmetry 
between constness and other qualifiers.

Additionally because of the simple opaque nature of delegates 
'qualifierA R delegate(P) qualifer1' should be implicitely 
convertible to 'qualifierB R delegate(P) qualifier1' as long as 
qualifierA and qualifierB are "weaker" than qualifier1. That is 
mutable/const/immtutable R delegate() immutable all convert to 
one another. And mutable/const R delegate(P) const convert 
between each other.

Which I think gives this graph (qualifier1/2 => qualifier1 R 
delegate(P) qualifier2:

m/m <- m/c <- m/i
  |      ^      ^
  v      v      v
c/m <- c/c <- c/i
  ^      ^      ^
  |      |      v
i/m <- i/c <- i/i

Where a delegate is callable iff 'qualifier1 qualifier2' is 
convertible to 'qualifier2'. Therefore only i/m and c/m are not 
callable (and they don't convert to a callable one). Is this 
correct?




More information about the Digitalmars-d mailing list