tags:

views:

108

answers:

2

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?

+10  A: 

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();
Mason Wheeler
I was unsure what he wanted to do here. Did he want to store a reference to Queue.Dequeue so he could invoke Queue.Dequeue later, or did he want to invoke Queue.Dequeue now, and return the result and store it into myproc?
Warren P
Well, considering that queue is a queue of TProc, and MyProc is a variable of type TProc, it should be clear that the intent is to grab the return value.
Mason Wheeler
+1 can't believe that that simple idea didn't come to my mind :) I'm not sure about what you say about the pointer though. In my test app, this works perfectly. `Pointer (Queue.Dequeue)` seems to be the address of the procedure and not the address of the `Dequeue` method.
Smasher
Interesting. Just tested it and it does work. Not what I'd have expected, but OK...
Mason Wheeler
Warren, it was clear to me that he didn't want to assign the address of `Dequeue` to the variable since it doesn't have the right type. The method's type is `function: TProc of object`, not `procedure of object`, so I'm a little puzzled why there was any error in the first place. Smasher, to get the address of the method, you'd use `Addr()`, not `Pointer()`, although we usually spell it `@()`.
Rob Kennedy
But is tmethod(tproc).data copied correctly in the way of the original post (pointer())? Or am I misunderstanding something here?
Marco van de Voort
@Marco: You're misunderstanding something here. An anonymous method reference isn't a method pointer, it's an interface reference to a secret interface generated by the compiler, with only one method on it beyond the standard IUnknown stuff, which matches the signature of the anonymous method.
Mason Wheeler
+4  A: 

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

Barry Kelly
+1 Thanks! Always nice to hear the reasons why something is done in the way it is done.
Smasher