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

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

问题描述

我正在尝试构建一个自定义比较器,该比较器允许将比较功能分配给一个内部字段.为了简化比较器的创建,我尝试添加一个类似于构造函数的类函数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屋!

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