Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 675
Uli Gerhardt Avatar asked Jul 22 '15 07:07

Uli Gerhardt


4 Answers

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.

like image 148
David Heffernan Avatar answered Nov 07 '22 07:11

David Heffernan


Not extremely beautiful either but you could trick the compiler like this:

procedure Variant3;
var
  ControlsA: array[Low(LabelsA)..High(LabelsA)] of TControl absolute LabelsA;
begin
  DoSomethingWithControls(ControlsA);
end;
like image 43
Ondrej Kelle Avatar answered Nov 07 '22 06:11

Ondrej Kelle


Declare an overloaded procedure:

procedure DoSomethingWithControls(const AControls: array of TControl); overload;
var
  i: Integer;
begin
  for i := 0 to High(AControls) do
    if Assigned(AControls[i]) then
       Writeln(AControls[i].Name)
    else
      WriteLn('Control item: ',i);
end;

procedure DoSomethingWithControls(const ALabels: array of TLabel); overload;
type
  TControlArray = array[0..Pred(MaxInt div SizeOf(TControl))] of TControl;
  PControlArray = ^TControlArray;
begin
  DoSomethingWithControls(Slice(PControlArray(@ALabels)^, Length(ALabels)));
end;

This is a general solution to your variant2. One declaration for all cases, so less prone to errors.

like image 44
LU RD Avatar answered Nov 07 '22 08:11

LU RD


Below example is based on how open array parameters are internally implemented. It won't work with "typed @ operator" however.

  procedure Variant4;
  type
    TCallProc = procedure (AControls: Pointer; HighBound: Integer);
  var
    CallProc: TCallProc;
  begin
    CallProc := @DoSomethingWithControls;

    CallProc(@LabelsA, Length(LabelsA) - 1);
    CallProc(@LabelsB, Length(LabelsB) - 1);
  end;

Passing High(Labels) for HighBound is perhaps better as long as all static arrays are 0 based.

like image 1
Sertac Akyuz Avatar answered Nov 07 '22 08:11

Sertac Akyuz