7
votes

UPDATE: Simple workaround. Data fields before Method/Operator fields.

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

2
That looks like a compiler bug. I'd avoid using var. How would that help?David Heffernan
@jachguate: I really disagree. var forces the argument to be a variable, for one thing...Andreas Rejbrand
@jachguate But then the function can modify it. So pass by value is semantically better than by variable reference. Knowing the back story here, and being the author of the code in question, I disagree with you.David Heffernan
While this is a complete aside, I really like Math387 for handling complex numbers. It's a free download for Delphi from DewResearch and it is really fast (as far as x87 asm code goes). I've done a few specific algorithms a bit quicker with SSE assembly where the 80-bit precision isn't as critical but it's otherwise a really good library if you need to do serious complex math. I've compiled with it up to XE2 with no issues. dewresearch.com/downloads-site/132-download-area-delphi -- ref dewresearch.com/help/delphi/mtxvec/…J...
Delphi 2006 had a number of bugs related to operator overloading, and this was one of them. It got fixed in Delphi 2007.Jeroen Wiert Pluimers

2 Answers

10
votes

You should not replace const by var. Let me explain why.

Background

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

The Relevant Case

From this discussion, it should be clear that you shouldn't replace const by var. Indeed,

  1. If you change from 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!
  2. Future implementations of the functions might change the arguments, which will accidently change the variables passed as arguments.

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!

Conclusion

The solution in your case, therefore, is simply to remove const altogether (and NOT replace it by var!).

6
votes

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.