Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why casting an open array parameter to an array type causes E2089 Invalid typecast?

I'm using Delphi 2007 (Pre generics) and I've defined many functions who can be used for all arrays of TObject's descendants, example:

function IndexOf(AArray : array of TObject; AItem : TObject) : integer;
begin
  //...
end;

For passing them dynamic arrays of TObject's descendants, I've defined an array type TObjectArray = array of TObject. In this way I can cast dynamic arrays and pass them to my functions without any problems:

type
  TChild = class(TObject);

...

procedure Test();
var
  Items : array of TChild;
  Item : TChild;
begin
  //...
  IndexOf(TObjectArray(Items), Item);
end;

The problem comes when I try to pass them open array parameters:

procedure  Test(AItems : array of TChild);
var
  Item : TChild;
begin
  //...
  IndexOf(TObjectArray(AItems), Item);
end;

In these cases, the compiler raises the following error message:

E2089 Invalid typecast

Why does this happen and how can I avoid that?

like image 276
Fabrizio Avatar asked May 18 '18 15:05

Fabrizio


1 Answers

You don't need to typecast when passing ANY type of array to an open array parameter, provided the elements are the same type. You can pass the array as-is, the open array will accept it just fine. That is the whole point of open arrays.

type
  TChild = class(TObject);

...

function IndexOf(AArray : array of TObject; AItem : TObject) : integer;
begin
  //...
end;

procedure Test();
var
  Items : array of TObject;
  Item : TChild;
begin
  //...
  IndexOf(Items, Item);
end;

procedure Test2();
var
  Items : array[0..N] of TObject;
  Item : TChild;
begin
  //...
  IndexOf(Items, Item);
end;

procedure Test3(AItems : array of TObject);
var
  Item : TChild;
begin
  //...
  IndexOf(AItems, Item);
end;

However, you cannot pass an array of TChild where an array of TObject is expected. The compiler will reject it with an "incompatible types" error. The input array must use the same element type as the open array.

A simple typecast can fix that when passing a dynamic array or a fixed array:

procedure Test();
type
  TObjectArray = array of TObject;
var
  Items : array of TChild;
  Item : TChild;
begin
  //...
  IndexOf(TObjectArray(Items), Item);
end;

procedure Test2();
type
  TObjectFixedArray = array[0..N] of TObject;
  PObjectFixedArray = ^TObjectFixedArray;
var
  Items : array[0..N] of TChild;
  Item : TChild;
begin
  //...
  IndexOf(PObjectFixedArray(@Items)^, Item);
end;

But, you simply cannot typecast an open array to any other array type. Different types of arrays have different memory layouts (typecasting a dynamic array to another dynamic array, or a fixed array to another fixed array, does not change the memory layout of the array being typecasted).

In the case of an open array, it is actually not even an array at all, it is just a pointer to the first element of the passed array, and there is a second hidden parameter for the array length. In other words, this kind of declaration:

procedure Test3(AItems : array of TChild);

Is actually implemented by the compiler behind the scenes like this:

procedure Test3(AItems : ^TChild; AItems_High: Integer);

So, you will have to make a copy of the open array elements to another array, and then pass that array along instead:

procedure Test3(AItems : array of TChild);
var
  Items: array of TObject;
  Item : TChild;
  I: Integer;
begin
  //...
  SetLength(Items, Length(AItems));
  For I := Low(AItems) to High(AItems) do
    Items[I] := AItems[I];
  IndexOf(Items, Item);
end;
like image 71
Remy Lebeau Avatar answered Oct 15 '22 09:10

Remy Lebeau