I get the following error:
E2009 Incompatible types: 'Parameter lists differ'
However I disagree, looking at the definitions I can see no difference.
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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With