Thread overview
[Understanding] Classes and delegate inheritance vs function pointers
Jan 09, 2021
Q. Schroll
Jan 09, 2021
Jacob Carlborg
Jan 09, 2021
Q. Schroll
Jan 09, 2021
sighoya
Jan 10, 2021
Q. Schroll
Jan 10, 2021
sighoya
Jan 10, 2021
sighoya
January 09, 2021
Say I have a class hierarchy like this:
  class Base { }
  class Derived : Base { }
A Derived object cannot be referenced as a Base object, but as a const(Base) object. That makes sense to me.

One can replace Base by a @system delegate type (SysDG) and Derived by a @safe delegate type (SafeDG) and it works the same way: a SafeDG object cannot be referenced as a SysDG object, but as a const(SysDG) object.

However, if I try that with function pointers instead of delegates (SysFP, SafeFP), for some reason, a SafeFP cannot be referenced as a const(SysFP).
This makes no sense in my head. Is there some reason I'm unable to see?

Example code is here: https://run.dlang.io/is/zSNArx
January 09, 2021
On 2021-01-09 19:16, Q. Schroll wrote:
> Say I have a class hierarchy like this:
>    class Base { }
>    class Derived : Base { }
> A Derived object cannot be referenced as a Base object, but as a const(Base) object. That makes sense to me.

It can:

Base b = new Derived();

> One can replace Base by a @system delegate type (SysDG) and Derived by a @safe delegate type (SafeDG) and it works the same way: a SafeDG object cannot be referenced as a SysDG object, but as a const(SysDG) object.
> 
> However, if I try that with function pointers instead of delegates (SysFP, SafeFP), for some reason, a SafeFP cannot be referenced as a const(SysFP).
> This makes no sense in my head. Is there some reason I'm unable to see?
> 
> Example code is here: https://run.dlang.io/is/zSNArx

Is there a reason all you're examples are using pointers? Classes are already reference types, delegates are kind of like reference types. They consist of a context pointer and a function pointer. Function pointer are, as the name suggest, already pointers.

-- 
/Jacob Carlborg
January 09, 2021
On Saturday, 9 January 2021 at 20:00:35 UTC, Jacob Carlborg wrote:
> On 2021-01-09 19:16, Q. Schroll wrote:
>> Say I have a class hierarchy like this:
>>    class Base { }
>>    class Derived : Base { }
>> A Derived object cannot be referenced as a Base object, but as a const(Base) object. That makes sense to me.
>
> It can:
>
> Base b = new Derived();

That's not what I mean. You copy the reference. That's not what referencing meant.

  Derived d = new Derived();
  Base* bp = &d; // fails
  const(Base) cbp = &d; // compiles.

> Is there a reason all you're examples are using pointers?

Yes. Actually, I need it for slices, but only pointer part of it really mattered.

A Derived[] is implicitly a const(Base)[], not a Base[].
A void delegate() @safe[] is implicitly a const(void delegate())[].
But it seems a void function() @safe[] **isn't** implicitly a const(void function())[].

Functions taking those are kind of useless like that.
January 09, 2021
On Saturday, 9 January 2021 at 20:20:38 UTC, Q. Schroll wrote:
> That's not what I mean. You copy the reference. That's not what referencing meant.
>
>   Derived d = new Derived();
>   Base* bp = &d; // fails
>   const(Base) cbp = &d; // compiles.

Generally, allowing covariant assignment for the Ptr<T>, i.e. T*, type only in case of const T* strikes me. IMHO, both should be synchronously (dis)allowed.
Because D doesn't support co(ntra)variance, it should be both disallowed?
Otherwise, because D already support the const case, why not the non const case?

Are there any alignment issues supporting the non const case?
January 10, 2021
On Saturday, 9 January 2021 at 21:57:43 UTC, sighoya wrote:
> On Saturday, 9 January 2021 at 20:20:38 UTC, Q. Schroll wrote:
>> That's not what I mean. You copy the reference. That's not what referencing meant.
>>
>>   Derived d = new Derived();
>>   Base* bp = &d; // fails
>>   const(Base) cbp = &d; // compiles.
>
> Generally, allowing covariant assignment for the Ptr<T>, i.e. T*, type only in case of const T* strikes me. IMHO, both should be synchronously (dis)allowed.

I'm unsure what you mean.

If you have an array `cars` of type Car[], you can treat it as a const(Vehicle)[] because reading a Car as a Vehicle is unproblematic. But if it could bind to Vehicle[], its elements could be written arbitrary Vehicles. Say it worked, then:

  Car[] cars = ...;
  Vehicle[] vehicles = cars;
  vehicles[0] = new Boat; // err...
  // cars[0] is a Boat now!?

Now what? Do what Java does and throw an Exception? Or use const to prevent mutation?

> Because D doesn't support co(ntra)variance, it should be both disallowed?

Imagine for a moment, `const` were named `read_only` and there were a specifier `write_only`. That looks silly, but makes sense in some cases:

  Vehicle[] vehicles = ...;
  write_only(Car)[] cars = vehicles; // WTF!?
  cars[i] = new Car; // wait, that's actually okay...

Because write_only isn't a thing in D, contravariance is an odd thing to get your a D mind around.

> Otherwise, because D already support the const case, why not the non const case?

The non-const case makes no sense.

> Are there any alignment issues supporting the non const case?

It's not alignment, probably. (Maybe it is, I know almost nothing about alignment, to be honest.) Among other things, in the non-const case, as you call it, the type system cannot guarantee anymore that stuff that is typed some way actually contains those objects.
It is one of the major issues Java has and why everyone and their mom tells you not to use Java's arrays and instead use List or similar well-behaved constructs.

BTW, my question wasn't about classes at all. I used them as an illustration what works and asked why (seemingly) the same thing doesn't with function pointers.
January 10, 2021
On Sunday, 10 January 2021 at 00:44:26 UTC, Q. Schroll wrote:

>
>   Car[] cars = ...;
>   Vehicle[] vehicles = cars;
>   vehicles[0] = new Boat; // err...
>   // cars[0] is a Boat now!?
>
> Now what? Do what Java does and throw an Exception? Or use const to prevent mutation?

Yes, you are right:

```
Derived derived;
Base* bp=&derived;
*bp=base;
derived.method() //ouch
```

> I'm unsure what you mean.

I mean D doesn't support co(ntra)varaince out of the box, but seems allowing them in certain cases increasing the degree of confusion just as it is the case here.
I think it would be just better to generally incorporate co(ntra)variance into D's semantic or completely disregard it.



January 10, 2021
On 1/9/21 3:20 PM, Q. Schroll wrote:
> Yes. Actually, I need it for slices, but only pointer part of it really mattered.
> 
> A Derived[] is implicitly a const(Base)[], not a Base[].
> A void delegate() @safe[] is implicitly a const(void delegate())[].
> But it seems a void function() @safe[] **isn't** implicitly a const(void function())[].
> 
> Functions taking those are kind of useless like that.

It should work. The reason const works is under the theory that you cannot change it, so there is no danger of replacing the value with something that isn't the original type.

I would say as long as it's covariant (or contravariant? I can't remember), implicit casting to a const array should work.

-Steve
January 10, 2021
On Saturday, 9 January 2021 at 18:16:04 UTC, Q. Schroll wrote:
> This makes no sense in my head. Is there some reason I'm unable to see?

And yes, I think it should work likewise for function pointers, too.