Search code examples
arraysdelphicastingopen-array-parameters

Cast static array to open array of different element type


(I already asked this at CodeReview where it got closed as off-topic. Hopefully it's on-topic here.)

I have a static arrays of a derived type (like LabelsA: array[0..3] of TLabel; in the following sample code) and a routine accepting an open array of the base type (like procedure DoSomethingWithControls(const AControls: array of TControl);), and I want to call DoSomethingWithControls with those static arrays. Please see my sample:

procedure DoSomethingWithControls(const AControls: array of TControl);
var
  i: Integer;
begin
  for i := Low(AControls) to High(AControls) do
    Writeln(AControls[i].Name);
end;

procedure Test;
var
  LabelsA: array[0..3] of TLabel;
  LabelsB: array[0..1] of TLabel;

  procedure Variant1;
  type
    TArray1 = array[Low(LabelsA)..High(LabelsA)] of TControl;
    TArray2 = array[Low(LabelsB)..High(LabelsB)] of TControl;
  begin
    DoSomethingWithControls(TArray1(LabelsA));
    DoSomethingWithControls(TArray2(LabelsB));
  end;

  procedure Variant2;
  type
    TControlArray = array[0..Pred(MaxInt div SizeOf(TControl))] of TControl;
    PControlArray = ^TControlArray;
  begin
    DoSomethingWithControls(Slice(PControlArray(@LabelsA)^, Length(LabelsA)));
    DoSomethingWithControls(Slice(PControlArray(@LabelsB)^, Length(LabelsB)));
  end;

  procedure Variant3;
  var
    ControlsA: array[Low(LabelsA)..High(LabelsA)] of TControl absolute LabelsA;
    ControlsB: array[Low(LabelsB)..High(LabelsB)] of TControl absolute LabelsB;
  begin
    DoSomethingWithControls(ControlsA);
    DoSomethingWithControls(ControlsB);
  end;

begin
  Variant1;
  Variant2;
  Variant3;
end;

There are some possible variants of calling DoSomethingWithControls:

  • Variant 1 is quite simple but needs an "adapter" types like TArray1 for every size of TLabel array. I would like it to be more flexible.

  • Variant 2 is more flexible and uniform but ugly and error prone.

  • Variant 3 (courtesy of TOndrej) is similar to Variant 1 - it doesn't need an explicit cast, but Variant 1 offers a tiny bit more compiler security if you mess something up (e.g. getting the array bounds wrong while copy-pasting).

Any ideas how i can formulate these calls without these disadvantages (without changing the element types of the arrays)? It should work with D2007 and XE6.


Solution

  • These casts are all rather ugly. They will all work, but using them makes you feel dirty. It's perfectly reasonable to use a helper function:

    type
      TControlArray = array of TControl;
    
    function ControlArrayFromLabelArray(const Items: array of TLabel): TControlArray;
    var 
      i: Integer;
    begin
      SetLength(Result, Length(Items));
      for i := 0 to high(Items) do
        Result[i] := Items[i];
    end;
    

    And then you call your function like this:

    DoSomethingWithControls(ControlArrayFromLabelArray(...));
    

    Of course, this would be so much cleaner if you could use generics.