Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieve TProc from Generic Container

Tags:

delphi

Just discovered something rather funny:

var
  Queue  : TQueue <TProc>;
  MyProc : TProc;
...
MyProc := Queue.Dequeue;

I think you see what is intendend here. However, the compiler thinks I want to store the Queue.Dequeue method (type "procedure of object") in MyProc and reports an error

E2010 Incompatible Types: 'TProc' und 'Procedure of object'

The workaround I came up with goes like this

MyProc := TProc (Pointer (Queue.Dequeue));

Is there a more elegant solution?

like image 724
jpfollenius Avatar asked Feb 17 '10 13:02

jpfollenius


2 Answers

There's a bit of syntactical ambiguity there about whether the name "Dequeue" refers to the function itself, or the function's return value. And since you're dealing with an anonymous method pointer which you can assign a normal function to, it's trying to interpret that as a function assignment, not a function result assignment. Casting it to a pointer is the wrong solution, as that would force the function assignment to go through, which would then cause all sorts of fun errors when you attempt to invoke MyProc.

The correct way to fix it is by removing the syntactical ambiguity. Put an empty parenthesis after Dequeue, so that the compiler is sure that you're calling the function and not simply referencing it by name, and then it'll work.

MyProc := Queue.Dequeue();
like image 141
Mason Wheeler Avatar answered Oct 21 '22 07:10

Mason Wheeler


As Mason said, there's an ambiguity in the Delphi syntax. Where TFoo.Bar is a method, it's not clear that FooValue.Bar means to refer to the result of calling TFoo.Bar, or a method pointer (or reference) TFoo.Bar itself (with implied Self argument of FooValue).

In the comments of Mason's answer, Rob Kennedy seems to suggest that the compiler simply figure this out based on the types of everything involved. This isn't simple; the compiler already does a lot of work to figure out whether you mean to refer to a method pointer value or a method call. It actually parses expressions in a different way when the expected receiver is a method pointer (or reference, or function pointer) type. The effort is especially involved when overloads are brought into the picture: the compiler scans through every overload candidate and checks for method pointer types in every parameter position, and then parses arguments differently depending on whether or not that parameter position contains a function pointer in one of the overloads. Then, if an overload that expects a function pointer isn't matched, the compiler changes the parse tree from function pointer to method call. The overloading mechanism itself needs to figure out which to use when its doing value to parameter comparisons. It's pretty messy, and it would be great if we didn't make it messier.

A prefix-style operator like @ or Addr() isn't much help in resolving this ambiguity, not least because functions may return function pointers, and so on; how many @ do you need to inhibit implicit (no () necessary) calling to grab the right value out? So when anonymous methods were introduced, a change in the expression parsing was made: I introduced the possibility of using () to force an invocation.

You can read more about it here:

http://blog.barrkel.com/2008/03/odd-corner-of-delphi-procedural.html

and here:

http://blog.barrkel.com/2008/03/procedurally-typed-expressions-redux.html

like image 43
Barry Kelly Avatar answered Oct 21 '22 08:10

Barry Kelly