【问题标题】:Is there a way to verify two generic formals are the same type if one is incomplete?如果一个不完整,有没有办法验证两个通用形式是否相同?
【发布时间】:2021-11-23 11:44:02
【问题描述】:

给定一个通用父包:

generic
    type T(<>) is tagged;
package Parent is
    type Instance is tagged private;
private
    type T_Access is access T;
    type Instance is tagged record
        Thing : T_Access := null;
    end record;
end Parent;

在子包中是否有办法确保作为通用形式传递给子包的类型与 Parent.T 的类型相同(甚至是其后代)?例如,考虑通用子包:

generic
    type T(<>) is new Base with private;
package Parent.Child is
    type T_Access is access T;
    function Make(Ref : not null T_Access) return Parent.Instance;
end Parent.Child;

package body Parent.Child is

    function To_Parent(Source : T_Access) return Parent.T_Access is
    begin
        -- here is where I need to be able to safely convert 
        -- an access to the complete type to an access to the 
        -- incomplete type.  I can used Unchecked_Conversion,
        -- but that goes south if someone passes in a type to 
        -- Parent.Child that is not the same as Parent.  If
        -- I could know that Parent.Child.T is a descendant of
        -- Parent.T, I could just convert it (I think??).
    end To_Parent;

    function Make(Ref : not null T_Access) return Parent.Instance is
    begin
        return (Thing => To_Parent(Ref);
    end Make;

end Parent.Child;

其中 Base 是一些基本标记类型。您可以使用以下内容作为占位符:

type Base is tagged limited null record;

我正在寻找一种编译时间或运行时的方法来验证 Parent.Child 内部 Parent.Child.T 是否与 Parent.T 相同(或者即使 Parent.Child.T 是 Parent.Child 的后代。 T.

注意:我正在尝试使用父子包关系,因为它允许 Child 看到 Parent 的私有部分。

我天真地尝试了一些基于运行时的东西,例如:

package body Parent.Child is

    -- other stuff

begin
    if Child.T not in Parent.T then
        raise Storage_Error with "Invalid type passed to child package";
    end if;
end Parent.Child;

但这只会导致 GNAT 错误:

premature usage of incomplete type "T"

因为 Parent.T 不完整。这里的目的是创建一个可用于不完整类型的自动内存管理框架,因此父包提供大部分功能,而子包可以稍后实例化并添加需要完整类型信息的功能(如构造/解除分配)。然后你可以做如下声明:

type Test is tagged;
package B is new Parent(Test);

type Test is new Base with record
    Thing : Parent.Instance;
end record;

package M is new B.Child(Test);

完整的测试代码集(请记住,这是原始和裸露的,以使其尽可能简单):

------------------------ Base Package ----------------------------
package Base is
   type Instance is tagged limited null record;
end Base;

----------------------- Parent Package ---------------------------
generic
    type T(<>) is tagged;
package Parent is
    type Instance is tagged private;
private
    type T_Access is access T;
    type Instance is tagged record
        Thing : T_Access := null;
    end record;
end Parent;

------------------------ Child Package ---------------------------
with Base;

generic
   type T(<>) is new Base.Instance with private;
package Parent.Child is
   
   type T_Access is access T;

   function Make(Ref : not null T_Access) return Parent.Instance;
   
end Parent.Child;

with Ada.Unchecked_Conversion;
with Ada.Unchecked_Deallocation;

package body Parent.Child is

   -- Used later in code not shown, but needed
   -- and requires Child.T to be complete.
   procedure Finalize is new Ada.Unchecked_Deallocation
      (Object => T,
       Name   => T_Access);

   function To_Parent is new Ada.Unchecked_Conversion
      (Source => Child.T_Access,
       Target => Parent.T_Access);

   -- This is where things get IFFY.  I do unchecked conversions here.
   -- If Parent.T is not equal to Parent.Child.T, then this can go bad
   -- really fast.  If there was a way to verify the types were the same,
   -- then I could safely do this.  Or if there was a way for me to
   -- verify that Parent.Child.T was a descendant of Parent.T, then
   -- I could just convert them without unchecked_conversion.
   function Make(Ref : not null T_Access) return Parent.Instance is
      (Thing => To_Parent(Ref));

end Parent.Child;

---------------------------- Main -------------------------------
with Ada.Text_IO;
with Base;
with Parent;
with Parent.Child;

procedure Main is

   type Test is tagged;
   
   package P is new Parent(Test);

   type Test is new Base.Instance with record
      Thing :  P.Instance;
   end record;

   package PC is new P.Child(Test);

   Thing : P.Instance := PC.Make(new Test);

begin
   Ada.Text_IO.Put_Line("Hello");
end Main;

【问题讨论】:

    标签: generics types ada


    【解决方案1】:

    如果你不需要泛型的父子关系,你可以这样做:

    foo.ads

    generic
       type T(<>) is tagged;
    package Foo is
    
    end Foo;
    

    bar.ads

    with Foo;
    
    generic
       type T(<>) is tagged private;
       with package Foo_Instance is new Foo(T); --package parameter
    package Bar is
     
    end Bar;
    

    这样,完整类型必须与不完整类型完全匹配,即。它不能是类型扩展,因此:

    with Foo;
    with Bar;
    
    package Baz is
    
       type Base is tagged;
       package Base_Foo is new Foo(Base);
       
       
       type Base is tagged null record;
       package Base_Bar is new Bar(Base, Base_Foo);
       
       
       type Extension is new Base with null record;
       package Extension_Bar is new Bar(Extension, Base_Foo); -- fails!
    
    end Baz;
    

    【讨论】:

    • 我确实需要查看 Parent 的私人部分。我将更新问题以包含该要求。
    • 我可以尝试玩的一件事是将 Bar 的形式应用于子包?假设我们说 Foo 是 Foo 并且 Bar 是 Foo 的孩子,I.E. Foo.Bar,Bar 可以查看 Foo_Instance 的私有部分的实例吗?虽然我会说将子包的父级实例传递给子级感觉......很奇怪?
    • 感觉很奇怪,因为这是不允许的... ;)
    • 确实!我刚试过它,它对我大喊两件事:1. 尝试使用 Parent 包实例化作为正式的,这是不允许的,以及 2. 尝试在 Child 中使用正式的 Parent 实例的私有元素。嗯,还有什么想法吗?我可以通过未经检查的转换来强制从孩子到父母,但随后我会向使用不同类型实例化子包的人敞开心扉。这就是我试图查看是否可以以某种方式验证类型是否相同并在详细说明中引发异常的地方。
    【解决方案2】:

    此答案的先前版本忽略了 Jere 需要它为 formal incomplete types 工作这一事实,这是与 AI05-0213 一起引入的。

    该人工智能的一个(主要?)用例是在某些情况下更容易创建签名包(参见Ada 2012 Rationale, section 4.3)。因此,这是一个使用签名包的产品 - 不知道它是否满足所需的用例。

    generic
       type T is tagged;
    package Signature is
    end Signature;
    
    with Signature;
    generic
       type T is tagged private;
       with package Sig is new Signature (T);
    package Parent is
       subtype Parent_T is T;
       Instance : T;
    end Parent;
    
    generic
       type T is new Parent.Parent_T with private;
       with package Sig is new Signature (T);
    package Parent.Child is
    end Parent.Child;
    
    with Signature;
    with Parent.Child;
    package User is
       type Base is tagged null record;
       procedure Proc (Param : Base);
    
       package Sig_For_Parent is new Signature (T => Base);
       package For_Parent is new Parent (T => Base, Sig => Sig_For_Parent);
    
       --  this is OK
       type Extension is new Base with null record;
       procedure Proc (Param : Extension);
       package Sig_For_Child is new Signature (T => Extension);
       package For_Child
       is new For_Parent.Child (T => Extension, Sig => Sig_For_Child);
    
       --  this fails
       type Wrong is tagged null record;  -- not in Base'Class
       package Sig_For_Wrong is new Signature (T => Wrong);
       package For_Wrong
       is new For_Parent.Child (T => Wrong, Sig => Sig_For_Wrong);
    
    end User;
    
    with Ada.Text_IO;
    package body User is
    
       procedure Proc (Param : Base) is
       begin
          Ada.Text_IO.Put_Line ("Base_P's Proc called.");
       end Proc;
    
       procedure Proc (Param : Extension) is
       begin
          Ada.Text_IO.Put_Line ("Extension_P's Proc called.");
       end Proc;
    
    end User;
    
    with User;
    procedure Test is
       Var : User.Extension;
    begin
       Var.Proc;
    end Test;
    

    Rationale chapter 的末尾,行

    (如果这太混乱了,别担心,如果你犯了错误,编译器会抱怨你。)

    当然是真的。我在玩弄这段代码时发现它不太善于告诉你错误是什么

    【讨论】:

    • 问题是关于不完整的父类型,但tagged private 不是不完整的类型
    • 我确实尝试过使用不完整的类型,但他的编译器不允许我使用它,这与 egilhh 的评论相匹配:类型 Test 已标记;包 P 是新父级(T => 测试);类型 Test 被标记为空记录;包 PC 是新的 P.Child(T => Test);
    • 旁注:给答案打分的礼仪是什么?我觉得我应该投票给那些试图提供帮助的人,即使答案没有回答我的问题,但我不知道这是否是这样的做事方式,或者投票是否纯粹是为了“这是正确的答案"
    • 将鼠标悬停在投票图标上会显示“这个答案很有用”,而不是“这个答案是唯一的”。但是,我认为答案需要删除(我不知道正式的 incomplete 类型,从来没有绝对需要这样的东西 - 虽然我刚刚从 2006 年发现了一个案例,它会有所帮助)
    • @SimonWright 我使用它们的主要地方是用于一般用途的内存管理,因为我想确保自引用类型可用于包。有关不完整类型使用的示例,请参阅我在 github 上的 Smart_Access 包。尽管“需要”让用户提供释放例程,但它具有副作用,因为您不能将 Unchecked_Deallocation 用于不完整类型。见github.com/jeremiahbreeden/bullfrog/blob/master/src/…
    【解决方案3】:

    所以我从 Simon Wright 的回答中获得了一些灵感,其中包括一个签名包。这本身是不够的,但它是最终解决方案的必要组成部分。基本上,由于 Ada 没有提供验证两种通用形式类型是否相同的方法,因此我使用单独的包在运行时通过为给定类型生成唯一 ID 来提供该功能,将该包传递给 Parent 和Parent.Child 包,并在 Parent.Child 主体内验证包的两个实例是否具有相同的 ID(因此是相同的包)。下面提供了一个示例:

    签名包的想法导致了下面的ID包:

    package Type_ID is
    
       type ID is limited private;
       function "="(L,R : ID) return Boolean;
    
       generic
          type Item_Type(<>);
       package Unique_ID is
          function Get_ID return ID;
       end Unique_ID;
       
    private
       
       -- Implement ID however you wish, just needs to be a unique ID for
       -- each package instantiation
    
    end Type_ID;
    

    然后我将父规范更改为:

    with Type_ID;
    
    generic
       with package ID is new Type_ID.Unique_ID(<>);
    package Parent is
       type Instance is tagged private;
    private
        -- private stuff
    end Parent;
    

    Parent.Child 包规范已更新为:

    with Base;
    
    generic
       type T(<>) is new Base.Instance with private;
       with package ID is new Type_ID.Unique_ID(T);
    package Parent.Child is
       
       type T_Access is access T;
    
       function Make(Ref : not null T_Access) return Parent.Instance;
       
    end Parent.Child;
    

    最后,验证类型相同的部分。由于 Parent 和 Parent.Child 都采用 Type_ID.Unique_ID() 的实例,我们只需要通过比较 Parent.Child 包体内的 Get_ID 函数的输出来确保它们都是同一个实例:

    package body Parent.Child is
    
       -- Other implementation stuff
    
       use all type Type_ID.ID;
    begin
       if Parent.ID.Get_ID /= Parent.Child.ID.Get_ID then
          raise Program_Error with "Invalid type passed to child package";
       end if;
    end Parent.Child;
    

    基本上是添加我自己的运行时类型信息。

    包的实例化则变为:

    with Ada.Text_IO;
    with Type_ID;
    with Base;
    with Parent;
    with Parent.Child;
    
    procedure Main is
    
       type Test is tagged;
    
       package ID is new Type_ID.Unique_ID(Test);
       package P is new Parent(ID);
    
       type Test is new Base.Instance with record
          Thing :  P.Instance;
       end record;
    
       package PC is new P.Child(Test,ID);
    
       Thing : P.Instance := PC.Make(new Test);
    
    begin
       Ada.Text_IO.Put_Line("Hello");
    end Main;
    

    【讨论】:

      【解决方案4】:

      在子包中是否有办法确保将类型传递到 作为通用形式的 child 与(甚至是后代)具有相同的类型 的) Parent.T?

      Yes.

      Generic
         Type Parent(<>) is tagged private;
      Package Example is
      End Example;
      

      ...和...

      Generic
         Type Descendant(<>) is new Parent with private;
      Package Example.Child is
      End Example.Child;
      

      【讨论】:

      • 从另一个答案复制/粘贴我的评论:这个问题专门关于不完整的父类型,但 tagged private 不是不完整的类型
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-08
      相关资源
      最近更新 更多