About Delphi / Generic / array's : why this doesn't compile?

Viewed 196

I'm wondering what is wrong in the generic code below (written in Delphi 10.4.1). Moreover, is there another way to achieve the same goal ? I mean searching inside an array (I know that I should use collection). Thanks in advance !

Regards

type
TDelphiExtention = class
public
  class function inside<T>(const value : T; const arr : array of T) : boolean;
end;

class function TDelphiExtention.inside<T>(const value : T; const arr : array of T) : boolean;
var
  i : integer;

begin
  result := true;
  for i := Low(arr) to High(arr) do begin
    if (arr[i] = value) then begin // E2015 Operator not applicable to this operand type !!!
      exit;
    end;
  end;

  result := false; // Not found
end;

// This one compiles like a charm... But is not generic :(
function inside(const value : integer; const arr : array of integer) : boolean;
var
  i : integer;

begin
  result := true;
  for i := Low(arr) to High(arr) do begin
    if (arr[i] = value) then begin
      exit;
    end;
  end;

  result := false; // Not found
end;
2 Answers

T could be literally anything. The compiler doesn't know how to compare for equality given an arbitrary type. For instance, suppose that T is a record. If you tried to write A = B where A and B were of that record type, that would also be a compiler error. Likewise for arrays, and so on.

You can use the facilities of the System.Generics.Defaults unit to obtain an equality comparer:

class function TDelphiExtention.inside<T>(const value: T;
  const arr: array of T): boolean;
var
  i: integer;
  comparer: IEqualityComparer<T>;
begin
  comparer := TEqualityComparer<T>.Default;

  for i := Low(arr) to High(arr) do
  begin
    if comparer.Equals(arr[i], value) then
    begin
      result := true;
      exit;
    end;
  end;

  result := false; // Not found
end;

Do beware though that not all types will have appropriate equality comparers, so you may want to offer an overload that accepts an equality comparer as an argument.

class function TDelphiExtention.inside<T>(const value: T;
  const arr: array of T; comparer: IEqualityComparer<T>): boolean;
var
  i: integer;
begin
  for i := Low(arr) to High(arr) do
  begin
    if comparer.Equals(arr[i], value) then
    begin
      result := true;
      exit;
    end;
  end;

  result := false; // Not found
end;

FWIW, your method would be better named Contains since this would fit with the naming convention used by most other Delphi libraries.

Well, I've mad some diggings and I'm a bit surprised on my findings.

  • Unit System.pas defines TArray as an alias to an array of T
  • Unit System.Generics.Collections defines TArray = class holding only static class methods (already funny) but very few of the classic ones. Here no contains so far !
  • Unit Spring.Collections defines a lot of interesting collections but no more support for the array one. In fact CreateArray did exist but have vanished... And documentation is quite often outdated :(
  • Quickly looked at the unit Rapid.Generics, it seems too intrusive, no predicate search, does not fill my requirements

==> finally my naive approach seems to be missing everywhere !-) My next question is why array are so rejected ? Still need Single, First, Any and their default variants

Last but not least, the proposed fix is great but just "postponed" the situation.

IEqualityComparer = interface function Equals(const Left, Right: T): Boolean;

Why Delphi compiler can't interpret my arr[i] as T or even value as T directly ?

if (value = value) then begin
   // Does not compile
end;
if (arr[i] = arr[i]) then begin
   // Idem
end;

Compiler is picky !

Regards

Related