Passing slice of a static/dynamic array by reference with start and length specifier Passing slice of a static/dynamic array by reference with start and length specifier arrays arrays

Passing slice of a static/dynamic array by reference with start and length specifier


Updated See a bit down for a generics solution.

Here is an alternative that encapsulates the type cast needed for the offset inside a function, which resides in an advanced record declared as a class function.Besides hiding the type cast, the offset is range checked against the high index of the array.

More types can be added if needed.

Type  SubRange = record    Type      TIntLongArray = array[0..MaxInt div SizeOf(Integer) - 1] of integer;      PIntLongArray = ^TIntLongArray;      TByteLongArray = array[0..MaxInt div SizeOf(Byte) - 1] of Byte;      PByteLongArray = ^TByteLongArray;    class function Offset( const anArray : array of Integer;                                  offset  : Integer) : PIntLongArray; overload; static;    class function Offset( const anArray : array of Byte;                                  offset  : Integer) : PByteLongArray; overload; static;    // ToDo: Add more types ...  end;class function SubRange.Offset(const anArray : array of Integer;                                      offset  : Integer): PIntLongArray;begin  Assert(offset <= High(anArray));  Result := PIntLongArray(@anArray[offset]);end;class function SubRange.Offset(const anArray : array of Byte;                                      offset  : Integer): PByteLongArray;begin  Assert(offset <= High(anArray));  Result := PByteLongArray(@anArray[offset]);end;

Note : the sum of the offset and the slice must not exceed the element count of the array.

Example calls:

WorkWithArray( Slice(SubRange.Offset(staticArray,1)^,2));WorkWithArray( Slice(SubRange.Offset(dynArray,1)^,2));WorkWithArray( Slice(SubRange.Offset(dynArrayG,1)^,2));

While this looks better, I'm still not convinced this is the optimal solution.


Update

When writing the above solution, I had a generics solution as the ultimate goal.

Here is an answer that utilizes anonymous methods and generics to implement a Slice(anArray,startIndex,Count) method that can be used with both static and dynamic arrays.

A straight generics solution would rely on range checking be turned off at every placed where it was used, and that would not be a pretty solution. The reason is that SizeOf(T) could not be used to declare a static array type of maximum size:

TGenericArray = array[0..MaxInt div SizeOf(T) - 1] of T; // SizeOf(T) not resolved

So we would have to use:

TGenericArray = array[0..0] of T;

instead. And this triggers the range check when it is on, for index > 0.

Solution

But the problem could be solved by another strategy, callbacks or a more modern terminology would be Inversion of Control (IoC) or Dependeny Injection (DI).The concept is best explained with, "Don't call me, we call you".

Instead of using a direct function, we pass the operational code as an anonymous method together with all parameters.Now the range check problem is contained within the Slice<T> frame.

Slice<Integer>.Execute(  procedure(const arr: array of Integer)  begin    WriteLn(Math.SumInt(arr));  end, dArr, 2, 7);

unit uGenericSlice;interfacetype  Slice<T> = record  private    type      PGenericArr = ^TGenericArr;      TGenericArr = array [0..0] of T;  public    type      TConstArrProc = reference to procedure(const anArr: array of T);    class procedure Execute(       aProc: TConstArrProc;                             const anArray: array of T;                                   startIndex,Count: Integer); static;  end;implementationclass procedure Slice<T>.Execute(aProc: TConstArrProc;  const anArray: array of T; startIndex, Count: Integer);begin  if (startIndex <= 0) then    aProc(Slice(anArray, Count))  else  begin    // The expression PGenericArr(@anArray[startIndex]) can trigger range check error    {$IFOPT R+}      {$DEFINE RestoreRangeCheck}      {$R-}    {$ENDIF}    Assert((startIndex <= High(anArray)) and (Count <= High(anArray)-startIndex+1),      'Range check error');    aProc(Slice(PGenericArr(@anArray[startIndex])^, Count));    {$IFDEF RestoreRangeCheck}      {$UNDEF RestoreRangeCheck}      {$R+}    {$ENDIF}  end;end;end.

Here are some example use cases:

program ProjectGenericSlice;{$APPTYPE CONSOLE}uses  Math,  uGenericSlice in 'uGenericSlice.pas';function Sum(const anArr: array of Integer): Integer;var  i: Integer;begin  Result := 0;  for i in anArr do    Result := Result + i;end;procedure SumTest(const arr: array of integer);begin  WriteLn(Sum(arr));end;procedure TestAll;var  aProc: Slice<Integer>.TConstArrProc;  dArr: TArray<Integer>;  mySum: Integer;const  sArr: array [1 .. 10] of Integer = (    1,2,3,4,5,6,7,8,9,10);begin  dArr := TArray<Integer>.Create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);  aProc :=    procedure(const arr: array of Integer)    begin      WriteLn(Sum(arr));    end;  // Test predefined anonymous method  Slice<Integer>.Execute( aProc, dArr, 2, 7);  // Test inlined anonymous method  Slice<Integer>.Execute(    procedure(const arr: array of Integer)    begin      WriteLn(Sum(arr));    end, dArr, 2, 7);  // Test call to Math.SumInt  Slice<Integer>.Execute(    procedure(const arr: array of Integer)    begin      WriteLn(Math.SumInt(arr));    end, dArr, 2, 7);  // Test static array with Low(sArr) > 0  Slice<Integer>.Execute(    procedure(const arr: array of Integer)    begin      WriteLn(Sum(arr));    end, sArr, 3 - Low(sArr), 7);  // Using a real procedure  Slice<Integer>.Execute(    SumTest, // Cannot be nested inside TestAll    dArr, 2, 7);  // Test call where result is passed to local var  Slice<Integer>.Execute(    procedure(const arr: array of Integer)    begin      mySum := Math.SumInt(arr);    end, dArr, 2, 7);  WriteLn(mySum);end;begin  TestAll;  ReadLn;end.


How about avoid open arrays and slice and use something like this ?

type   TArrayRef<T> = record   strict private     type PointerOfT = ^T;     FItems: PointerOfT;     FCount: Integer;   public       // FItems := @AItems[Offset]; FCount := Count;     constructor Create(AItems: array of T; Offset, Count: Integer);     property Items[Index: Integer]: T read GetItem; // Exit(FItems[Index])     property Count: Integer read FCount;    end;   TArrayRef = record // helpers     class function Create<T>(AItems: array of T; Offset, Count: Integer); static;     class function Create<T>(AItems: array of T; Count: Integer); static;     class function Create<T>(AItems: array of T); static;   end; procedure WorkWithArray(const anArray : TArrayRef<Integer>);var  I: Integer;begin  for I := 0 to anArray.Count - 1 do WriteLn(anArray[I]);end;WorkWithArray(TArrayRef.Create(StaticArray, 3)); // first three itemsWorkWithArray(TArrayRef.Create(DynArray, 10, 3)); 


In case anyone else was tripped up like me. In older versions of Delphi (D2007 and older. Not sure about XE versions), you will also get E2193 errors if you use overloads:

  procedure Polygon(Points: array of TPoint); overload;  procedure Polygon(Points: array of TDPoint); overload;

Works if remove overload:

  procedure Polygon(Points: array of TPoint);   procedure PolygonD(Points: array of TDPoint);

This is fixed in Delphi 10.3.0 (and probably other older versions too).