www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Dynamic templated virtuals - I still often want them

reply Adam D. Ruppe <destructionator gmail.com> writes:
You might know about D's template this parameters, which adapt to 
the static type of a `this` reference on a call, or with the 
curiously recurring template pattern, which passes a derived 
class to the base class so the base class can inspect the derived 
class.

Both of these are useful at times, but neither quite do what I 
have in mind here.

Imagine this:

---
class Base {
      virtual string serialize(this This)() {
            return This.stringof; // just for example
      }
}

class Derived : Base {

}

void main() {
     Base b = new Derived();
     assert(b.serialize() == "Derived");
}
---

That does NOT work today. For one, of course, D has no `virtual` 
keyword, but if you left that out, it would compile, but fail the 
assert because the static type of `b` passed to the template This 
is actually still `Base`.

But imagine if `serialize` actually got a virtual table entry, 
just like if it wasn't a template at all, but each child class 
automatically got an instance of it made for itself.

So it would be as if I wrote

class Base {
      string serialize() {
            return Base.stringof;
      }
}

class Derived : Base {
      override string serialize() {
            return Derived.stringof;
      }
}

by hand. Of course, it is possible to do this kind of thing with 
mixin templates or the CRTP, but in both cases, the Derived class 
must actually write it in the child class, and if you don't 
there's no way to really tell; it will still compile and just use 
the base class implementation. Which might be OK but it isn't 
perfect.

It would just be cool if the base class template was 
automatically instantiated again for the child class, while still 
working like a normal virtual call.
Jul 22 2020
next sibling parent reply Manu <turkeyman gmail.com> writes:
On Thu, Jul 23, 2020 at 12:40 PM Adam D. Ruppe via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 You might know about D's template this parameters, which adapt to
 the static type of a `this` reference on a call, or with the
 curiously recurring template pattern, which passes a derived
 class to the base class so the base class can inspect the derived
 class.

 Both of these are useful at times, but neither quite do what I
 have in mind here.

 Imagine this:

 ---
 class Base {
       virtual string serialize(this This)() {
             return This.stringof; // just for example
       }
 }

 class Derived : Base {

 }

 void main() {
      Base b = new Derived();
      assert(b.serialize() == "Derived");
 }
 ---

 That does NOT work today. For one, of course, D has no `virtual`
 keyword, but if you left that out, it would compile, but fail the
 assert because the static type of `b` passed to the template This
 is actually still `Base`.

 But imagine if `serialize` actually got a virtual table entry,
 just like if it wasn't a template at all, but each child class
 automatically got an instance of it made for itself.

 So it would be as if I wrote

 class Base {
       string serialize() {
             return Base.stringof;
       }
 }

 class Derived : Base {
       override string serialize() {
             return Derived.stringof;
       }
 }

 by hand. Of course, it is possible to do this kind of thing with
 mixin templates or the CRTP, but in both cases, the Derived class
 must actually write it in the child class, and if you don't
 there's no way to really tell; it will still compile and just use
 the base class implementation. Which might be OK but it isn't
 perfect.

 It would just be cool if the base class template was
 automatically instantiated again for the child class, while still
 working like a normal virtual call.
That's clever, and I wish I had have thought of it before!! This pattern would have helped me in multiple scenarios I've encountered in the past where I had to deploy awkward and ugly mixin tricks.
Jul 22 2020
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 23 July 2020 at 03:50:03 UTC, Manu wrote:
 That's clever, and I wish I had have thought of it before!!
Yeah, in the past my thought was so much on `mixin` this particular thing never came to mind. But last night, I was trying to goof around with no-runtime classes again (actually very easy to do basics nowadays, I'll probably blog about this monday) and wanted to experiment with dynamic casts and this came up... and I was just like "omg if the virtual slot got a template instance it would rock". And thus it came to mind.
Jul 23 2020
parent reply Max Samukha <maxsamukha gmail.com> writes:
On Thursday, 23 July 2020 at 14:41:06 UTC, Adam D. Ruppe wrote:
 On Thursday, 23 July 2020 at 03:50:03 UTC, Manu wrote:
 That's clever, and I wish I had have thought of it before!!
Yeah, in the past my thought was so much on `mixin` this particular thing never came to mind. But last night, I was trying to goof around with no-runtime classes again (actually very easy to do basics nowadays, I'll probably blog about this monday) and wanted to experiment with dynamic casts and this came up... and I was just like "omg if the virtual slot got a template instance it would rock". And thus it came to mind.
FWIW, a more general way to initialize an object with static type data would be (currently it fails for indirectly derived classes due to a compiler bug): class Base { this(this This)() { _serialize = function(Base base) { // base can be safely reinterpreted into This here if needed return This.stringof; // just for example }; } final string serialize() { return _serialize(this); } private string function(Base) _serialize; } class Derived: Base { } void main() { Base b = new Derived; assert(b.serialize() == "Derived"); } Also, there seems to be no good reason "this T" should not work for any class member, including class static constructors.
Jul 23 2020
next sibling parent Arafel <er.krali gmail.com> writes:
On 23/7/20 17:00, Max Samukha wrote:
 WIW, a more general way to initialize an object with static type data 
 would be (currently it fails for indirectly derived classes due to a 
 compiler bug):
 
 class Base {
      this(this This)() {
          _serialize = function(Base base) {
              // base can be safely reinterpreted into This here if
needed
 
              return This.stringof; // just for example
          };
      }
 
      final string serialize() {
          return _serialize(this);
      }
 
      private string function(Base) _serialize;
 }
 
 class Derived: Base {
 }
 
 void main() {
      Base b = new Derived;
      assert(b.serialize() == "Derived");
 }
 
Good that I read all the replies before hitting "send", because I was going to propose exactly this :-) My only doubt is how it would interact with constructors defined in the derived classes. I think I tried doing something like this some time ago, but I couldn't make it work in the end because the `super` constructor wasn't being called. Being able to register classes when there are first instantiated would be a useful workaround.
 Also, there seems to be no good reason "this T" should not work for any 
 class member, including class static constructors.
This seems to be regularly reported as a bug, I posted earlier some of them. I would really love to see this work, and not just for static functions, also for enums, aliases or actually any other kind of template.
Jul 23 2020
prev sibling next sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 23 July 2020 at 15:00:59 UTC, Max Samukha wrote:
 FWIW, a more general way to initialize an object with static 
 type data would be (currently it fails for indirectly derived 
 classes due to a compiler bug):
Yeah, I've tried that in the past too, it has a couple limitations (even if it worked): 1) it would only run at the construction site, meaning it must actually store data with the instance instead of just returning a new data in a method. This could be as simple as storing a pointer to some static data in the instance but it must be something. 2) it might not go all the way down. Imagine: class Grandchild : Derived {} there the implicit constructor for Derived would be called that can satisfy Base... but Grandchild may not be seen. Of course the compiler could require an explicit call to the (this This) one too though.
 Also, there seems to be no good reason "this T" should not work 
 for any class member, including class static constructors.
indeed.
Jul 23 2020
prev sibling parent Tove <tove fransson.se> writes:
On Thursday, 23 July 2020 at 15:00:59 UTC, Max Samukha wrote:
 FWIW, a more general way to initialize an object with static 
 type data would be (currently it fails for indirectly derived 
 classes due to a compiler bug):

 class Base {
     this(this This)() {
         _serialize = function(Base base) {
             // base can be safely reinterpreted into This here 
 if needed

             return This.stringof; // just for example
         };
     }

     final string serialize() {
         return _serialize(this);
     }

     private string function(Base) _serialize;
 }

 class Derived: Base {
 }
class GrandChild : Derived {} Please file a bug/enhancement report that 'this(this This)()' in Base doesn't work intuitively for GrandChild. It would give a lot of expressive power with a very small fix/change... small enough that it wouldn't require a DIP, at least not in my opinion.
Jul 24 2020
prev sibling next sibling parent reply Arafel <er.krali gmail.com> writes:
On 23/7/20 4:38, Adam D. Ruppe wrote:
 That does NOT work today. For one, of course, D has no `virtual` 
 keyword, but if you left that out, it would compile, but fail the assert 
 because the static type of `b` passed to the template This is actually 
 still `Base`.
This does work today, you just don't need the `virtual` keyword: ``` class Base { string serialize(this This)() { return This.stringof; // just for example } } class Derived : Base { } void main() { Base b = new Derived(); assert(b.serialize() == "Derived"); } ``` https://run.dlang.io/is/vp1koK I have used this to do some nice tricks. What is missing is to make the `this` parameter work out of normal function, so with static functions, and even out of functions: https://issues.dlang.org/show_bug.cgi?id=10488 https://issues.dlang.org/show_bug.cgi?id=20277 Specifically, I want to be able to transform Compile-time introspection into run-time instrospection: ``` TypeInfo_Class[string] rtIntrospection; class Base { shared static this (this This) { rtIntrospection[This.stringof] = This.classinfo; } } class Derived : Base { } // The entry is added automatically ``` Now if you want to have something like this you have to depend on the end user registering each class manually.
Jul 23 2020
next sibling parent Arafel <er.krali gmail.com> writes:
On 23/7/20 10:46, Arafel wrote:
 
 ```
 
 TypeInfo_Class[string] rtIntrospection;
 
 class Base {
      shared static this (this This) {
          rtIntrospection[This.stringof] = This.classinfo;
      }
 }
 
 class Derived : Base { } // The entry is added automatically
 ```
Sorry, there's a missing set of parentheses there, it should of course be: ``` TypeInfo_Class[string] rtIntrospection; class Base { shared static this (this This)() { rtIntrospection[This.stringof] = This.classinfo; } } class Derived : Base { } // The entry is added automatically ```
Jul 23 2020
prev sibling next sibling parent reply Tove <tove fransson.se> writes:
On Thursday, 23 July 2020 at 08:46:54 UTC, Arafel wrote:
 On 23/7/20 4:38, Adam D. Ruppe wrote:
 That does NOT work today. For one, of course, D has no 
 `virtual` keyword, but if you left that out, it would compile, 
 but fail the assert because the static type of `b` passed to 
 the template This is actually still `Base`.
This does work today, you just don't need the `virtual` keyword: ``` class Base { string serialize(this This)() { return This.stringof; // just for example } } class Derived : Base { } void main() { Base b = new Derived(); assert(b.serialize() == "Derived"); } ``` https://run.dlang.io/is/vp1koK
It compiles but doesn't work, in your link you compiled with "-c", so the runtime assert was never evalutated.
Jul 23 2020
parent reply Arafel <er.krali gmail.com> writes:
On 23/7/20 11:51, Tove wrote:
 It compiles but doesn't work, in your link you compiled with "-c", so 
 the runtime assert was never evalutated.
Ups! Sorry, I feel quite dumb now... I had the `-c` left from some previous tests and didn't notice. I have indeed reviewed my code, and the caller is also templated on the actual class, i.e., it's also calling the method with the derived type. And in fact, now that I think about it, it can't possibly work at compile time. Consider: ``` Base b = uniform(0,2) ? new Base() : new Derived(); ``` This can obviously not be resolved at compile time (where the template `this` parameter works). For runtime resolution you can already use `typeid(this)`, and this (this time yes) works: ``` module foo; import std; class Base { string serialize() { return typeid(this).toString; // just for example } } class Derived : Base { } void main() { Base b = new Derived(); assert(b.serialize == "foo.Derived"); } ```
Jul 23 2020
next sibling parent reply Arafel <er.krali gmail.com> writes:
On 23/7/20 12:18, Arafel wrote:
 
 And in fact, now that I think about it, it can't possibly work at 
 compile time. Consider:
 
 ```
 Base b = uniform(0,2) ? new Base() : new Derived();
 ```
Forget it, I misunderstood the proposal, I'm feeling dumber and dumber now :-/ So the function would be instantiated again for each implementing class. It would be really cool and I like it, but I've got the feeling that it's just working around the fact that there is no proper RT introspection yet. If you could do RT introspection the same way you can do it at compile time (for instance access to the individual fields), this wouldn't be needed.
Jul 23 2020
parent Tove <tove fransson.se> writes:
On Thursday, 23 July 2020 at 10:27:14 UTC, Arafel wrote:
 On 23/7/20 12:18, Arafel wrote:

 If you could do RT introspection the same way you can do it at 
 compile time (for instance access to the individual fields), 
 this wouldn't be needed.
I had one project where I had to solve the same problem both in + Simple - Slow Later I had to optimize it by doing "RT introspection" and caching the result. + Fast - Somewhat complex Adams proposal is both simple and fast out of the box.
Jul 23 2020
prev sibling next sibling parent Tove <tove fransson.se> writes:
On Thursday, 23 July 2020 at 10:18:34 UTC, Arafel wrote:
 On 23/7/20 11:51, Tove wrote:
 It compiles but doesn't work, in your link you compiled with 
 "-c", so the runtime assert was never evalutated.
Ups! Sorry, I feel quite dumb now... I had the `-c` left from some previous tests and didn't notice. I have indeed reviewed my code, and the caller is also templated on the actual class, i.e., it's also calling the method with the derived type. And in fact, now that I think about it, it can't possibly work at compile time. Consider: ``` Base b = uniform(0,2) ? new Base() : new Derived(); ``` This can obviously not be resolved at compile time (where the template `this` parameter works). For runtime resolution you can already use `typeid(this)`, and this (this time yes) works: ``` module foo; import std; class Base { string serialize() { return typeid(this).toString; // just for example } } class Derived : Base { } void main() { Base b = new Derived(); assert(b.serialize == "foo.Derived"); } ```
Yes, this works fine. It has at least two issues though. 1) Slower than a virtual function call, but I suppose the result of typeid could be cached to mitigate this issue. 2) TypeInfo cannot be used with -betterC
Jul 23 2020
prev sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 23 July 2020 at 10:18:34 UTC, Arafel wrote:
 This can obviously not be resolved at compile time (where the 
 template `this` parameter works).

 For runtime resolution you can already use `typeid(this)`, and 
 this (this time yes) works:
Right. Part of my drive here is to provide features typeid currently doesn't; if you can auto-instantiate a template in child classes to implement a normal virtual interface, then we can bridge the CT and RT worlds very, very easily and create new things as-needed.
Jul 23 2020
prev sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 23 July 2020 at 08:46:54 UTC, Arafel wrote:
 Specifically, I want to be able to transform Compile-time 
 introspection into run-time instrospection:
That's a possible use case of this too, depending on how it is implemented. You might be able to make an interface method that includes a static initializer. interface Reflectable { virtual void callMethod(this This)(string name) { // normal CT reflection based impl } } Then any class A : Reflectable {} will automatically implement the callMethod interface thanks to the virtual template. Also MIGHT be possible to auto-register: virtual template reflect(this This) { // and maybe for registering a factory function... shared static this() { /* register */ } } but that's a lot iffier because a long-form template like that may have multiple members and muddy up the interface. Still worth considering though. In the past, proposals on this area have been along the lines of the parent class/interface defining a mixin template that the compiler automatically mixes into its child classes. That would definitely enable the auto-registration, among other things. That's actually a strict superset of my new idea and I'd be happy with it too, just this simpler one might have advantages in its simplicity; it certainly feels natural to write.
Jul 23 2020
prev sibling next sibling parent Tove <tove fransson.se> writes:
On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe wrote:
 You might know about D's template this parameters, which adapt 
 to the static type of a `this` reference on a call, or with the 
 curiously recurring template pattern, which passes a derived 
 class to the base class so the base class can inspect the 
 derived class.


 It would just be cool if the base class template was 
 automatically instantiated again for the child class, while 
 still working like a normal virtual call.
Yes, please! I recall similar ideas resurfacing multiple times over the course of at least 7 years from different people. Back then we, didn't have a good DIP process so noone could take it to the next level. I am convinced this is a killer enabler feature. As it is today, many people that has been using D for a long time have their own more or less ugly mixin workarounds, but it's not easy for everyone just starting with D to come up with this pattern themselves.
Jul 23 2020
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 7/22/20 10:38 PM, Adam D. Ruppe wrote:

 But imagine if `serialize` actually got a virtual table entry, just like 
 if it wasn't a template at all, but each child class automatically got 
 an instance of it made for itself.
This is a great idea. Fits right in with D's philosophy of having the compiler write code for you so you don't have to. I can imagine some great uses for serialization. The syntax is probably the trickiest part of this potential feature -- especially since templates are automatically final, and we have no virtual keyword. Potentially as well, you could specify overrides of the Base template in a derived type if needed. One other tricky thing is that the override functions must not vary the parameter or return types (except for covariance). -Steve
Jul 23 2020
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 23 July 2020 at 16:06:03 UTC, Steven Schveighoffer 
wrote:
 Potentially as well, you could specify overrides of the Base 
 template in a derived type if needed.
Right, I'd want to do that with a normal `override` definition just like any other method. (semantically, the virtual template would probably work the same as a mixin template. If you provide your own override, it just ignores the template version using yours instead.)
 One other tricky thing is that the override functions must not 
 vary the parameter or return types (except for covariance).
Yeah, it would still be subject to the same semantic checks as if it was written by hand and would need to error there; it is still an interface method. Though the compiler might not actually diagnose that error until you actually try to subclass it (again, just like the mixin template thing, just without the explicit `mixin X`).
Jul 23 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 7/23/20 1:09 PM, Adam D. Ruppe wrote:
 One other tricky thing is that the override functions must not vary 
 the parameter or return types (except for covariance).
Yeah, it would still be subject to the same semantic checks as if it was written by hand and would need to error there; it is still an interface method. Though the compiler might not actually diagnose that error until you actually try to subclass it (again, just like the mixin template thing, just without the explicit `mixin X`).
It might be trickier than that. It might be that you have to ENSURE that there's only one version of the function to override. Otherwise, you can have an unbounded requirement for the vtable. e.g.: void foo(this This, T)(T item) {... } As this is valid today, we can't (and shouldn't) disallow it. But it also can't be virtual. It means we might have to require a separate syntax for this feature. An actual virtual keyword would be super-useful here. -Steve
Jul 23 2020
parent Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 23 July 2020 at 17:40:50 UTC, Steven Schveighoffer 
wrote:
 It means we might have to require a separate syntax for this 
 feature. An actual virtual keyword would be super-useful here.
Yeah, the "virtual" keyword in my example was intentional for exactly this reason; it should create one and only one virtual table slot. Though the specific syntax of course might be different, I try not to get too worked up over that (though omg yes virtual keyword would be very useful in other places too).
Jul 23 2020
prev sibling next sibling parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe wrote:
 But imagine if `serialize` actually got a virtual table entry, 
 just like if it wasn't a template at all, but each child class 
 automatically got an instance of it made for itself.
That will fail for at least one use case: Consider class A that has templated virtual method do. It lives in library X. Then we have class B : A that is in library Y. Now application U has variable of type A that contains type B. At some point U calls 'do' with an int. How should compiler proceed with such situation given Y is available only in compiled version? Virtual template methods could work if D would have generics similar to Java, which are basically syntactic sugar for dynamic casts. Best regards, Alexandru.
Jul 28 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 7/28/20 5:01 PM, Alexandru Ermicioi wrote:
 On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe wrote:
 But imagine if `serialize` actually got a virtual table entry, just 
 like if it wasn't a template at all, but each child class 
 automatically got an instance of it made for itself.
That will fail for at least one use case: Consider class A that has templated virtual method do. It lives in library X. Then we have class B : A that is in library Y. Now application U has variable of type A that contains type B. At some point U calls 'do' with an int. How should compiler proceed with such situation given Y is available only in compiled version?
It should work. The idea is that the derived type generates the template code and stores it in the vtable. It's not much different from the solution Max Samukha posted, except the compiler should do it instead of the user, and it's put in the vtable instead of the instance. So it doesn't matter where the code is stored, it is already concrete and part of the vtable. -Steve
Jul 28 2020
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Tuesday, 28 July 2020 at 21:09:51 UTC, Steven Schveighoffer 
wrote:
 So it doesn't matter where the code is stored, it is already 
 concrete and part of the vtable.

 -Steve
Well it does. How do you plan to handle the case where Y library is already compiled (separately, or available as .lib with .di interface)? Vtable should already be defined, which means that upon loading of lib, main app needs to patch vtable with missing template specializations. Best regards, Alexandru.
Jul 29 2020
next sibling parent Arafel <er.krali gmail.com> writes:
On 29/7/20 10:01, Alexandru Ermicioi wrote:
 Well it does. How do you plan to handle the case where Y library is 
 already compiled (separately, or available as .lib with .di interface)? 
 Vtable should already be defined, which means that upon loading of lib, 
 main app needs to patch vtable with missing template specializations.
 
 Best regards,
 Alexandru.
I think the trick here is that they wouldn't actually be templates in the usual sense. So: ``` class Base { virtual void fun(this This)() { /* body */ } } ``` would be lowered to: ``` class Base { void fun() { alias This = Base; /* body */ } } ``` The code for `fun` would still be available in the .di file (as with any other template), so when you then later do: ``` class Derived : Base { } ``` it would be rewritten / lowered as: ``` class Derived : Base { override void fun() { alias This = Derived; /* body */ } } ``` that could be then perfectly interact with all other compilation units. I'm happy to be corrected if I understood it wrong, but I think it could work this way without any side-effect (i.e. you can try the lowered version now and it works): ``` class Base { string serialize() { alias This = Base; return This.stringof; // just for example } } class Derived : Base { override string serialize() { alias This = Derived; return This.stringof; // just for example } } void main() { Base b = new Derived(); assert(b.serialize() == "Derived"); } ```
Jul 29 2020
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 7/29/20 4:01 AM, Alexandru Ermicioi wrote:
 On Tuesday, 28 July 2020 at 21:09:51 UTC, Steven Schveighoffer wrote:
 So it doesn't matter where the code is stored, it is already concrete 
 and part of the vtable.
Well it does. How do you plan to handle the case where Y library is already compiled (separately, or available as .lib with .di interface)? Vtable should already be defined, which means that upon loading of lib, main app needs to patch vtable with missing template specializations.
This statement suggests you are not understanding the proposal. Arafel is right -- it's not a template function to be instantiated on calling, but a concrete function in the vtable instantiated on declaration. I can't explain it better than Arafel did. -Steve
Jul 29 2020
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Wednesday, 29 July 2020 at 12:17:55 UTC, Steven Schveighoffer 
wrote:
 On 7/29/20 4:01 AM, Alexandru Ermicioi wrote:>> On Tuesday, 28 
 July 2020 at 21:09:51 UTC, Steven Schveighoffer This statement 
 suggests you are not understanding the proposal. Arafel is 
 right -- it's not a template function to be instantiated on 
 calling, but a concrete function in the vtable instantiated on 
 declaration.
Alright, I did read again original post, and indeed it is about (this T) case only, which may work. I was referring to broader case of templates in general, which would have issues I mentioned. Still, for these virtual (this T) methods, it's best to have a different/modified syntax to existing x(this T)(), since they mean two different things. And this feature can be implemented also in following fashion right now: --- class Base { protected string doImpl(this T)() { return T.stringof; } public abstract do(); } class Derived { public do() { this.doImpl(); } } --- imho, this feature can be lowered to code in example above, instead of generating new body for each derived class. Best regards, Alexandru.
Jul 29 2020
parent reply Arafel <er.krali gmail.com> writes:
On 29/7/20 15:04, Alexandru Ermicioi wrote:
 imho, this feature can be lowered to code in example above, instead of 
 generating new body for each derived class.
Sure, I was just making an example to show how it would work. I leave the inner details on how it would be implemented to more knowledgeable people than me :-) Still, in your version, at the end of the day you're instantiating anyway the template at each class, so the code will also be generated, and even with an indirection (ok, it should be optimized / inlined). Also it could be tricky with things such as attributes, etc. One possible issue I see is what would happen with template-specific features, like `auto ref`, that wouldn't be available in this case, for instance: ``` class Base { virtual foo(this This)(auto ref bar) { } } ``` or if you use This as the type of a parameter: ``` class Base { virtual foo(this This)(This bar) { } } class Derived : Base { } void main() { Base b = new Derived; b.foo(new Base); // Fails to compile? } ``` You could of course special-case the syntax to disable these cases, but for the users it would be an unexpected outlier in how the templates work.
Jul 29 2020
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 7/29/20 9:37 AM, Arafel wrote:

 You could of course special-case the syntax to disable these cases, but 
 for the users it would be an unexpected outlier in how the templates work.
I think it's a 2-stage process. 1) instantiate the template with the type ONLY. If it can't be instantiated, then it's not valid. 2) check for normal overriding rules. If it cannot override the base function, then it's an error. One difference might have to be attribute inference, as you may not want the template to infer more restrictive attributes than you need, and there is no way to say e.g. gc. -Steve
Jul 29 2020
prev sibling parent reply surlymoor <surlymoor cock.li> writes:
On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe wrote:
 You might know about D's template this parameters, which adapt 
 to the static type of a `this` reference on a call, or with the 
 curiously recurring template pattern, which passes a derived 
 class to the base class so the base class can inspect the 
 derived class.

 Both of these are useful at times, but neither quite do what I 
 have in mind here.

 Imagine this:

 ---
 class Base {
      virtual string serialize(this This)() {
            return This.stringof; // just for example
      }
 }

 class Derived : Base {

 }

 void main() {
     Base b = new Derived();
     assert(b.serialize() == "Derived");
 }
 ---

 That does NOT work today. For one, of course, D has no 
 `virtual` keyword, but if you left that out, it would compile, 
 but fail the assert because the static type of `b` passed to 
 the template This is actually still `Base`.

 But imagine if `serialize` actually got a virtual table entry, 
 just like if it wasn't a template at all, but each child class 
 automatically got an instance of it made for itself.

 So it would be as if I wrote

 class Base {
      string serialize() {
            return Base.stringof;
      }
 }

 class Derived : Base {
      override string serialize() {
            return Derived.stringof;
      }
 }

 by hand. Of course, it is possible to do this kind of thing 
 with mixin templates or the CRTP, but in both cases, the 
 Derived class must actually write it in the child class, and if 
 you don't there's no way to really tell; it will still compile 
 and just use the base class implementation. Which might be OK 
 but it isn't perfect.

 It would just be cool if the base class template was 
 automatically instantiated again for the child class, while 
 still working like a normal virtual call.
Man, I'd really kill for this right about now. I was designing something that depended on the proposed behavior existing. My ignorance is my own fault, of course, but still this would be a very cool feature for D.
Mar 11 2022
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 3/12/22 06:10, surlymoor wrote:
 On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe wrote:
 You might know about D's template this parameters, which adapt to the 
 static type of a `this` reference on a call, or with the curiously 
 recurring template pattern, which passes a derived class to the base 
 class so the base class can inspect the derived class.

 Both of these are useful at times, but neither quite do what I have in 
 mind here.

 Imagine this:

 ---
 class Base {
      virtual string serialize(this This)() {
            return This.stringof; // just for example
      }
 }

 class Derived : Base {

 }

 void main() {
     Base b = new Derived();
     assert(b.serialize() == "Derived");
 }
 ---

 That does NOT work today. For one, of course, D has no `virtual` 
 keyword, but if you left that out, it would compile, but fail the 
 assert because the static type of `b` passed to the template This is 
 actually still `Base`.

 But imagine if `serialize` actually got a virtual table entry, just 
 like if it wasn't a template at all, but each child class 
 automatically got an instance of it made for itself.

 So it would be as if I wrote

 class Base {
      string serialize() {
            return Base.stringof;
      }
 }

 class Derived : Base {
      override string serialize() {
            return Derived.stringof;
      }
 }

 by hand. Of course, it is possible to do this kind of thing with mixin 
 templates or the CRTP, but in both cases, the Derived class must 
 actually write it in the child class, and if you don't there's no way 
 to really tell; it will still compile and just use the base class 
 implementation. Which might be OK but it isn't perfect.

 It would just be cool if the base class template was automatically 
 instantiated again for the child class, while still working like a 
 normal virtual call.
Man, I'd really kill for this right about now. I was designing something that depended on the proposed behavior existing. My ignorance is my own fault, of course, but still this would be a very cool feature for D.
The way I work around this is by putting template mixins into all subclasses, using `typeof(this)`. (It's a bit finicky though, template mixins are notorious for exposing forward reference compiler bugs.)
Mar 12 2022
next sibling parent surlymoor <surlymoor cock.li> writes:
On Saturday, 12 March 2022 at 13:00:24 UTC, Timon Gehr wrote:
 The way I work around this is by putting template mixins into 
 all subclasses, using `typeof(this)`. (It's a bit finicky 
 though, template mixins are notorious for exposing forward 
 reference compiler bugs.)
Yeah, I ended up doing this since I made the opening post. Luckily I've yet to experience any compiler bugs, so yourself and others must have ironed most of them out by now, lol. But to have what Adam proposed would still be amazing. On Saturday, 12 March 2022 at 15:06:58 UTC, zjh wrote:
 Each class should have a `This` type or information like rust's 
 `self/Self` to indicate `Self Type`.
 `The base class` should have a special `This` template 
 parameter. As long as subclass inherits it, `the subclass` will 
 automatically replace `This` as itself Type.
 In fact, it once again shows the `benefits` of `inheritance`.
 `Struct` should be able to inherit, or even multi inheritation 
 like `C++`.
Mostly in agreement, but I don't really see the need for structs to have vtables and whatnot if you are given the ability to easily allocate class instances however you please.
Mar 12 2022
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 3/12/2022 5:00 AM, Timon Gehr wrote:
 (It's a bit finicky though, template mixins are notorious 
 for exposing forward reference compiler bugs.)
The fwd ref problems I see are usually caused by circular references.
Mar 13 2022
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 14.03.22 07:31, Walter Bright wrote:
 On 3/12/2022 5:00 AM, Timon Gehr wrote:
 (It's a bit finicky though, template mixins are notorious for exposing 
 forward reference compiler bugs.)
The fwd ref problems I see are usually caused by circular references.
Yes, those are usually a component, but it's much less likely to be an issue without introspection and code generation.
Mar 14 2022
prev sibling next sibling parent zjh <fqbqrr 163.com> writes:
On Saturday, 12 March 2022 at 05:10:10 UTC, surlymoor wrote:
 On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe wrote:
 You might know about D's template this parameters, which adapt
Each class should have a `This` type or information like rust's `self/Self` to indicate `Self Type`. `The base class` should have a special `This` template parameter. As long as subclass inherits it, `the subclass` will automatically replace `This` as itself Type. In fact, it once again shows the `benefits` of `inheritance`. `Struct` should be able to inherit, or even multi inheritation like `C++`.
Mar 12 2022
prev sibling parent reply bauss <jj_1337 live.dk> writes:
On Saturday, 12 March 2022 at 05:10:10 UTC, surlymoor wrote:
 On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe wrote:
 ...
While not exactly the same, this can be worked around by using the constructor of the base class. ```d class Base { private string _stringof; this(this This)() { _stringof = This.stringof; } string serialize() { return _stringof; } } class Derived : Base { } void main() { Base b = new Derived(); assert(b.serialize() == "Derived"); // Passes } ```
Mar 14 2022
parent reply zjh <fqbqrr 163.com> writes:
On Monday, 14 March 2022 at 07:48:49 UTC, bauss wrote:
 On Saturday, 12 March 2022 at 05:10:10 UTC, surlymoor wrote:
 On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe wrote:
 ...
but this fails: ```d class Base { string serialize(this This)() { string _stringof = This.stringof; return _stringof; } } class Derived : Base { } void main() { Base b = new Derived(); assert(b.serialize() == "Derived"); } ```
Mar 14 2022
parent bauss <jj_1337 live.dk> writes:
On Monday, 14 March 2022 at 07:59:00 UTC, zjh wrote:
 On Monday, 14 March 2022 at 07:48:49 UTC, bauss wrote:
 On Saturday, 12 March 2022 at 05:10:10 UTC, surlymoor wrote:
 On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe 
 wrote:
 ...
but this fails: ```d class Base { string serialize(this This)() { string _stringof = This.stringof; return _stringof; } } class Derived : Base { } void main() { Base b = new Derived(); assert(b.serialize() == "Derived"); } ```
Yes, that's the point of my message, that it's not the same but it can be used to workaround, so you basically "cache" the values to use in the constructor, since you can't retrieve them elsewhere. The code I shared will achieve the same in theory, while of course not being 1:1
Mar 14 2022