www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - opDispatch doesn't play nice with inheritance

reply Carl Sturtivant <sturtivant gmail.com> writes:
//Consider this:
import std.stdio;

void main() {
     X obj = new Y;
     writeln( obj._f() );
}

class Proxy {
     X x;
     this(X x) { this.x = x; }
     string _f() { return "Proxy._f called"; }
}

class X {
     auto opDispatch(string f, Args...)(Args args) {
         Proxy p = new Proxy(this);
         return mixin("p."~f~"(args)");
     }
}

class Y : X {
     string _f() { return "Y._f called"; }
}
//

Presumably the presence of obj._f()in main causes the compilation 
of a function _f() in the class X, yet the function _f() in Y 
that inherits it merely shadows it; the keyword override cannot 
be used for this function.

opDispatch is special in that it allows for functions to be added 
to a class or struct when undefined overtly but used elsewhere 
but it seems those functions sadly are final.

Can anything useful be done to remedy the situation?
Nov 15 2018
next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 15 November 2018 at 17:14:21 UTC, Carl Sturtivant 
wrote:
 opDispatch is special in that it allows for functions to be 
 added to a class or struct when undefined overtly but used 
 elsewhere but it seems those functions sadly are final.
Right, all templates are final, including opDispatch. What I've done in the past is to forward them to a virtual function with runtime arguments instead of compile time arguments. Kinda like: void opDispatch(string name)() { callMethod(name); } /* virtual */ void callMethod(string name) { // do a reflection loop or associate array or switch or whatever to dispatch it here, reimplemented (or at least re-mixed-in) in the child classes to account for their new methods } What exactly do you need to do with it? Maybe you can do an alternative design.
Nov 15 2018
parent Carl Sturtivant <sturtivant gmail.com> writes:
On Thursday, 15 November 2018 at 18:04:42 UTC, Adam D. Ruppe 
wrote:
 Right, all templates are final, including opDispatch.

 What I've done in the past is to forward them to a virtual 
 function with runtime arguments instead of compile time 
 arguments.

 Kinda like:

 void opDispatch(string name)() {
      callMethod(name);
 }

 /* virtual */ void callMethod(string name) {
      // do a reflection loop or associate array or switch or 
 whatever to dispatch it here, reimplemented (or at least 
 re-mixed-in) in the child classes to account for their new 
 methods
 }



 What exactly do you need to do with it? Maybe you can do an 
 alternative design.
Well, I found an alternative design not using opDispatch, not fundamentally dissimilar to what you suggested above, thanks for that, and it's not too uneconomical in its textual organization. I was hoping to avoid doing any of my own dispatching when inheritance would do it for me.
Nov 15 2018
prev sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/15/2018 09:14 AM, Carl Sturtivant wrote:

 opDispatch is special in that it allows for functions to be added to a
 class or struct when undefined overtly but used elsewhere but it seems
 those functions sadly are final.

 Can anything useful be done to remedy the situation?
For the compiler to be able to make all opDispatch instantiations virtual, it would have to first see all calls that generate opDispatch instantiations. (Impossible in the presence of separate compilation.) Only then the compiler would know how large the vtbl of the base class should be and what member functions of the derived class are overrides of those virtual functions. I think it would be confusing as well; a function suddenly becomes virtual just because someone else made a call on the base class interface. Ali
Nov 15 2018
parent reply Carl Sturtivant <sturtivant gmail.com> writes:
On Thursday, 15 November 2018 at 19:01:45 UTC, Ali Çehreli wrote:
 On 11/15/2018 09:14 AM, Carl Sturtivant wrote:

 opDispatch is special in that it allows for functions to be
added to a
 class or struct when undefined overtly but used elsewhere but
it seems
 those functions sadly are final.

 Can anything useful be done to remedy the situation?
For the compiler to be able to make all opDispatch instantiations virtual, it would have to first see all calls that generate opDispatch instantiations. (Impossible in the presence of separate compilation.) Only then the compiler would know how large the vtbl of the base class should be and what member functions of the derived class are overrides of those virtual functions.
I suppose it's such administrative difficulties that led to D defining anything that might conceivably be overridden to be virtual, whether or not actually overridden.
Nov 17 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Saturday, November 17, 2018 11:09:51 PM MST Carl Sturtivant via 
Digitalmars-d-learn wrote:
 On Thursday, 15 November 2018 at 19:01:45 UTC, Ali Çehreli wrote:
 On 11/15/2018 09:14 AM, Carl Sturtivant wrote:
 opDispatch is special in that it allows for functions to be
added to a
 class or struct when undefined overtly but used elsewhere but
it seems
 those functions sadly are final.

 Can anything useful be done to remedy the situation?
For the compiler to be able to make all opDispatch instantiations virtual, it would have to first see all calls that generate opDispatch instantiations. (Impossible in the presence of separate compilation.) Only then the compiler would know how large the vtbl of the base class should be and what member functions of the derived class are overrides of those virtual functions.
I suppose it's such administrative difficulties that led to D defining anything that might conceivably be overridden to be virtual, whether or not actually overridden.
The issue with templated functions is that you don't have the full list of instantiations when the base class is compiled, whereas as I understand it, the issue with virtual-by-default is more of an issue of choosing code correctness by default over efficiency by default. You can get some fun, subtle bugs in C++ when you call non-virtual functions in circumstances where they were overridden and used as if they were virtual. So, by making all public and protected class member functions virtual by default and not allowing non-virtual functions to be overridden, it prevents a whole class of bugs. It _does_ have some performance downsides in that it easily leads to functions being virtual when they don't need to be, which can be gotten around with some extra effort, but it's effectively favoring correctness by default over efficiency. And for a lot of programs, it's a great tradeoff, especially when the whole point of classes in D is to use inheritance and polymorphism, and the cases where you don't need it, you use a struct. For the rest, final can be used to devirtualize a function as long as it's not overriding a function. To avoid bugs in the same way that we currently do without having virtual be the default, we probably would have had to make it illegal to override any class functions unless they were explicitly virtual, which would have created a different set of problems. And whether that would be better or worse is a matter of debate. In the end, it's a matter of tradeoffs, and which is better is likely to depend on how your applications benefit from and use the feature. If most of your class functions are used polymorphically, then having to mark them as virtual or having to put virtual: at the top of your class would get really annoying, whereas if you typically have only a few virtual functions and then lots of non-virtual property functions (like the AAA guys apparently like to do), then having to mark things with final to devirtualize them is very annoying. There's no pleasing everyone. Either way, allowing overriding non-virtual functions like C++ does would just be begging for bugs. - Jonathan M Davis
Nov 18 2018