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 での私の投稿の定期的な読者は、私が値型レコードに対する演算子のオーバーロードの大きな支持者であることを知っているでしょう。私はこの機能を広範囲に使用しており、効率的で読みやすいコードが得られます。ただし、より複雑で相互に依存する型を強く推し始めると、設計と実装が崩壊します。

この質問で強調されている欠陥は、1 つの良い例です。コンパイラがこれを処理できると期待することは、本当に珍しいことではありません。型が自分自身を参照できると期待するのは非常に合理的です。

const実装がプログラマーを失望させるもう 1 つの例は、そのレコードにレコード タイプのを入れたい場合です。たとえば、次のタイプを考えてみましょう。

type
  TComplex = record
  public
    R, I: Double;
  const
    Zero: TComplex = (R: 0.0, I: 0.0);
  end;

Zeroこれは、E2086 タイプ 'TComplex' がまだ完全に定義されていない の宣言でコンパイルに失敗します。

もう 1 つの制限は、タイプ A がタイプ B を参照できないことです。クラスの前方宣言はできますが、レコードはできません。これをサポートするには、コンパイラの実装を変更する必要があることは理解していますが、達成することは確かに可能です。

そして、もっとあります。レコードの継承を許可できないのはなぜですか? ポリモーフィズムは必要ありません。レコードのデータ メンバーとメソッドを継承したいだけです。そして、クラスで得られる動作である必要さえありません。でなくても構いませTDerivedRecordTBaseRecord。私が望むのは、重複を避けるためにメンバーと関数を継承することだけです。

悲しいことに、私の考えでは、これは 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  //                 ^^^  

問題も解消されます。

varバックを変更するとconst、問題が再発することに注意してください。

于 2013-10-02T22:47:09.197 に答える