Delphi のインライン変数宣言と配列


はじめに

Delphi 10.3 Rio でインライン変数宣言とその型推論が実装されたのですが、試していたら配列が宣言できない事に気付きました。どうにか配列をインライン変数宣言できないものか試してみます。

インライン変数宣言と型推論に関する記事は以前にも幾つか書いています。

問題のコード

var ブロックであれば静的配列は以下のように宣言できます。

procedure Test;
var
  arr: array [0..9] of Integer;
begin

  ...

end;

これを var キーワードを使ってインライン変数宣言してみると、

procedure Test;
begin
  var arr: array [0..9] of Integer;
  ...

end;

これが通らないのです。

procedure Test;
begin
  var arr: array of Integer;
  ...

end;

動的配列もダメです。

不思議な記述

何故か型推論を使うと以下のような(動的)配列の宣言が可能です。

procedure Test;
begin
  var arr := [512, 1024]; // array of Integer
  ...

end;

値が 255 を超えても OK (集合ではない) ですし、

procedure Test;
begin
  var arr := [512, 1024]; // array of Integer
  arr[0] := 2048; // 代入
  ...

end;

代入も可能です。そしてこれは動的配列扱いなので、配列の拡張も可能です。

procedure Test;
begin
  var arr := [512, 1024]; // array of Integer
  SetLength(arr, 10); // 配列の拡張
  ...

end;

但し、空の動的配列は宣言できません。

procedure Test;
begin
  var arr := []; // NG
  ...

end;

これの何が 不思議な記述 なのかと言うと、Delphi では配列がローカル変数でなければ宣言と同時に初期値を代入できるのですが、

var
  Nums: array [0..2] of Integer = (1, 2, 3);

だったら、さっきのも

procedure Test;
begin
  var arr := (512, 1024); // array of Integer
  ...

end;

() を使った方が自然じゃないのかと思うわけです。何故 [] にしたのでしょうね?

動的配列の文字列風の操作

...と、不思議に思って調べていたら XE7 で動的配列の操作に新機能が追加されていました。

var
  A: array of integer;
  B: TBytes = [1, 2, 3, 4]; //Initialization can be done from declaration
begin
  ...
  A := [1, 2, 3];  // assignation using constant array
  A := A + [4, 5]; // addition - A will become [1,2,3,4,5]
  ...
end;

これかぁ!

他の書き方

他には TArray<T> を使う書き方があります。

procedure Test;
begin
  var arr: TArray<Integer>;
  ...

end;

初期化したいのなら配列コンストラクタが使えます。

procedure Test;
begin
var arr := TArray<Integer>.Create(1, 2);
  ...

end;

または System.Types を uses して、TIntegerDynArray を使うかです。

uses
  ..., System.Types;

...

procedure Test;
begin
  var arr: TIntegerDynArray;
  ...

end;

System.Types には T???DynArray という名前で以下のような動的配列の型が定義されています。

配列型 要素の型
TShortIntDynArray  ShortInt
TByteDynArray Byte
TSmallIntDynArray SmallInt
TWordDynArray Word
TIntegerDynArray Integer
TCardinalDynArray Cardinal
TInt64DynArray Int64
TUInt64DynArray UInt64
TSingleDynArray  Single
TDoubleDynArray Double
TBooleanDynArray Boolean
TStringDynArray string
TWideStringDynArray WideString

これらを使ってもいいですね。

多次元配列は?

これがですね。

procedure Test;
begin
  var arr := [1, 2][3,4];     // NG
  var arr := [1, 2], [3,4];   // NG
  var arr := [[1, 2], [3,4]]; // NG
  ...

end;

どの書き方でもダメなんです。どこかに以下のように定義しておき、

type
  TArray2D<T> = array of array of T;
  TArray3D<T> = array of array of array of T;

こんな感じで使うしかないのでしょうね。何故これらの定義が最初から用意されていないのだろう?

procedure Test;
begin
  var arr: TArray2D<Integer>;
  ...

end;

あ!TArray を入れ子で使えという事か。

procedure Test;
begin
  var arr2d: TArray<TArray<Integer>>;         // 2D
  var arr3d: TArray<TArray<TArray<Integer>>>; // 3D
  ...

end;

他にいい方法があったら教えて偉い人!

おわりに

いずれにせよ、インライン変数宣言では静的配列は宣言できないようです。

この制限の理由は大体想像がつきます。Pascal は結果の型 (戻り値の型) に単純型かポインタ型しか使えず、Delphi だと結果の型に構造化型を使うためには型の定義が必要だからでしょう。

あるいは型推論がなければインライン変数宣言で静的配列の宣言ができたのかもしれませんが。