Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

E2009 Incompatible types: 'Parameter lists differ'

I get the following error:

E2009 Incompatible types: 'Parameter lists differ'

However I disagree, looking at the definitions I can see no difference.

Looks the same to me...

Here's the record definition:

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

And here's the Mod function I want to assign:

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

The following assignment issues the error:

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

What's wrong with my code?

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.
like image 263
Johan Avatar asked Oct 02 '13 21:10

Johan


1 Answers

First of all, some general advice. Your SSCCE is poor. It is neither short nor self-contained. This is actually rather important. Making the demonstration code as short as possible frequently helps you understand the problem. That is definitely the case here.

Here's my take on an 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.

This fails to compile with E2009. You can make it compile a number of ways. For instance, removing the data member results in successful compilation.

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.

In XE3 you can make it compile by adding the [ref] attribute to the parameter of the procedural type. To be explicit, this compiles in 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.

This gives us a strong clue as to what the compiler is doing. An undecorated const record parameter is passed either by value or by reference. If the record is small enough to fit into a register, it will be passed by value.

When the compiler is processing the record, it has not fully finalised the record's size. I'm guessing that internally in the compiler there is a variable containing the size of the record. Until the record's declaration is complete, I posit that this size variable is zero. So the compiler decides that the const parameter of record type will be passed by value in a register. When the procedure myproc is encountered, the record's true size is known. It does not fit in a register, and the compiler therefore recognises a mismatch. The type in the record receives its parameter by value, but that being offered for assignment passes the parameter by reference.

Indeed, you can remove the [ref] from the declaration of myproc and the program still compiles.

This also explains why you found that using a var parameter resulted in successful compilation. This obviously forces the parameter to be passed by reference.

If you can move to XE3 or later then the solution is obvious: use [ref] to force the compiler's hand.

If you cannot move to XE3 then perhaps an untyped const parameter is the best solution. This also forces the compiler to pass the parameter by reference.

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.

Regular readers of my postings here on Stack Overflow will know that I'm a big proponent of operator overloading on value type records. I use this feature extensively and it results in efficient and highly readable code. However, when you start pushing hard with more complex and interdependent types, the design and implementation breaks down.

The flaw highlighted in this question is one good example. It's really not uncommon to expect that the compiler could handle this. It's very reasonable to expect a type to be able to refer to itself.

Another example where the implementation lets the programmer down is when you wish to put a const of the record type in that record. For example, consider this type:

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

This fails to compile at the declaration of Zero with E2086 Type 'TComplex' is not yet completely defined.

Another limitation is the inability of type A to refer to type B, and vice versa. We can make forward declarations for classes, but not records. I understand that the compiler implementation would need to be modified to support this, but it's certainly possible to achieve.

And there's more. Why is it not possible to allow inheritance for records? I don't want polymorphism, I just want to inherit the data members and methods of the record. And I don't even need the is a behaviour that you get with classes. That is I don't mind if TDerivedRecord is not a TBaseRecord. All I want is to inherit members and functions to avoid duplication.

Sadly, to my mind, this is a feature that has been done 90% and is just missing the tender, loving care needed to take it to completion.

like image 58
David Heffernan Avatar answered Sep 29 '22 08:09

David Heffernan