Is it safe to type-cast TArray to array of X?

2020-06-09 02:09发布

问题:

Today I discovered a compiler bug (QC#108577).

The following program fails to compile:

program Project1;
{$APPTYPE CONSOLE}

procedure P(M: TArray<TArray<Integer>>);
begin
  SetLength(M, 1, 2);
end;

begin
end.

The compiler gags on the SetLength line and says:

[dcc32 Error] E2029 ')' expected but ',' found

I know I could fix it like this:

procedure P(M: TArray<TArray<Integer>>);
var
  i: Integer;
begin
  SetLength(M, 1);
  for i := low(M) to high(M) do
    SetLength(M[i], 2);
end;

but naturally I'm keen to avoid having to resort to this.

The following variant compiles and seems to work:

procedure P(M: TArray<TArray<Integer>>);
type
  TArrayOfArrayOfInteger = array of array of Integer;
begin
  SetLength(TArrayOfArrayOfInteger(M), 1, 2);
end;

I don't know enough about the implementation details of dynamic arrays, TArray<T> casting, reference counting etc. to be confident that this is safe.

Is there anybody out there who does know enough to say one way or another whether or not this will produce the correct code at runtime?

回答1:

The compiler intrinsic procedure SetLength constructs an array of dimensions on the fly on the stack and calls DynArraySetLength for any dynamic array, be it generic or not. If a generic array wouldn't be structurally compatible with a regular dynamic array, the same implementation for setting the length possibly wouldn't be called.

In fact documentation of DynArraySetLength offers SetLength as an alternative for multi-dimensional arrays. DynArraySetLength could also be used instead of a typecast, but I don't see any reason to prefer one or the other.



回答2:

By design of the generics implementation, using a manual map to array of array of Integer will work.

But there is no benefit of using generics here!

Just code:

type
  TArrayOfArrayOfInteger = array of array of Integer;

procedure P(M: TArrayOfArrayOfInteger);
begin
  SetLength(TArrayOfArrayOfInteger, 1, 2);
end;

Note also that such TArray<> or array of .. are passed by value, and copied on the stack, unless you specify const or var:

procedure P(var M: TArrayOfArrayOfInteger);
begin
  SetLength(TArrayOfArrayOfInteger, 1, 2);
end; // now caller instance of the parameter will be resized

var A: TArrayOfArrayOfInteger;
...
A := nil;
P(A);
assert(length(A)=1);
assert(length(A[0])=2);


回答3:

I was recently bitten by the fact that DynamicArray<T> and TArray<T> in C++ are actually implemented differently (DynamicArray is a standalone class, whereas TArray is a TObject descendant), which implies that array of T and TArray<T> do have some implementation differences in Delphi as well. They certainly produce different types of RTTI, at least. Which was the root cause of a problem in some of my C++ code that started failing when the Delphi compiler started outputting TArray typedefs in HPP files for Delphi array of ... types instead of DynamicArray typedefs.