December 05
On Thursday, September 19, 2024 9:56:06 AM MST Richard Andrew Cattermole (Rikki) via dip.ideas wrote:
> As an idea this has come up in Razvan's DConf 2024 talk.
>
> 1. Support inheritance on struct, for other structs.
>
> ```d
> struct Parent {
>      ...
> }
>
> struct Child : Parent {
>      ...
> }
> ```
>
> 2. ``opDispatch`` function, may work in place of ``alias this`` when no parent exists.
>
> ```d
> struct Parent {
>      T thing;
>      ref T opDispatch(string:"")() {
>          return this.thing;
>      }
> }
>
> struct Child : Parent {
> }
>
> Child child;
> T got = child;
> ```

After that talk, I discussed struct inheritance briefly with Walter, and his idea was that if we did it, there would be _no_ conversions of any kind that came from it. It would purely be a way to have a struct inherit all of the fields and member functions from the "parent" struct and as such would essentially be the compiler copying and pasting that code into the struct doing the inheriting.

So, it would provide a way to inherit / copy the implementation, but the types would be completely divorced from one another in terms of how they were used.

Personally, I think that that's the right approach, since it's the issues around implicit conversions that are the core problem with alias this, and it avoids all issues with regards to slicing objects (which is the primary reason why classes in D are on the heap instead of on the stack like they can be in C++).

- Jonathan M Davis



December 06
On 06/12/2024 11:41 AM, Jonathan M Davis wrote:
> On Thursday, September 19, 2024 9:56:06 AM MST Richard Andrew Cattermole
> (Rikki) via dip.ideas wrote:
>> As an idea this has come up in Razvan's DConf 2024 talk.
>>
>> 1. Support inheritance on struct, for other structs.
>>
>> ```d
>> struct Parent {
>>       ...
>> }
>>
>> struct Child : Parent {
>>       ...
>> }
>> ```
>>
>> 2. ``opDispatch`` function, may work in place of ``alias this``
>> when no parent exists.
>>
>> ```d
>> struct Parent {
>>       T thing;
>>       ref T opDispatch(string:"")() {
>>           return this.thing;
>>       }
>> }
>>
>> struct Child : Parent {
>> }
>>
>> Child child;
>> T got = child;
>> ```
> 
> After that talk, I discussed struct inheritance briefly with Walter, and his
> idea was that if we did it, there would be _no_ conversions of any kind that
> came from it. It would purely be a way to have a struct inherit all of the
> fields and member functions from the "parent" struct and as such would
> essentially be the compiler copying and pasting that code into the struct
> doing the inheriting.
> 
> So, it would provide a way to inherit / copy the implementation, but the
> types would be completely divorced from one another in terms of how they
> were used.
> 
> Personally, I think that that's the right approach, since it's the issues
> around implicit conversions that are the core problem with alias this, and
> it avoids all issues with regards to slicing objects (which is the primary
> reason why classes in D are on the heap instead of on the stack like they
> can be in C++).
> 
> - Jonathan M Davis

From my perspective that is mostly an implementation detail.

The reason it is not fully an implementation detail is due to methods effectively having what I've been calling ``@reinterpretAsChild`` turned on.

We can still make overrides, and is expression to check if it inherits from the parent type to work.

I'm ok with this approach. It simplifies a bunch of problems down.

December 05
On Thursday, 5 December 2024 at 22:41:25 UTC, Jonathan M Davis wrote:
>
> After that talk, I discussed struct inheritance briefly with Walter, and his idea was that if we did it, there would be _no_ conversions of any kind that came from it. It would purely be a way to have a struct inherit all of the fields and member functions from the "parent" struct and as such would essentially be the compiler copying and pasting that code into the struct doing the inheriting.
>
> So, it would provide a way to inherit / copy the implementation, but the types would be completely divorced from one another in terms of how they were used.
>
> Personally, I think that that's the right approach, since it's the issues around implicit conversions that are the core problem with alias this, and it avoids all issues with regards to slicing objects (which is the primary reason why classes in D are on the heap instead of on the stack like they can be in C++).

So if I read that correctly, for the latter code example I gave, assuming use of structs rather and classes, and adjusted...

The 'Assign' case would be a compile error, and once removed it would yield:

```
MethB(P) Direct Parent
MethC(P) Direct Parent

MethB(P) Inherit Child
MethC(C) Inherit Child
```

Is that correct?

Also is it the case that method overrides would dominate over any 'pasted' method from the parent - i.e. the MethC case?  Or would those be forbidden as a name conflict?

Presumably one could not have data fields with the same name in the Parent and Child, or would there be a rule for resolving that conflict?


December 05
On Thursday, December 5, 2024 4:36:17 PM MST Derek Fawcus via dip.ideas wrote:
> On Thursday, 5 December 2024 at 22:41:25 UTC, Jonathan M Davis
>
> wrote:
> > After that talk, I discussed struct inheritance briefly with Walter, and his idea was that if we did it, there would be _no_ conversions of any kind that came from it. It would purely be a way to have a struct inherit all of the fields and member functions from the "parent" struct and as such would essentially be the compiler copying and pasting that code into the struct doing the inheriting.
> >
> > So, it would provide a way to inherit / copy the implementation, but the types would be completely divorced from one another in terms of how they were used.
> >
> > Personally, I think that that's the right approach, since it's the issues around implicit conversions that are the core problem with alias this, and it avoids all issues with regards to slicing objects (which is the primary reason why classes in D are on the heap instead of on the stack like they can be in C++).
>
> So if I read that correctly, for the latter code example I gave, assuming use of structs rather and classes, and adjusted...
>
> The 'Assign' case would be a compile error, and once removed it would yield:
>
> ```
> MethB(P) Direct Parent
> MethC(P) Direct Parent
>
> MethB(P) Inherit Child
> MethC(C) Inherit Child
> ```
>
> Is that correct?

The types would be completely independent from one another. It would just be a way to copy the implementation from one to the other. So, no conversions or assignments would ever take place without some explicit code handling it, just like normally occurs with any two types which have nothing to do with one another. It's purely an inheritance of implementation and would not create a hierarchy like you get with classes. So, it would therefore avoid all of the various issues that you get with class inheritance and objects on the stack in C++ - but it also wouldn't get any form of polymorphism. It would just be a way to share code.

> Also is it the case that method overrides would dominate over any 'pasted' method from the parent - i.e. the MethC case?  Or would those be forbidden as a name conflict?

IIRC, from what Walter said, if the child implemented the same member function as the parent, the child's would take precedence, and the parent's would basically not exist in the child.

> Presumably one could not have data fields with the same name in the Parent and Child, or would there be a rule for resolving that conflict?

I didn't discuss that particular issue with Walter, so I don't know what his stance would be, but the most obvious thing would presumably be to just make it illegal. Particularly when you're dealing with private members, it's not like it would be a big deal to just name the member variable something else to avoid the conflict.

We had a fairly quick discussion on some of the obvious issues that I thought of off the top of my head (e.g. conversions and object slicing), but it's not like we sat down and hashed it all out in detail. So, if/when Walter decides to implement it, he'll have to sort that out with a DIP (or he'll have to approve someone else's DIP where they provide all of those details).

But the discussion _was_ enough to make it clear that Walter's intention was to use struct inheritance purely as a way to inherit the implementation and _not_ as a way to provide any sort of implicit conversions or a way to use one type as another (though templated code could use duck typing like it normally does to work with types that have the appropriate API, so you could have the same code work with multiple struct types that inherited from the same parent, since they'd all have the API from the parent).

- Jonathan M Davis



December 05
On Thursday, December 5, 2024 4:10:46 PM MST Richard (Rikki) Andrew Cattermole via dip.ideas wrote:
> > After that talk, I discussed struct inheritance briefly with Walter, and his idea was that if we did it, there would be _no_ conversions of any kind that came from it. It would purely be a way to have a struct inherit all of the fields and member functions from the "parent" struct and as such would essentially be the compiler copying and pasting that code into the struct doing the inheriting.
> >
> > So, it would provide a way to inherit / copy the implementation, but the types would be completely divorced from one another in terms of how they were used.
> >
> > Personally, I think that that's the right approach, since it's the issues around implicit conversions that are the core problem with alias this, and it avoids all issues with regards to slicing objects (which is the primary reason why classes in D are on the heap instead of on the stack like they can be in C++).
> >
> > - Jonathan M Davis
>
>  From my perspective that is mostly an implementation detail.
>
> The reason it is not fully an implementation detail is due to methods effectively having what I've been calling ``@reinterpretAsChild`` turned on.
>
> We can still make overrides, and is expression to check if it inherits from the parent type to work.
>
> I'm ok with this approach. It simplifies a bunch of problems down.

What isn't an implementation detail is that what Walter proposed would involve _zero_ conversions. So, there would be no casting to a parent from a child or vice versa unless you explicitly implemented casts for that. With what Walter was looking to do with struct inheritance, it would not be creating at type hierarchy at all. It would purely be a way to copy the implementation. So, there would nothing like @reinterpretAsChild. The types would effectively be unrelated to one another as far as the type system was concerned. And if we _did_ have some sort of is expression to test whether one struct inherited from another, it couldn't be the same one that's used for classes, because there is no conversion (though honestly, the fact that we test for inheritance via implicit conversion with classes is broken given the fact that alias this exists).

- Jonathan M Davis



December 06
On 06/12/2024 1:14 PM, Jonathan M Davis wrote:
> On Thursday, December 5, 2024 4:10:46 PM MST Richard (Rikki) Andrew Cattermole
> via dip.ideas wrote:
>>> After that talk, I discussed struct inheritance briefly with Walter, and
>>> his idea was that if we did it, there would be _no_ conversions of any
>>> kind that came from it. It would purely be a way to have a struct inherit
>>> all of the fields and member functions from the "parent" struct and as
>>> such would essentially be the compiler copying and pasting that code into
>>> the struct doing the inheriting.
>>>
>>> So, it would provide a way to inherit / copy the implementation, but the
>>> types would be completely divorced from one another in terms of how they
>>> were used.
>>>
>>> Personally, I think that that's the right approach, since it's the issues
>>> around implicit conversions that are the core problem with alias this, and
>>> it avoids all issues with regards to slicing objects (which is the primary
>>> reason why classes in D are on the heap instead of on the stack like they
>>> can be in C++).
>>>
>>> - Jonathan M Davis
>>
>>   From my perspective that is mostly an implementation detail.
>>
>> The reason it is not fully an implementation detail is due to methods
>> effectively having what I've been calling ``@reinterpretAsChild`` turned on.
>>
>> We can still make overrides, and is expression to check if it inherits
>> from the parent type to work.
>>
>> I'm ok with this approach. It simplifies a bunch of problems down.
> 
> What isn't an implementation detail is that what Walter proposed would
> involve _zero_ conversions. So, there would be no casting to a parent from a
> child or vice versa unless you explicitly implemented casts for that. With
> what Walter was looking to do with struct inheritance, it would not be
> creating at type hierarchy at all. It would purely be a way to copy the
> implementation. So, there would nothing like @reinterpretAsChild. The types
> would effectively be unrelated to one another as far as the type system was
> concerned. And if we _did_ have some sort of is expression to test whether
> one struct inherited from another, it couldn't be the same one that's used
> for classes, because there is no conversion (though honestly, the fact that
> we test for inheritance via implicit conversion with classes is broken given
> the fact that alias this exists).
> 
> - Jonathan M Davis

For the checking of inheritance what I mean is:

``is(Child : Parent)``

The same as a class has. Something the compiler certainly has the ability to offer.

For casting, you could only do it via pointers in ``@system`` code, you can do this today. No way would it be appropriate to add a new form of casting. Since there is no vtable or guarantee of heap allocation.

As for ``@reinterpretAsChild`` it would effectively be turned on and couldn't be opt-out, and would be a better way of describing this behavior as it would exist for classes. Rather than introducing some new unique to struct behavior.

This needs a DIP written to make this stuff clear. So that'll be a next months job. Because I understand what you're saying and what Walter is concerned with based upon your statement. Its just that the spec and the implementation are going to have different concerns and they need to be aligned for both the end user and for it to be implementable.

December 05
On Thursday, December 5, 2024 5:35:38 PM MST Richard (Rikki) Andrew Cattermole via dip.ideas wrote:
> For the checking of inheritance what I mean is:
>
> ``is(Child : Parent)``

Except that that _doesn't_ check for inheritance. It checks for an implicit conversion. It's just used for checking for inheritance based on the fact that a reference to a child class can implicitly convert to a reference of its parent class. It's already fundamentally broken in the sense that alias this can make it incorrect if you're testing for inheritance, e.g.

```
void main()
{
    auto foo = new Foo;
    A a = foo;

    static assert(is(Foo : A));
}

class A {}

class Foo
{
    A a;

    alias this = a;
}
```

Really, we need something in __traits to test for inheritance and to stop using is expressions for it.

Either way, it's fundamentally wrong to have is(Child : Parent) tell you that a struct named Child inherits from a struct named Parent, because they wouldn't have an implicit conversion, and : is testing specifically for an implicit conversion.

- Jonathan M Davis



December 06
On 06/12/2024 2:04 PM, Jonathan M Davis wrote:
> On Thursday, December 5, 2024 5:35:38 PM MST Richard (Rikki) Andrew Cattermole
> via dip.ideas wrote:
>> For the checking of inheritance what I mean is:
>>
>> ``is(Child : Parent)``
> 
> Except that that _doesn't_ check for inheritance. It checks for an implicit
> conversion. It's just used for checking for inheritance based on the fact
> that a reference to a child class can implicitly convert to a reference of
> its parent class. It's already fundamentally broken in the sense that alias
> this can make it incorrect if you're testing for inheritance, e.g.
> 
> ```
> void main()
> {
>      auto foo = new Foo;
>      A a = foo;
> 
>      static assert(is(Foo : A));
> }
> 
> class A {}
> 
> class Foo
> {
>      A a;
> 
>      alias this = a;
> }
> ```
> 
> Really, we need something in __traits to test for inheritance and to stop
> using is expressions for it.
> 
> Either way, it's fundamentally wrong to have is(Child : Parent) tell you
> that a struct named Child inherits from a struct named Parent, because they
> wouldn't have an implicit conversion, and : is testing specifically for an
> implicit conversion.
> 
> - Jonathan M Davis

Problem is, template parameters also should work, using ``Child : Parent`` syntax.

Otherwise the usability on this is going to be absolutely atrocious in meta-programming.

December 05
On Thursday, December 5, 2024 6:13:04 PM MST Richard (Rikki) Andrew Cattermole via dip.ideas wrote:
> Problem is, template parameters also should work, using ``Child : Parent`` syntax.
>
> Otherwise the usability on this is going to be absolutely atrocious in meta-programming.

: tests for implicit conversions. That's all it ever does, and making it do anything else will introduce inconsistencies into the language. For instance, since struct inheritance would _not_ involve an implicit conversion, how would you test for an implicit conversion if is(T : U) were true because T inherited from U? After all, since we're unfortunately not actually going to get rid of alias this, it would be quite possible to have T inherit from U an then have an alias this which makes it implicitly convert to U.

Code that wants to test for struct inheritance can do so with a template constraint and a __traits trait just like plenty of other code does. There's nothing special about inheritance, let alone struct inheritance that makes it any different.

Also, honestly, I don't think that testing for struct inheritance with metaprogramming is even likely to be a valid use case outside of niche situations. There is no conversion unless the programmer explicitly adds one, so a "child" type can't be treated as if it were the same as its "parent" type, and if templated code wants to use the API of the parent struct so that it works with any struct type that's derived from it, it's arguably better to test its API and not anything inheritance-related, because it's the API that actually matters for using it, not whether that API was inherited from a particular struct. And if it's the API that's tested, then it can work with any type with that API regardless of whether it's derived from a particular struct.

Really, if struct inheritance does not involve implicit conversion, then I don't think that there's much reason to even care that that relationship exists. They're just two types which happen to have some of the same members, and if they need to be used by the same code, it's going to need to be templated just like happens with any two unrelated types which share enough of the same API to be duck typed together. And there's no need to take the inheritance into account at all with that code. It's simply the API that matters, not where the implementation happens to have come from.

Either way, given that : is specifically for testing implicit conversions, it's really not appropriate to conflate it with inheritance when implict conversions aren't involved. And even for class inheritance, which has the implicit conversion, it's arguably a mistake to use it to test for inheritance, because it's testing for an implicit conversion, not inheritance, and the implicit conversion can be there for other reasons. So, in general, we should be moving away from using is(T : U) to test for inheritance of any kind, not making struct inheritance use it too.

- Jonathan M Davis



December 06
I suppose we could do > and < comparison of types in template parameters and is expressions.

Which could be hierarchy based instead of implicit conversions.

I would need to double check it, but ok I can ditch the : in favor of > here.