为什么我不能将我的函数引用分配给匹配的变量?E2555 升高 [英] Why can't I assign my function reference to a matching variable? E2555 is raised

查看:23
本文介绍了为什么我不能将我的函数引用分配给匹配的变量?E2555 升高的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试构建一个自定义比较器,它允许将比较函数分配给内部字段.为了简化比较器的创建,我尝试添加一个类似构造函数的类函数 Construct 来初始化比较器.

现在,如果我尝试编译以下示例,编译器会显示

<块引用>

[dcc32 Fehler] ConsoleDemo1.dpr(37):无法跟踪 E2555 符号结果"

我有以下示例代码:

程序 ConsoleDemo1;{$APPTYPE 控制台}{$R *.res}用途泛型.集合,泛型.默认,System.SysUtils;类型TConstFunc<T1,T2,TResult>= 对函数的引用(const Arg1:T1;const Arg2:T2):TResult;TDemo = class(TComparer)私人的FVar: TConstFunc;function CompareInternal(const L, R: string): Integer;民众类函数构造():TDemo;函数比较(const L,R:字符串):整数;覆盖;结尾;function TDemo.Compare(const L, R: string): Integer;开始结果 := FVar(L, R);结尾;函数 TDemo.CompareInternal(const L, R: string): Integer;开始结果:= AnsiCompareStr(L, R);结尾;类函数 TDemo.Construct: TDemo;开始结果:= TDemo.Create();结果.FVar := Result.CompareInternal;结尾;结尾.

解决方案

我不认为这是一个错误.至关重要的是,您已经将 TConstFunc 定义为一种匿名方法类型.这些是托管的、引用计数的、非常特殊的类型,与常规对象方法完全不同.通过编译器魔法,它们通常是赋值兼容的,但有几个重要的警告.考虑更简洁:

program Project1;{$APPTYPE 控制台}类型TFoo = 程序参考;TDemo = 类私人的FFoo : TFoo;程序 Foo;民众类函数构造():TDemo;结尾;程序 TDemo.Foo;开始WriteLn('foo');结尾;类函数 TDemo.Construct: TDemo;开始结果:= TDemo.Create();结果.FFoo := result.foo;结尾;结尾.

这也会产生相同的编译器错误 (E2555).因为成员方法是一个procedure of object(对象方法)类型,而你将它赋值给一个reference to procedure(匿名方法)类型,这相当于(我怀疑编译器正在将其扩展为):

类函数TDemo.Construct:TDemo;开始结果:= TDemo.Create();result.FFoo := 过程开始结果.foo;结尾;结尾;

编译器不能直接分配方法引用(因为它们是不同的类型),因此(我猜)必须将它包装在一个匿名方法中,该方法隐含地要求捕获 result 变量.匿名方法无法捕获函数返回值,但是 - 只有局部变量可以.

在您的情况下(或者实际上,对于任何 function 类型),由于隐藏了 result 变量的匿名包装器,甚至无法表达等效项,但我们可以理论上想象一下:

类函数TDemo.Construct:TDemo;开始结果:= TDemo.Create();Result.FVar := function(const L, R : string) : 整数开始结果:= result.CompareInternal(L,R);//** 不能这样做结尾;结尾;

正如 David 所展示的,引入局部变量(可以捕获)是一种正确的解决方案.或者,如果您不需要 TConstFunc 类型是匿名的,您可以简单地将其声明为常规对象方法:

TConstFunc= function(const Arg1: T1; const Arg2: T2): TResult of object;

<小时><小时>

尝试捕获 result 失败的另一个示例:

program Project1;{$APPTYPE 控制台}类型TBar = 程序参考;TDemo = 类私人的FFoo : 整数;FBar : TBar;民众类函数构造():TDemo;结尾;类函数 TDemo.Construct: TDemo;开始结果:= TDemo.Create();结果.FFoo := 1;result.FBar := 过程开始WriteLn(result.FFoo);结尾;结尾;结尾.

<小时>

这不起作用的根本原因是方法的返回值实际上是一个var参数,而匿名闭包捕获的是变量,而不是强>.这是一个关键点.同样,这也是不允许的:

program Project1;{$APPTYPE 控制台}类型TFoo = 程序参考;TDemo = 类私人的FFoo : TFoo;过程栏(var x:整数);结尾;过程 TDemo.Bar(var x: Integer);开始FFoo := 过程开始写Ln(x);结尾;结尾;开始结尾.

<块引用>

[dcc32 错误] Project1.dpr(18): E2555 无法捕获符号x"

在引用类型的情况下,就像在原始示例中一样,您实际上只对捕获引用的感兴趣,而不是对包含它的变量感兴趣.这并不使其在语法上等效,并且编译器为此目的为您创建一个新变量是不合适的.

我们可以这样重写上面的,引入一个变量:

procedure TDemo.Bar(var x: Integer);无功y:整数;开始y := x;FFoo := 过程开始写Ln(y);结尾;结尾;

这是允许的,但预期的行为会大不相同.在捕获 x(不允许)的情况下,我们希望 FFoo 始终写入作为参数传入的任何变量的当前值 xBar,无论在此期间它可能在何处或何时发生了更改.我们还希望闭包即使在它脱离创建它的任何作用域之后也能使变量保持活动状态.

然而,在后一种情况下,我们期望 FFoo 输出 y 的值,即变量 x 的值这是最后一次调用Bar.

<小时>

回到第一个例子,考虑一下:

program Project1;{$APPTYPE 控制台}类型TFoo = 程序参考;TDemo = 类私人的FFoo : TFoo;FBar:字符串;程序 Foo;民众类函数构造():TDemo;结尾;程序 TDemo.Foo;开始WriteLn('foo' + FBar);结尾;类函数 TDemo.Construct: TDemo;无功LDemo : TDemo;开始结果:= TDemo.Create();LDemo := 结果;LDemo.FBar := 'bar';result.FFoo := LDemo.foo;LDemo := nil;结果.FFoo();//**访问冲突结尾;无功LDemo:TDemo;开始LDemo := TDemo.Construct;结尾.

这里很清楚:

result.FFoo := LDemo.foo;

我们没有为LDemo中存储的TDemo实例所属的方法foo分配正常引用code>,但实际上捕获了变量 LDemo 本身,而不是它当时包含的.之后将 LDemo 设置为 nil 自然会产生访问冲突,即使它在进行分配时引用的对象实例仍然存在.

这与我们简单地将TFoo定义为对象过程而不是对过程的引用完全不同的行为/代码>.如果我们这样做了,上面的代码会像人们天真地预期的那样工作(输出 foobar 到控制台).

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 Symbol 'Result' cannot be tracked

I have the following example-code:

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.

解决方案

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).

这篇关于为什么我不能将我的函数引用分配给匹配的变量?E2555 升高的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆