Ada 202x (42日目) - その他のaccessの変更¶
神は細部に宿るらしいです。 (意味分かんないで使ってる。)
access型と可視性¶
次のコードはコンパイラによってコンパイルできたりできなかったりしました。
with Ada.Unchecked_Deallocation;
package Pkg1 is
type T (<>) is limited private; -- unconstrained and limited
type T_Access is access T;
private
type T is new String (1 .. 10); -- constrained and unlimited
procedure Free is new Ada.Unchecked_Deallocation (T, T_Access); -- ?
end Pkg1;
公開部では T
はunconstrainedかつlimitedの抽象型です。
T_Access
は宣言時点ではそのような T
へのaccess型です。
その後private部で T
の実体はconstrainedかつunlimitedと明かされました。
このとき T_Access
の示す先の型の可視性がどちらに従うかという話です。
宣言時点に従うならunconstrainedかつlimitedの抽象型、使用箇所に従うなら Pkg1
のprivate部やbody部ではconstrainedかつunlimitedの new String (1 .. 10)
となります。
Ada.Unchecked_Deallocation
は割り当てる型とそのaccess型を引数に取ります。
ところが T_Access
の示す先が宣言時点のものですと Ada.Unchecked_Deallocation
をインスタンス化している ?
の行は T
の実体と T_Access
の示す先の抽象型が一致せずコンパイルエラーになります。
Ada 202xで有効なコードであることが確定しました。 コンパイルできるのが正しいです。
珍しくJanus/Adaが修正を迫られることになった件。 GNATとICC/Adaは元からこの挙動でした。
access型とfreezing¶
Adaには型のfreezingと呼ばれる概念があります。 型はその型の詳細が必要になった時点でfreezingされます。 freezingされた型はその後に内部表現を指定したりできなくなります。 (良い訳として何がいいでしょうね。)
declare
type T is mod 2 ** 8;
X : T;
for T'Size use 8; -- error
この例では型 T
に対してその型の変数 X
を宣言した後から内部表現を指定しようとしておりエラーになります。
その上で細かい話になります。 access型の指す先のサブプログラムの引数や返値の型はfreezingされるような規格の文面になっていました。
declare
type T is mod 2 ** 8;
type A is access procedure (X : T);
for T'Size use 8; -- error
勿論access型ですから宣言時点では型の詳細は必要ないです。
そして実際にはfreezingしないことを求めるようなACATSのテストがありました。
Ada 202xでfreezingしないと規格の文面が修正されました。 Adaを名乗るコンパイラはACATSのテストを通っていますので動作が変わるわけではないです。
匿名のaccess型とprotected¶
protected
の中にサブプログラムを指す匿名のaccess型があった場合その匿名のaccess型まで protected
になってしまっていました。
protected Prot1 is
procedure High_Order (Callback : access procedure);
end Prot1;
このコードは次のコードと同じでした。
protected Prot2 is
procedure High_Order (Callback : access protected procedure);
end Prot2;
Ada 202xで修正され、protected
を付けていない場合は匿名のaccess型は普通のサブプログラムを指すようになりました。
protected
に属するサブプログラムを指したい場合は protected
を書く必要があります。
匿名のaccess型と呼び出し規約¶
record
の中にサブプログラムを指す匿名のaccess型があった場合呼び出し規約は record
に従うようになりました。
type R is
record
Callback : access procedure;
end record
with Convention => C;
この Callback
の呼び出し規約は従来は Ada
でした。
Ada 202xからは C
になります。
便利になったのではあるのでしょうけれども。 呼び出し規約は細かく指定しましょう。
type R is
record
Callback : access procedure
with Convention => Ada;
end record
with Convention => C;
匿名のaccess型の暗黙の型変換と生存期間¶
次の例を見てください。
declare
type Integer_Access is access constant Integer;
Global : Integer_Access;
function Get_Access (X : aliased in Integer)
return access constant Integer is
begin
return X'Access;
end Get_Access;
begin
declare
Local : aliased Integer;
begin
Global := Get_Access (Local);
Global := Integer_Access (Get_Access (Local)); -- error
end;
関数の返値が匿名のaccess型の場合その寿命は引数から推定されます。
実際この例では Get_Access
は単に引数のアクセス値を返しているだけです。
それを他のaccess型の変数に代入する場合は寿命のチェックが行われます。
ここで Integer_Access
に明示的に型変換している行はエラーになるのですが、暗黙の型変換は見逃されていました。
Ada 202xで修正され、暗黙の型変換でもチェックが行われるようになりました。
access型のnot nullとtagged型のプリミティブ¶
tagged型のプリミティブは引数がaccess型の場合暗黙に not null
ということになります。
package Pkg2 is
type T is tagged null record;
procedure Primitive (Object : access T);
end Pkg2;
-- bodyは省略
procedure Renamed (Object : not null access Pkg2.T) renames Pkg2.Primitive;
しかし private
にしてしまいますと、公開部の情報では not null
ではないということになります。
package Pkg3 is
type T is private;
procedure Primitive (Object : access T);
private
type T is tagged null record;
end Pkg3;
Pkg3.Primitive
に null
を渡すと実行時 Constraint_Error
になります。
ところが現在のGNATは可視性を無視して次の renames
を許してしまいます。
procedure Renamed (Object : not null access Pkg3.T) renames Pkg3.Primitive;
Janus/Adaはこの renames
をエラーにします。
またJanus/Adaはなんと本体側をもエラーにしてしまいます。
package body Pkg3 is
procedure Primitive (Object : access T) is
begin
null;
end Primitive;
end Pkg3;
ここでは可視性の上で T
が tagged
であると見えていますのでこの本体側の Primitive
は not null
付きと見做されるようです。
そのため not null
なしの仕様部と一致しなくなります。
この仕様がAda 202xで詰められました。 簡単に言えばJanus/Adaの勝利です。
Ada 95時代に書かれたコードは仕様部に not null
を付けて回らないとエラーになります。
既にAda 95からAda 2005までの期間よりも、Ada 2005以降の方が長いんですよね……。
あー、未だにAda 95までの情報で書いてある記事滅びねえかなあ……。
access型のnot nullとrenamesとgeneric¶
次の例を見てください。
type Float_Access is access Float;
generic
A : in Float_Access;
package Gen1 is
procedure Proc1;
end Gen1;
package body Gen1 is
procedure Proc1 is
R : not null Float_Access renames A;
begin
null;
end Proc1;
end Gen1;
R
では not null
を付けて generic
の引数を renames
しています。
勿論 A
に null
を渡すと実行時に Constraint_Error
になります。
つまり気をつけないと危ないわけです。
in out
の時は既に not null
を一致させないとエラーになっていましたが in
は違いました。
Ada 202xでは in
も含めてコンパイル時エラーになります。
関連AI¶
AI12-0140-1 Access to unconstrained partial view when full view is constrained
AI12-0186-1 Profile freezing for the Access attribute
AI12-0207-1 Convention of anonymous access types
AI12-0278-1 Implicit conversions of anonymous return types
AI12-0287-1 Legality Rules for null exclusions in renaming are too fierce
AI12-0289-1 Implicitly null excluding anonymous access types and conformance