digitalmars.D - Dynamic templated virtuals - I still often want them
- Adam D. Ruppe (48/48) Jul 22 2020 You might know about D's template this parameters, which adapt to
- Manu (5/53) Jul 22 2020 That's clever, and I wish I had have thought of it before!!
- Adam D. Ruppe (9/10) Jul 23 2020 Yeah, in the past my thought was so much on `mixin` this
- Max Samukha (25/35) Jul 23 2020 FWIW, a more general way to initialize an object with static type
- Arafel (12/42) Jul 23 2020 Good that I read all the replies before hitting "send", because I was
- Adam D. Ruppe (15/20) Jul 23 2020 Yeah, I've tried that in the past too, it has a couple
- Tove (7/25) Jul 24 2020 class GrandChild : Derived {}
- Arafel (34/38) Jul 23 2020 This does work today, you just don't need the `virtual` keyword:
- Arafel (11/24) Jul 23 2020 Sorry, there's a missing set of parentheses there, it should of course b...
- Tove (3/23) Jul 23 2020 It compiles but doesn't work, in your link you compiled with
- Arafel (29/31) Jul 23 2020 Ups! Sorry, I feel quite dumb now... I had the `-c` left from some
- Arafel (10/17) Jul 23 2020 Forget it, I misunderstood the proposal, I'm feeling dumber and dumber
- Tove (11/15) Jul 23 2020 I had one project where I had to solve the same problem both in
- Tove (5/37) Jul 23 2020 Yes, this works fine. It has at least two issues though.
- Adam D. Ruppe (6/10) Jul 23 2020 Right. Part of my drive here is to provide features typeid
- Adam D. Ruppe (28/30) Jul 23 2020 That's a possible use case of this too, depending on how it is
- Tove (10/18) Jul 23 2020 Yes, please! I recall similar ideas resurfacing multiple times
- Steven Schveighoffer (12/15) Jul 23 2020 This is a great idea. Fits right in with D's philosophy of having the
- Adam D. Ruppe (13/17) Jul 23 2020 Right, I'd want to do that with a normal `override` definition
- Steven Schveighoffer (11/19) Jul 23 2020 It might be trickier than that. It might be that you have to ENSURE that...
- Adam D. Ruppe (7/9) Jul 23 2020 Yeah, the "virtual" keyword in my example was intentional for
- Alexandru Ermicioi (12/15) Jul 28 2020 That will fail for at least one use case:
- Steven Schveighoffer (8/21) Jul 28 2020 It should work. The idea is that the derived type generates the template...
- Alexandru Ermicioi (9/12) Jul 29 2020 Well it does. How do you plan to handle the case where Y library
- Arafel (53/60) Jul 29 2020 I think the trick here is that they wouldn't actually be templates in
- Steven Schveighoffer (6/17) Jul 29 2020 This statement suggests you are not understanding the proposal. Arafel
- Alexandru Ermicioi (27/33) Jul 29 2020 Alright, I did read again original post, and indeed it is about
- Arafel (29/31) Jul 29 2020 Sure, I was just making an example to show how it would work. I leave
- Steven Schveighoffer (9/11) Jul 29 2020 I think it's a 2-stage process. 1) instantiate the template with the
- surlymoor (5/53) Mar 11 2022 Man, I'd really kill for this right about now. I was designing
- Timon Gehr (4/68) Mar 12 2022 The way I work around this is by putting template mixins into all
- surlymoor (9/21) Mar 12 2022 Yeah, I ended up doing this since I made the opening post.
- Walter Bright (2/4) Mar 13 2022 The fwd ref problems I see are usually caused by circular references.
- Timon Gehr (3/9) Mar 14 2022 Yes, those are usually a component, but it's much less likely to be an
- zjh (9/11) Mar 12 2022 Each class should have a `This` type or information like rust's
- bauss (18/20) Mar 14 2022 While not exactly the same, this can be worked around by using
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
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
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
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: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.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
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
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
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
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
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
On Thursday, 23 July 2020 at 08:46:54 UTC, Arafel wrote:On 23/7/20 4:38, Adam D. Ruppe wrote:It compiles but doesn't work, in your link you compiled with "-c", so the runtime assert was never evalutated.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
Jul 23 2020
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
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
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
On Thursday, 23 July 2020 at 10:18:34 UTC, Arafel wrote:On 23/7/20 11:51, Tove wrote: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 -betterCIt 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
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
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
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
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
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
On 7/23/20 1:09 PM, Adam D. Ruppe wrote: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. -SteveOne 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
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
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
On 7/28/20 5:01 PM, Alexandru Ermicioi wrote:On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe wrote: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. -SteveBut 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?
Jul 28 2020
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. -SteveWell 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
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
On 7/29/20 4:01 AM, Alexandru Ermicioi wrote:On Tuesday, 28 July 2020 at 21:09:51 UTC, Steven Schveighoffer wrote: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. -SteveSo 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.
Jul 29 2020
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
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
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
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
On 3/12/22 06:10, surlymoor wrote:On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe 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.)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 12 2022
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
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
On 14.03.22 07:31, Walter Bright wrote:On 3/12/2022 5:00 AM, Timon Gehr wrote:Yes, those are usually a component, but it's much less likely to be an issue without introspection and code generation.(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 14 2022
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: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++`.You might know about D's template this parameters, which adapt
Mar 12 2022
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
On Monday, 14 March 2022 at 07:48:49 UTC, bauss wrote:On Saturday, 12 March 2022 at 05:10:10 UTC, surlymoor 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"); } ```On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe wrote:...
Mar 14 2022
On Monday, 14 March 2022 at 07:59:00 UTC, zjh wrote:On Monday, 14 March 2022 at 07:48:49 UTC, bauss wrote: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:1On Saturday, 12 March 2022 at 05:10:10 UTC, surlymoor 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"); } ```On Thursday, 23 July 2020 at 02:38:27 UTC, Adam D. Ruppe wrote:...
Mar 14 2022