当前位置:  开发笔记 > 编程语言 > 正文

为什么我不能将我的函数引用分配给匹配的变量?E2555被提升了

如何解决《为什么我不能将我的函数引用分配给匹配的变量?E2555被提升了》经验,为你挑选了2个好方法。

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

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

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

我有以下示例代码:

program ConsoleDemo1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  Generics.Collections, Generics.Defaults,
  System.SysUtils;

type

  TConstFunc = reference to function(const Arg1: T1; const Arg2: T2): TResult;

  TDemo = class(TComparer)
  private
    FVar: TConstFunc;
    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.

J..... 12

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

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(匿名方法)类型,这相当于(并且我怀疑编译器正在扩展它):

class function TDemo.Construct: TDemo;
begin
  result := TDemo.Create();
  result.FFoo := procedure
                 begin
                   result.foo;
                 end;
end;

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

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

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匿名类型,则可以将其声明为常规对象方法:

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


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

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参数,而匿名闭包捕获变量而不是.这是一个关键点.同样,这也是不允许的:

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'

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

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

procedure TDemo.Bar(var x: Integer);
var
  y : integer;
begin
  y := x;
  FFoo := procedure
          begin
            WriteLn(y);
          end;
end;

这是允许的,但预期的行为将是非常不同的.在拍摄的情况下x(不允许),我们预期FFoo会一直写传递中作为参数的变数,当前值xBar时也可能在此期间已经改变,无论在哪里,或.我们也期望封闭即使在它变得无法创建它的任何范围之后仍然保持变量.

但是,在后一种情况下,我们希望FFoo输出值y,该值是变量的值,x因为它是最后一次Bar调用的值.


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

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;

我们没有为正在存储foo实例的方法分配正常引用,但实际上已经捕获了变量本身,而不是当时包含的.设置于后自然产生的访问冲突,甚至认为它称作时作出转让仍然活着的对象实例. TDemoLDemo LDemoLDemonil

这与我们简单定义为a 而不是a的行为截然不同.如果我们这样做了,上面的代码就像人们可能天真地期望的那样(输出到控制台). TFooprocedure of objectreference to procedurefoobar



1> J.....:

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

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(匿名方法)类型,这相当于(并且我怀疑编译器正在扩展它):

class function TDemo.Construct: TDemo;
begin
  result := TDemo.Create();
  result.FFoo := procedure
                 begin
                   result.foo;
                 end;
end;

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

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

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匿名类型,则可以将其声明为常规对象方法:

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


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

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参数,而匿名闭包捕获变量而不是.这是一个关键点.同样,这也是不允许的:

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'

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

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

procedure TDemo.Bar(var x: Integer);
var
  y : integer;
begin
  y := x;
  FFoo := procedure
          begin
            WriteLn(y);
          end;
end;

这是允许的,但预期的行为将是非常不同的.在拍摄的情况下x(不允许),我们预期FFoo会一直写传递中作为参数的变数,当前值xBar时也可能在此期间已经改变,无论在哪里,或.我们也期望封闭即使在它变得无法创建它的任何范围之后仍然保持变量.

但是,在后一种情况下,我们希望FFoo输出值y,该值是变量的值,x因为它是最后一次Bar调用的值.


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

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;

我们没有为正在存储foo实例的方法分配正常引用,但实际上已经捕获了变量本身,而不是当时包含的.设置于后自然产生的访问冲突,甚至认为它称作时作出转让仍然活着的对象实例. TDemoLDemo LDemoLDemonil

这与我们简单定义为a 而不是a的行为截然不同.如果我们这样做了,上面的代码就像人们可能天真地期望的那样(输出到控制台). TFooprocedure of objectreference to procedurefoobar


我做.我想你已经解释了限制背后的实现细节,你做得非常好.你应该得到的不仅仅是我的唯一投票.但是,这是一个实现细节.这是一种对象的方法.没有歧义.应该没有变量捕获.编译器应该这样做.

2> David Heffer..:

我的英文Delphi上的编译器错误如下:

[dcc32错误] E2555无法捕获符号'结果'

这是由于设计有缺陷.根本没有理由在这里进行任何变量捕获.赋值的右侧是实例方法而不是匿名方法.但是编译器通过将方法包装在匿名方法中来处理它.编译器进行翻译

Result.FVar := Result.CompareInternal;

Result.FVar := 
  function(const Arg1, Arg2: string): Integer
  begin
    InnerResult := OuterResult.CompareInternal(Arg1, Arg2);
  end;

抛开对两个单独的结果变量的混淆,编译器拒绝这一点,因为外部结果变量不是本地变量,它是一个var参数.因此无法捕获.

但在我看来,整个设计是错误的.不需要任何变量捕获.当你写作时,Result.CompareInternal你打算引用一种常规of object方法.通过更好的设计,编译器将允许此分配而无需创建匿名方法.

您可以像这样解决问题:

class function TDemo.Construct: TDemo;
var
  Demo: TDemo;
begin
  Demo := TDemo.Create();
  Demo.FVar := Demo.CompareInternal;
  Result := Demo;
end;

这里Demo可以捕获局部变量.

或者像我建议的那样:

program ConsoleDemo1;

{$APPTYPE CONSOLE}

uses
  Generics.Defaults,
  System.SysUtils;

type
  TConstFunc = reference to function(const Arg1: T1; 
    const Arg2: T2): TResult;

  TDemo = class(TComparer)
  private
    FVar: TConstFunc;
    function CompareInternal(const L, R: string): Integer;
  public
    constructor Create;
    function Compare(const L, R: string): Integer; override;
  end;

constructor TDemo.Create;
begin
  inherited;
  FVar := CompareInternal;
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;

end.


没关系.顺便说一句,真棒MCVE.自动向我投票给像这样的MCVE.如果只是提出这样的问题!!
FWIW我在XE4上解决了这个问题:`class function TDemo.Construct:TDemo; 开始结果:= TDemo.Create; Result.AssignInternalComparer; 结束;`和`程序TDemo.AssignInternalComparer; 开始FVar:= CompareInternal; 结束;`
推荐阅读
ERIK又
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有