E2009 不兼容的类型:'参数列表不同'

5

我看到以下错误:

E2009 不兼容类型:'参数列表不同'

然而我不同意,从定义上看我没有发现任何差异。

在我看来是一样的...

这是记录定义:

type
  TFastDiv = record
  private
    ...
    DivideFunction: function (const Buffer: TFastDiv; x: integer): integer;

以下是我想要分配的模数函数:

function dividefixedi32(const Buffer: TFastDiv; x: integer): integer;
asm

以下的任务出现了错误:
class operator TFastDiv.Implicit(a: integer): TFastDiv;
begin
  if (a = 0) then begin 
    raise EDivByZero.Create('Setting a zero divider is a division by zero error') 
      at ReturnAddress; 
  end;
  Result.FSign:= Math.sign(a);
  case Result.FSign of
    -1: begin
      SetDivisorI32(Result, a);
      Result.DivideFunction:= dividefixedi32;  <<-- error E2009

我的代码有什么问题?

SSCCE

(简单自给的完整性测试)
unit SSCCE;

interface

uses Math;

type
  TFastDiv = record
  private
    FBuffer: UInt64; // The reciprocal of the divider
    FDivider: integer; // The divider itself (need with modulus etc).
    FSign: TValueSign;
    DivideFunction: function (const Buffer: TFastDiv; x: integer): integer;
    ModFunction: function (const Buffer: TFastDiv; x: integer): integer;
  public
    class operator Implicit(a: integer): TFastDiv;
  end;


implementation

uses SysUtils;

function dividefixedi32(const Buffer: TFastDiv; x: integer): integer; forward;

class operator TFastDiv.Implicit(a: integer): TFastDiv;
begin
  if (a = 0) then begin raise EDivByZero.Create('Setting a zero divider is a division by zero error') at ReturnAddress; end;
  Result.FSign:= Math.sign(a);
  case Result.FSign of
    -1: begin
      //SetDivisorI32(Result, a);
      Result.DivideFunction:= dividefixedi32;
     end; {-1:}
    1: begin
      //SetDivisorU32(Result.FBuffer, a);
    end; {1:}
  end; {case}
  Result.FDivider:= a;
end;

function dividefixedi32(const Buffer: TFastDiv; x: integer): integer;
asm
  mov     eax, edx
  mov     r8d, edx               // x
  mov     r9, rcx                // Buffer
  imul    dword [r9]             // m
  lea     eax, [rdx+r8]          // r8 = r8 or rsi
  mov     ecx, [r9+4]            // shift count
  sar     eax, cl
  sar     r8d, 31                // sign(x)
  sub     eax, r8d
  ret
end;

end.

1
当我尝试时,它对我来说运行良好。没有看到SSCCE,我的猜测是可能有多个TFastDiv类型在范围内相互冲突。 - Remy Lebeau
没有TFastDiv记录定义(在文件中查找确认)。 - Johan
@RemyLebeau,我已经添加了一个SSCCE。 - Johan
2个回答

6

首先,一些通用的建议。你的SSCCE很差。它既不短,也不自包含。这实际上非常重要。尽可能地使演示代码变得简短经常有助于你理解问题。在这里,这绝对是正确的。

以下是我对一个SSCCE的看法:

program soq19147523_version1;

type
  TRecord = record
    data: Integer;
    proc: procedure(const rec: TRecord);
  end;

procedure myproc(const rec: TRecord);
begin
end;

procedure foo;
var
  rec: TRecord;
begin
  rec.proc := myproc; // fail, E2009
end;

begin
end.

这段代码无法通过E2009编译。您可以采取多种方式使其编译成功。例如,删除data成员将导致编译成功。

program soq19147523_version2;

type
  TRecord = record
    proc: procedure(const rec: TRecord);
  end;

procedure myproc(const rec: TRecord);
begin
end;

procedure foo;
var
  rec: TRecord;
begin
  rec.proc := myproc; // compiles
end;

begin
end.

在XE3中,您可以通过将[ref]属性添加到过程类型的参数中使其编译。明确地说,在XE3中,这样编译是可以的:
program soq19147523_version3;

type
  TRecord = record
    data: Integer;
    proc: procedure(const [ref] rec: TRecord);
  end;

procedure myproc(const [ref] rec: TRecord);
begin
end;

procedure foo;
var
  rec: TRecord;
begin
  rec.proc := myproc; // compiles in XE3, no [ref] in XE2
end;

begin
end.

这使我们对编译器的工作有了一个强烈的线索。未被装饰的const记录参数会通过值或引用传递。如果记录足够小,可以放入寄存器中,则会通过值传递。
当编译器处理记录时,它尚未完全确定记录的大小。我猜测在编译器内部有一个变量包含记录的大小。在记录的声明完成之前,我认为这个大小变量为零。因此,编译器决定通过寄存器按值传递记录类型的const参数。当遇到过程myproc时,记录的真实大小已知。它不适合寄存器,因此编译器识别到了一个不匹配。记录中的类型通过值接收其参数,但提供分配的参数通过引用传递。
实际上,您可以从myproc的声明中删除[ref],程序仍然可以编译。
这也解释了为什么使用var参数会导致成功编译。这显然强制参数通过引用传递。
如果您可以转移到XE3或更高版本,则解决方案很明显:使用[ref]来强制编译器的操作。
如果您无法升级到XE3,则可能是未类型化的const参数是最佳解决方案。这也强制编译器通过引用传递参数。
program soq19147523_version4;

type
  TRecord = record
    data: Integer;
    proc: procedure(const rec{: TRecord});
  end;

procedure myproc(const rec{: TRecord});
begin
  Writeln(TRecord(rec).data);
end;

procedure foo;
var
  rec: TRecord;
begin
  rec.proc := myproc;
end;

begin
end.

我的Stack Overflow帖子的常读者会知道,我非常支持在值类型记录上进行运算符重载。我广泛使用此功能,这导致了高效且易于阅读的代码。但是,当您开始使用更复杂和相互依赖的类型时,设计和实现就会崩溃。
这个问题中突出的缺陷是一个很好的例子。期望编译器能够处理这个问题并不罕见。期望类型能够引用自身是非常合理的。
另一个示例是当您希望将记录类型的const放入该记录时,实现让程序员失望。例如,考虑以下类型:
type
  TComplex = record
  public
    R, I: Double;
  const
    Zero: TComplex = (R: 0.0, I: 0.0);
  end;

在声明Zero时,编译失败,并出现E2086类型“TComplex”尚未完全定义的错误。

另一个限制是类型A无法引用类型B,反之亦然。我们可以为类进行前向声明,但不能为记录进行前向声明。我理解编译器实现需要进行修改才能支持此功能,但肯定可以实现。

还有更多。为什么记录不允许继承?我不需要多态性,只想继承记录的数据成员和方法。而且我甚至不需要类所具有的“is a”行为。也就是说,如果TDerivedRecord不是TBaseRecord,我也不介意。我只想继承成员和函数以避免重复。

可悲的是,在我看来,这是一个完成了90%但缺少精心呵护的功能。


这解释得非常清楚。Remy的回答让我误入歧途,认为区别在于“程序”与“单元”,而实际上是在于“数据成员”与“无数据成员”。你刚刚让我开心了一整天 :-|),谢谢。 - Johan
似乎也可以使用未定义类型的赋值rec.proc := @myproc({$T-})。编译器可能不太喜欢它,有时会导致内部错误。 - Sertac Akyuz
@SertacAkyuz 我想知道当你这样做时编译器是通过引用还是值传递。 - David Heffernan
@SertacAkyuz 看起来生成的代码没问题。这似乎是一个不错的解决方法。相信你会找到好用 @ 和过程类型的方法,对吧?!-;) - David Heffernan

2

解决方法

如果我像这样更改代码:

记录定义

type
  TFastDiv = record
  private
    ...
    DivideFunction: function (var Buffer: TFastDiv; x: cardinal): cardinal;
                              ^^^

函数定义

function dividefixedu32(var Buffer: TFastDiv; x: Cardinal): cardinal; // unsigned
asm  //                 ^^^  

问题也顺利解决了。
请注意,如果我将var改回const,问题就会再次出现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接