I don't think that this is a bug. Critically, you've defined TConstFunc
as an anonymous method type. These are managed, reference counted, very special types that are quite different from regular object methods. By compiler magic they are usually assignment compatible, but with several important caveats. Consider the more concise :
program Project1;
{$APPTYPE CONSOLE}
type
TFoo = reference to procedure;
TDemo = class
private
FFoo : TFoo;
procedure Foo;
public
class function Construct(): TDemo;
end;
procedure TDemo.Foo;
begin
WriteLn('foo');
end;
class function TDemo.Construct: TDemo;
begin
result := TDemo.Create();
result.FFoo := result.foo;
end;
end.
This also produces the same compiler error (E2555). Because the member method is a procedure of object
(object method) type, and you are assigning it to a reference to procedure
(anonymous method) type, this is equivalent to (and I suspect that the compiler is expanding this as) :
class function TDemo.Construct: TDemo;
begin
result := TDemo.Create();
result.FFoo := procedure
begin
result.foo;
end;
end;
The compiler cannot assign the method reference directly (since they are of different types), and therefore (I guess) has to wrap it in an anonymous method which is implicitly requiring capture of the result
variable. Function return values cannot be captured by anonymous methods, however - only local variables can.
In your case (or, indeed, for any function
type), the equivalent cannot even be expressed due to the anonymous wrapper hiding the result
variable, but we can imagine the same in theory as:
class function TDemo.Construct: TDemo;
begin
Result := TDemo.Create();
Result.FVar := function(const L, R : string) : integer
begin
result := result.CompareInternal(L,R); // ** can't do this
end;
end;
As David has shown, introducing a local variable (which can be captured) is one correct solution. Alternatively, if you don't need the TConstFunc
type to be anonymous, you can simply declare it as a regular object method :
TConstFunc<T1, T2, TResult> = function(const Arg1: T1; const Arg2: T2): TResult of object;
Another example where attempting to capture result
fails :
program Project1;
{$APPTYPE CONSOLE}
type
TBar = reference to procedure;
TDemo = class
private
FFoo : Integer;
FBar : TBar;
public
class function Construct(): TDemo;
end;
class function TDemo.Construct: TDemo;
begin
result := TDemo.Create();
result.FFoo := 1;
result.FBar := procedure
begin
WriteLn(result.FFoo);
end;
end;
end.
The fundamental reason why this does not work is because a method's return value is effectively a var
parameter and the anonymous closure captures variables, not values. This is a critical point. Similarly, this is also not allowed :
program Project1;
{$APPTYPE CONSOLE}
type
TFoo = reference to procedure;
TDemo = class
private
FFoo : TFoo;
procedure Bar(var x : integer);
end;
procedure TDemo.Bar(var x: Integer);
begin
FFoo := procedure
begin
WriteLn(x);
end;
end;
begin
end.
[dcc32 Error] Project1.dpr(18): E2555 Cannot capture symbol 'x'
In the case of a reference type, as in the original example, you really are only interested in capturing the value of the reference and not the variable that contains it. This does not make it syntactically equivalent and it would not be proper for the compiler to create a new variable for you for this purpose.
We could rewrite the above as this, introducing a variable :
procedure TDemo.Bar(var x: Integer);
var
y : integer;
begin
y := x;
FFoo := procedure
begin
WriteLn(y);
end;
end;
And this is allowed, but the expected behaviour would be very different. In the case of capturing x
(not allowed), we would expect that FFoo
would always write the current value of whatever variable was passed in as argument x
to Bar
, regardless of where or when it may have been changed in the interim. We would also expect that the closure would keep the variable alive even after it fell out of whatever scope created it.
In the latter case, however, we expect FFoo
to output the value of y
, which is the value of the variable x
as it was the last time Bar
was called.
Returning to the first example, consider this :
program Project1;
{$APPTYPE CONSOLE}
type
TFoo = reference to procedure;
TDemo = class
private
FFoo : TFoo;
FBar : string;
procedure Foo;
public
class function Construct(): TDemo;
end;
procedure TDemo.Foo;
begin
WriteLn('foo' + FBar);
end;
class function TDemo.Construct: TDemo;
var
LDemo : TDemo;
begin
result := TDemo.Create();
LDemo := result;
LDemo.FBar := 'bar';
result.FFoo := LDemo.foo;
LDemo := nil;
result.FFoo(); // **access violation
end;
var
LDemo:TDemo;
begin
LDemo := TDemo.Construct;
end.
Here it is clear with :
result.FFoo := LDemo.foo;
that we have not assigned a normal reference to the method foo
beloning to the instance of TDemo
stored in LDemo
, but have actually captured the variable LDemo
itself, not the value it contained at the time. Setting LDemo
to nil
afterwards naturally produces an access violation, even thought the object instance it referred to when the assignment was made is still alive.
This is radically different behaviour than if we simply defined TFoo
as a procedure of object
instead of a reference to procedure
. Had we done that instead, the above code works as one might naively expect (output foobar
to the console).
TConstFunc<T1, T2, TResult> = function(const Arg1: T1; const Arg2: T2): TResult of object;
, the code compiles and works. – Basilica