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?
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.
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);
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.