Delphi 'in' operator overload on a set

Tag: delphi , operator-overloading , delphi-xe2 Author: formartz Date: 2011-10-22

In Delphi XE2, I'm trying to overload the in operator on a record to allow me to check whether the value represented by the record is part of a set. My code looks like this:

type
  MyEnum = (value1, value2, value3);
  MySet = set of MyEnum;
  MyRecord = record
    Value: MyEnum;
    class operator In(const A: MyRecord; B: MySet): Boolean;
  end;

class operator MyRecord.In(const A: MyRecord; B: MySet): Boolean;
begin
  Result := A.Value in B;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  R: MyRecord;
  S: MySet;
begin
  R.Value := value1;
  S := [value1, value2];
  Button1.Caption := BoolToStr(R in S);
end;

The code fails to compile. For the statement R in S the compiler says: Incompatible types MyRecord and MyEnum.

How can I overload the In operator on MyRecord so that R in S will evaluate to True in the above code?

Best Answer

Well, you can almost do this, but you may not want to. AFAIK, class operators only work on the class (or record) they are defined within, so both R and S in your code have to be TMyRecord. With some injudicious use of implicit casting, we get the following:

unit Unit2;
interface
type
  MyEnum = (value1, value2, value3);
  MySet = set of MyEnum;
  MyRecord = record
    Value: MyEnum;
    ValueSet: MySet;
    class operator Implicit(A: MyEnum): MyRecord;
    class operator Implicit(A: MySet): MyRecord;
    class operator In (Left,Right:MyRecord): Boolean;
  end;

implementation

class operator MyRecord.Implicit(A: MyEnum): MyRecord;
begin
  Result.Value := A;
end;

class operator MyRecord.Implicit(A: MySet): MyRecord;
begin
  Result.ValueSet := A;
end;

class operator MyRecord.In(Left, Right: MyRecord): Boolean;
begin
  Result:= left.Value in Right.ValueSet;
end;
end.

The following will now complile, and even work:

procedure TForm1.Button1Click(Sender: TObject);
var
  R: MyRecord;
  S: MyRecord;
begin
  R.Value := value1;
  S := [value1,value2,value3];
  Button1.Caption := BoolToStr(R In S,true);
end;

Which, I'm sure we will all agree, is much more elegant than 'BoolToStr(R.Value in S)'. However the following will also compile, but give the wrong result:

procedure TForm1.Button1Click(Sender: TObject);
var
  R: MyRecord;
  S: MyRecord;
begin
  R.Value := value1;
  S := [value1,value2,value3];
  Button1.Caption := BoolToStr(S In R,true);
end;

So, as Dorin commented, better to just have dull, staid old 'BoolToStr(R.Value in S)'. Unless of course you are being paid per line of code. And a bonus for bug-fixing.

comments:

This particular question was just an exercise in figuring out class operators, which I had never used before. The record type in the actual code I was working on is a lot more complicated. The operators it defines allow the code that uses that record to be much simpler. A few hundred lines of simple operator functions allow thousands of lines of complex code to be much more readable.
Sorry, my comments were somewhat facetious. I admit I am very taken with the new operator loading for records, and am using it widely. And I agree that it makes code much clearer in many cases. I was only poking fun at my own solution, as it is overkill in this particulary case ;-)
"so both R and S in your code have to be TMyRecord" this is incorrect.

Other Answer1

For the in operator to work the right operand must be of the record type since it's a set operator and not a binary operator. In your case it is the left operand.

So the following will work:

type
  MyRecord = record
    Value: MyEnum;
    class operator In(const A: MyRecord; const B: MySet): Boolean;
  end;

  MyRecord2 = record
    Value: MySet;
    class operator In(const A: MyRecord; const B: MyRecord2): Boolean;
    class operator In(const A: MyEnum; const B: MyRecord2): Boolean;
  end;

class operator MyRecord.In(const A: MyRecord; const B: MySet): Boolean;
begin
  Result := A.Value in B;
end;

class operator MyRecord2.In(const A: MyRecord; const B: MyRecord2): Boolean;
begin
  Result := A.Value in B.Value;
end;

class operator MyRecord2.In(const A: MyEnum; const B: MyRecord2): Boolean;
begin
  Result := A in B.Value;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  R: MyRecord;
  R2: MyRecord2;
begin
  R.Value := value1;
  R2.Value := [value1, value2];

  if R in R2 then;
  if value1 in R2 then;
end;