Markus Gerwinski wrote:
I just did a little experiment. The full source code is attached, but what I'm doing is, in brief, the following:
- Declaring an object type tTestObj with a method `foo',
- giving this method an attribute name,
- declaring (in the same source!) an external-declared function with the same attribute name and a pointer (for the object) as an additional parameter,
- assigning this method to a procedure variable,
- calling the variable with a pTestObj as a parameter.
Result: It works! :-) The method is executed as if it was "correctly" called via tTestObj.foo, including access to private fields of the object.
Question: Is this supposed to work? I.e. can I rely on this feature remaining in GPC? Or is it rather looked upon as a bug, so I have to expect it'll be removed some day?
I'd consider it perhaps a misfeature, but I think it will keep working, as long as you use explicit linker names (as you do), and take care of the parameter list ("Self" last, in contrast to BP where it's first, no difference in the example).
However, actually "Self" is a reference parameter of object type, not a value parameter ob object pointer type. Though both are implemented the same, and this is also unlikely to change, the example can easily be changed to avoid depending on this.
I'd like to use it as a feature, since it provides me with a way to assign methods to procedure/function variables, something I've been missing in GPC so far. OTOH, I would, of course, even prefer to have real, "syntax-approved"
Yes, the first problem is to define a syntax. I think there might have been some suggestions before, but nothing concrete so far AFAIR. Do other dialects have something like that, if so which syntax?
method variables that also take into account polymorphy and the fact that methods might be overridden.
Here's an alternative:
gFoo:= tFooFunc (@tTestObj.foo);
This avoids the linker name tricks, but requires a type-cast. It has the advantage that it works with (non-)overridden methods, i.e. you can put in a derived type of tTestObj, whether in inherits tTestObj.foo or defines its own foo. For a compiler-implemented solution, this issue would also be easily solved.
For polymorphy (i.e., virtual) methods, the problem is that a procedural variable needs a constant address. One way to solve it is to write a wrapper routine that does the actual virtual call. This solution (see below) doesn't even require a type-cast or other dirty stuff. It also works for non-virtual methods, of course, if you're willing to accept the small wrapper overhead (which seems inevitable for virtual methods, but can be avoided for non-virtual methods, as in your example).
For a compiler-implemented solution, polymorphy would also be an issue, and AFAICS, the compiler would need to emit such a wrapper routine automatically then. That might not be too difficult per se (just some work to do ...), but the important question is, when and where to emit such wrappers (looks a bit like the C++ template problems). We could emit one on each usage, at the danger of emitting the same one several times (might even be in different units). Or emit one for each virtual method, at the danger of emitting some (often many) that aren't used at all. Of course, it's not a huge issue (just a few bytes per routine), but since a manual solution is available, I'd first wonder if the effort is justified for a feature that might not be used all that often ...
Program Test;
type
pTestObj = ^tTestObj;
tTestObj = object public Constructor init ( aBar: integer ); Procedure foo; virtual; private fBar: integer; end (* tTestObj *);
p2 = ^t2;
t2 = object (tTestObj) Procedure foo; virtual; end (* tTestObj *);
tFooFunc = Procedure ( var obj: tTestObj );
var g1, g2: pTestObj; gFoo: tFooFunc;
Constructor tTestObj.init ( aBar: integer );
begin (* tTestObj.init *) fBar:= aBar; end (* tTestObj.init *);
Procedure tTestObj.foo;
begin (* tTestObj.foo *) writeln ( 'I''m one.' ); end (* tTestObj.foo *);
Procedure t2.foo;
begin (* tTestObj.foo *) writeln ( 'Me two.' ); end (* tTestObj.foo *);
Procedure FooWrapper ( var obj: tTestObj ); begin obj.foo end;
begin g1 := new ( pTestObj, init ( 42 ) ); g2 := new ( p2, init ( 42 ) ); gFoo:= FooWrapper; gFoo ( g1^ ); gFoo ( g2^ ); end.
Frank