Jeroen Wiert Pluimers
Jeroen Wiert Pluimers

Reputation: 24483

Do dynamic arrays support a non-zero lower bound (for VarArrayCreate compatibility)?

I'm going maintain and port to Delphi XE2 a bunch of very old Delphi code that is full of VarArrayCreate constructs to fake dynamic arrays having a lower bound that is not zero.

Drawbacks of using Variant types are:

Both could become moot if I could use dynamic arrays.

Good thing about variant arrays is that they can have non-zero lower bounds.

What I recollect is that dynamic arrays used to always start at a lower bound of zero.

Is this still true? In other words: Is it possible to have dynamic arrays start at a different bound than zero?

As an illustration a before/after example for a specific case (single dimensional, but the code is full of multi-dimensional arrays, and besides varDouble, the code also uses various other varXXX data types that TVarData allows to use):

function CalculateVector(aSV: TStrings): Variant;
var
  I: Integer;
begin
  Result := VarArrayCreate([1,aSV.Count-1],varDouble);
  for I := 1 to aSV.Count-1 do
    Result[I] := CalculateItem(aSV, I);
end;

The CalculateItem function returns Double. Bounds are from 1 to aSV.Count-1.

Current replacement is like this, trading the space zeroth element of Result for improved compile time checking:

type
  TVector = array of Double;
function CalculateVector(aSV: TStrings): TVector;
var
  I: Integer;
begin
  SetLength(Result, aSV.Count); // lower bound is zero, we start at 1 so we ignore the zeroth element
  for I := 1 to aSV.Count-1 do
    Result[I] := CalculateItem(aSV, I);
end;

Upvotes: 4

Views: 1904

Answers (2)

David Heffernan
David Heffernan

Reputation: 612954

Having answered your direct question already, I also offer you the beginnings of a generic class that you can use in your porting.

type
  TSpecifiedBoundsArray<T> = class
  private
    FValues: TArray<T>;
    FLow: Integer;
    function GetHigh: Integer;
    procedure SetHigh(Value: Integer);
    function GetLength: Integer;
    procedure SetLength(Value: Integer);
    function GetItem(Index: Integer): T;
    procedure SetItem(Index: Integer; const Value: T);
  public
    property Low: Integer read FLow write FLow;
    property High: Integer read GetHigh write SetHigh;
    property Length: Integer read GetLength write SetLength;
    property Items[Index: Integer]: T read GetItem write SetItem; default;
  end;

{ TSpecifiedBoundsArray<T> }

function TSpecifiedBoundsArray<T>.GetHigh: Integer;
begin
  Result := FLow+System.High(FValues);
end;

procedure TSpecifiedBoundsArray<T>.SetHigh(Value: Integer);
begin
  SetLength(FValues, 1+Value-FLow);
end;

function TSpecifiedBoundsArray<T>.GetLength: Integer;
begin
  Result := System.Length(FValues);
end;

procedure TSpecifiedBoundsArray<T>.SetLength(Value: Integer);
begin
  System.SetLength(FValues, Value);
end;

function TSpecifiedBoundsArray<T>.GetItem(Index: Integer): T;
begin
  Result := FValues[Index-FLow];
end;

function TSpecifiedBoundsArray<T>.SetItem(Index: Integer; const Value: T);
begin
  FValues[Index-FLow] := Value;
end;

I think it's pretty obvious how this works. I contemplated using a record but I consider that to be unworkable. That's down to the mix between value type semantics for FLow and reference type semantics for FValues. So, I think a class is best here.

It also behaves rather weirdly when you modify Low.

No doubt you'd want to extend this. You'd add a SetBounds, a copy to, a copy from and so on. But I think you may find it useful. It certainly shows how you can make an object that looks very much like an array with non-zero lower bound.

Upvotes: 2

David Heffernan
David Heffernan

Reputation: 612954

Dynamic arrays always have a lower bound of 0. So, low(A) equals 0 for all dynamic arrays. This is even true for empty dynamic arrays, i.e. nil.

From the documentation:

Dynamic arrays are always integer-indexed, always starting from 0.

Upvotes: 5

Related Questions