为什么我不能将函数引用分配给匹配的变量? E2555被举起 [英] Why can't I assign my function reference to a matching variable? E2555 is raised
问题描述
我正在尝试构建一个自定义比较器,该比较器允许将比较功能分配给一个内部字段.为了简化比较器的创建,我尝试添加一个类似于构造函数的类函数Construct
,该函数初始化比较器.
I'm trying to build an custom comparer which allows the assignment of the comparison function to an internal field. In order to ease the creation of the comparer, I tried to add a constructor-like class function Construct
which initializes the comparer.
现在,如果我尝试编译以下示例,则编译器将显示
Now if I try to compile the following example, the compiler displays
[dcc32 Fehler] ConsoleDemo1.dpr(37):无法跟踪E2555符号结果"
[dcc32 Fehler] ConsoleDemo1.dpr(37): E2555 Symbol 'Result' cannot be tracked
我有以下示例代码:
program ConsoleDemo1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Generics.Collections, Generics.Defaults,
System.SysUtils;
type
TConstFunc<T1, T2, TResult> = reference to function(const Arg1: T1; const Arg2: T2): TResult;
TDemo = class(TComparer<string>)
private
FVar: TConstFunc<string, string, Integer>;
function CompareInternal(const L, R: string): Integer;
public
class function Construct(): TDemo;
function Compare(const L, R: string): Integer; override;
end;
function TDemo.Compare(const L, R: string): Integer;
begin
Result := FVar(L, R);
end;
function TDemo.CompareInternal(const L, R: string): Integer;
begin
Result := AnsiCompareStr(L, R);
end;
class function TDemo.Construct: TDemo;
begin
Result := TDemo.Create();
Result.FVar := Result.CompareInternal;
end;
end.
推荐答案
我不认为这是一个错误.至关重要的是,您已经将TConstFunc
定义为匿名方法类型.这些是托管的,引用计数的,非常特殊的类型,与常规对象方法完全不同.用编译器魔术来说,它们通常是赋值兼容的,但是有几个重要的警告.考虑更简洁:
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.
这也会产生相同的编译器错误(E2555).因为成员方法是procedure of object
(对象方法)类型,并且您将其分配给reference to procedure
(匿名方法)类型,所以这等效于(而且我怀疑编译器会将其扩展为):>
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;
编译器无法直接分配方法引用(由于它们的类型不同),因此(我想)必须将其包装在匿名方法中,这隐式要求捕获result
变量. 函数返回值不能用匿名方法捕获,但是-只有局部变量可以.
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.
在您的情况下(或者实际上,对于任何function
类型),由于匿名包装器隐藏了result
变量,所以甚至无法表示等效项,但是理论上我们可以想象到: >
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;
如David所示,引入局部变量(可以捕获)是一种正确的解决方案.另外,如果不需要TConstFunc
类型匿名,则只需将其声明为常规对象方法即可:
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;
另一个尝试捕获result
失败的示例:
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.
不起作用的根本原因是因为方法的返回值实际上是一个var
参数,并且匿名闭包捕获了变量,而不是值.这是一个关键点.同样,这也是不允许的:
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错误] Project1.dpr(18):E2555无法捕获符号'x'
[dcc32 Error] Project1.dpr(18): E2555 Cannot capture symbol 'x'
在引用类型的情况下(如原始示例一样),您实际上只对捕获引用的 value 感兴趣,而对包含引用的 variable 感兴趣. .这并不能使其在语法上等效,并且为此目的,编译器也不适合为您创建一个新变量.
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;
这是允许的,但是预期的行为将有很大的不同.在捕获x
的情况下(不允许),我们希望FFoo
将始终将作为参数x
传入的任何变量的当前值写入到Bar
中,无论它在何处或何时具有在此期间进行了更改.我们还希望闭包即使在超出其创建范围的范围内也能使变量保持活动状态.
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.
但是,在后一种情况下,我们希望FFoo
输出值y
,它是变量x
的值,因为它是上次调用Bar
时的 .
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.
这里很明显:
result.FFoo := LDemo.foo;
我们尚未为存储在LDemo
中的TDemo
的实例的方法foo
分配正常引用,但实际上已捕获了变量 LDemo
本身,而不是当时包含的值.之后将LDemo
设置为nil
自然会导致访问冲突,甚至认为在进行分配时它引用的对象实例仍然有效.
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.
与我们简单地将TFoo
定义为procedure of object
而不是reference to procedure
的行为相比,这是根本的行为.如果我们做到了这一点,上面的代码就会像天真地期望的那样工作(将输出foobar
输出到控制台).
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).
这篇关于为什么我不能将函数引用分配给匹配的变量? E2555被举起的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!