Today I tried to reproduce this bug using the simplest example I could make.
I started with a basic record (TBasicRecord) having only simple set and print methods (no operators), and there was no problem passing const x:TBasicBecord.
I then added a unary operator thinking that would trigger the bug, but still no problems in passing the record as const.
I then added a binary operator, but and still the bug wouldn't surface.
Finally I noticed in my simple example I had declared the data fields ahead of the method fields, and this turned out to be all that's required to mute the bug.
I'd also made my data fields private, so at first I thought that must be the issue, but in the end it turned out to be irrelevant. The only thing that makes a difference is whether of not I placed the data fields before the operator and method fields.
Overall I'm happy with this resolution. Personally I've always put the data fields first anyway. It's funny that doing it the other way around didn't seem to cause any other problems, just as long as you don't try to pass the record type as a "const" parameter anywhere.
Original Posting:
Previously I have been using Delphi 7 but today installed Delphi 2006 to gain access to operator methods that D7 didn't support.
I was attempting to compile the code (complex number implementation) listed in one of the replies to an earlier question here: Request simple example of how to a TComplexMath class (source included)
Here's a partial listing of the relevant code:
type
TComplex = record
public
class operator Implicit(const D: Double): TComplex;
class operator Negative(const C: TComplex): TComplex;
class operator Equal(const C1, C2: TComplex): Boolean;
class operator NotEqual(const C1, C2: TComplex): Boolean;
class operator Add(const C1, C2: TComplex): TComplex;
class operator Add(const C: TComplex; const D: Double): TComplex;
class operator Add(const D: Double; const C: TComplex): TComplex;
class operator Subtract(const C1, C2: TComplex): TComplex;
class operator Subtract(const C: TComplex; const D: Double): TComplex;
class operator Subtract(const D: Double; const C: TComplex): TComplex;
class operator Multiply(const C1, C2: TComplex): TComplex;
class operator Multiply(const C: TComplex; const D: Double): TComplex;
class operator Multiply(const D: Double; const C: TComplex): TComplex;
class operator Divide(const C1, C2: TComplex): TComplex;
class operator Divide(const C: TComplex; const D: Double): TComplex;
class operator Divide(const D: Double; const C: TComplex): TComplex;
function IsZero: Boolean;
function IsNonZero: Boolean;
function Conj: TComplex;
function Sqr: TComplex;
function Sqrt: TComplex;
function Mag: Double;
function SqrMag: Double;
public
r: Double;
c: Double;
end;
class operator TComplex.Negative(const C: TComplex): TComplex;
begin
Result.r := -C.r;
Result.c := -C.c;
end;
---- etc ---
The problem is, when I try to compile this code (in D2006), every operator that takes a TComplex type gives an error of E2037: Declaration of "----" differs from the previous declaration. (where "---" is the operator name).
My work around was to just remove the const keyword from every TComplex parameter and then the code complies (and runs) correctly. I can keep the "const x: Double" parameters,the compiler gives no error on those, but I had to remove "const" from all of the others.
Does anyone know if this is some compiler option that's not enabled? Or is this something supported in later versions of Delphi but not D2006? Or just me doing something else incorrectly?
Also, if I cant use const parameters here, would there be any advantage to just substituting var for const (compared to just deleting the const keyword altogether).
You should not replace const
by var
. Let me explain why.
function Add(a: integer): integer;
begin
result := a + 5;
end;
returns its argument + 5. Try ShowMessage(IntToStr(Add(10)))
. You can also do a := 10; ShowMessage(IntToStr(Add(a)))
to get the same result. In both cases, the thing passed to the function Add
is the number 10
.The message shows 15
.
The intended use of var
parameters is like this:
procedure Add(var a: integer);
begin
a := a + 5;
end;
var
indicates that the argument variable should be passed by reference; that is, only a pointer to the argument variable should be passed to the procedure/function.
Hence, now you can do
a := 10;
Add(a);
ShowMessage(IntToStr(a)); // You get 15
Now you cannot even do Add(10)
, since 10
isn't a variable at all!
To compare,
function Add(a: integer): integer;
begin
a := a + 5;
result := a;
end;
will not affect a
. So,
a := 10;
ShowMessage(IntToStr(Add(a))); // You get 15
ShowMessage(IntToStr(a)); // You get 10
Now, consider this horrible function:
function Add(var a: integer): integer;
begin
a := a + 5;
result := a;
end;
This will also return its argument + 5, but it will also affect its argument (very unexpextedly!!), and you cannot pass anything but variables as arguments (so Add(10)
won't work!!)!
a := 10;
ShowMessage(IntToStr(Add(a))); // You get 15
ShowMessage(IntToStr(a)); // You get 15 (!!!)
So, what is const
? Well, const
roughly means "pass by reference if possible (to speed up; for instance, you need not make a copy of a large record), but do never accept any changes to the argument". Hence, a const
argument effectively works as normal argument except that you cannot change it:
function Add(const a: integer): integer;
begin
result := a + 5;
end;
works while
function Add(const a: integer): integer;
begin
a := a + 5;
result := a;
end;
doesn't even compile! But you can still do Add(10)
.
From this discussion, it should be clear that you shouldn't replace const
by var
. Indeed,
const
to var
, your functions no longer accept arguments that are literals (10
) or expressions (Tag + 30
or SomeFunc(a, b)
). This is a major show-stopper!
Example of first point. Using const
or normal arguments:
function Complex(a, b: real): TComplex;
begin
result.r := a;
result.c := b;
end;
...
var
c, d: TComplex;
begin
d := -c; // Works!
d := -Complex(10, 20); // Works!
But using var
:
var
c, d: TComplex;
begin
d := -c; // Works!
d := -Complex(10, 20); // [DCC Error] Unit5.pas(262):
// E2015 Operator not applicable to this
// operand type
This will not work either (with var
):
var
a, b, c: TComplex;
begin
a := -(b + c);
Indeed, now the argument of Negative
isn't a variable, but the expression b + c
. So you lose very much!
Example of second point. Say you have a bad day and you suckify the implementation of Negative
to
class operator TComplex.Negative(var C: TComplex): TComplex;
begin
C.r := -C.r;
C.c := -C.c;
result := C;
end;
then the following code,
var
c, d: TComplex;
begin
c := Complex(10, 20);
d := -c;
ShowMessage(FloatToStr(c.r));
ShowMessage(FloatToStr(d.r));
which used to result in messages 10
and -10
, will suddenly change and yield -10
, -10
, which is highly unexpected!
The solution in your case, therefore, is simply to remove const
altogether (and NOT replace it by var
!).
Don't replace const with var in operator overloads. Period.
Even if you promise to never modify the var param inside the bodies of your functions (a dubious basis to begin with), just the presence of var params will destroy a very important aspect of operator functions: composition of expressions. A var param in an operator function makes it impossible to compose that operator together with other operators in compound expressions, because function results cannot be passed into var params.
Example: (A + B) * C
.
If A, B, and C are all TComplex type, then this compiles down to TComplex.Multiply(TComplex.Add(A, B), C)
. If TComplex.Multiply is declared with var params, the function result of Add cannot be passed into Multiply (because a function result is an intermediate value, not a variable that lives at a specific memory address), which means a simple math expression like (A + B) * C will not compile.
So, if you want your operators to be usable in compound expressions, don't use var params in your operator functions.
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