5

我收到以下错误:

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

但是我不同意,看看定义我看不出有什么区别。

在我看来是一样的...

这是记录定义:

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

这是我要分配的 Mod 功能:

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

2 回答 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.

[ref]在 XE3 中,您可以通过将属性添加到过程类型的参数来使其编译。明确地说,这在 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遇到该过程时,记录的真实大小是已知的。它不适合寄存器,因此编译器会识别出不匹配。记录中的类型通过值接收其参数,但提供赋值的类型通过引用传递参数。

实际上,您可以[ref]从声明中删除myproc并且程序仍然可以编译。

这也解释了为什么您发现使用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,反之亦然。我们可以对类进行前向声明,但不能对记录进行声明。我知道需要修改编译器实现以支持这一点,但这当然是可以实现的。

还有更多。为什么不允许记录继承?我不想要多态,我只想继承记录的数据成员和方法。而且我什至不需要你在课堂上得到那种行为。那就是我不介意如果TDerivedRecord不是TBaseRecord。我想要的只是继承成员和函数以避免重复。

可悲的是,在我看来,这个功能已经完成了 90%,只是缺少完成它所需的温柔、关爱。

于 2013-10-03T08:20:19.200 回答
2

解决方法

如果我像这样更改代码:

记录定义

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

函数定义

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

问题也消失了。

请注意,如果我将其改varconst,问题会再次出现。

于 2013-10-02T22:47:09.197 回答