【问题标题】:How do I write the specification for a generic instantiation?如何编写通用实例化的规范?
【发布时间】:2018-02-23 21:10:13
【问题描述】:

我将从 Ada 中泛型过程的经典示例开始:

-------------------------
--  swaps.ads
-------------------------
package Swaps is
  generic
    type E is private;
  procedure Generic_Swap (Left, Right : in out E);
end Swaps;
-------------------------
--  swaps.adb
-------------------------
package body Swaps is
  procedure Generic_Swap (Left, Right : in out E) is
    Temporary : E;
  begin
    Temporary := Left;
    Left := Right;
    Right := Temporary;
  end Generic_Swap;
end Swaps;

现在假设我想实现一个专门的String_Swap 过程来交换字符串,并将它提供给我包的所有用户。我可以在swaps.adb 的正文声明中添加以下内容:

procedure String_Swap is new Generic_Swap (String);

但是,如果我在swaps.ads 中的规范中没有添加任何内容,则没有包可以使用此过程。例如:

-------------------------
--  main.adb
-------------------------
with Swaps; use Swaps;
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
   First  : String := "world!";
   Second : String := "Hello, ";
begin
   String_Swap (First, Second); -- #Error: String_Swap is undefined#
   Put_Line (First);
   Put_Line (Second);
end Main;

我已尝试将程序的类型添加到规范中:

procedure String_Swap (Left, Right : in out String);

但随后 Ada 抱怨此规范缺少正文,并且 swaps.adb 中的定义与它冲突。

【问题讨论】:

    标签: generics ada


    【解决方案1】:

    我解决这个问题的方法是使用子包:

    with Ada.Strings.Unbounded;
    
    package Swaps.Instances is
       procedure Swap is new Generic_Swap (Element => Character);
       procedure Swap is new Generic_Swap (Element => Ada.Strings.Unbounded.Unbounded_String;
       ...
    end Swaps.Instances;
    

    请注意,可以编写处理不定类型的泛型:

    generic
       type Element (<>) is private;
    

    并将Generic_Swap 的正文更改为

    procedure Generic_Swap (Left, Right : in out Element) is
       Temp : constant Element := Left;
    begin -- Generic_Swap
       Left := Right;
       Right := Temp;
    end Generic_Swap;
    

    但要使用它,实际对象必须是不受约束的或具有相同的子类型。

    【讨论】:

      【解决方案2】:

      您不能将泛型用于String 类型,因为它是不受约束的类型。但是让我们改用Ada.Strings.Unbounded.Unbounded_String

      你要做的是:

      1. 在包规范中发布合适过程的规范。
      2. 通过调用内部实例化的通用过程来实现公共过程。
      with Ada.Strings.Unbounded;
      
      package Swaps is
         generic
            type Element_Type is private;
         procedure Generic_Swap (Left, Right : in out Element_Type);
      
         procedure Swap (Left, Right : in out Ada.Strings.Unbounded.Unbounded_String);
      end Swaps;
      
      package body Swaps is
      
         procedure Generic_Swap (Left, Right : in out Element_Type) is
            Temporary : Element_Type;
         begin
            Temporary := Left;
            Left      := Right;
            Right     := Temporary;
         end Generic_Swap;
      
         procedure Swap_Unbounded_Strings is
           new Generic_Swap (Element_Type => Ada.Strings.Unbounded.Unbounded_String);
      
         procedure Swap (Left, Right : in out Ada.Strings.Unbounded.Unbounded_String) is
         begin
            Swap_Unbounded_Strings (Left  => Left,
                                    Right => Right);
         end Swap;
      end Swaps;
      

      但总的来说,我更喜欢将泛型的实例化与这些泛型的规范和实现完全分开。

      【讨论】:

      • 请注意,除了调用实例化的过程之外,您还可以使用重命名为主体来实现交换:procedure Swap (Left, Right : in out Ada.Strings.Unbounded_String) renames Swap_Unbounded_Strings;
      • 看起来我遗漏了Unbounded:应该是Ada.Strings.Unbounded.Unbounded_String;
      【解决方案3】:

      Swaps 的用户只能看到规范。由于规范中没有任何关于 String_Swap 的内容,因此对包 body 的任何修改都不会产生任何影响。

      如果你想“实现一个专门的String_Swap 交换字符串的过程”,你必须将它包含在规范中:

      package Swaps is
         generic
            type E is private;
         procedure Generic_Swap(Left, Right : in out E);
         procedure String_Swap is new Generic_Swap(String);
      end Swaps;
      

      结果证明这是一个不好的例子:当使用-gnatl 编译时,我们得到

       1. package Swaps is
       2.    generic
       3.       type E is private;
       4.    procedure Generic_Swap(Left, Right : in out E);
       5.    procedure String_Swap is new Generic_Swap(String);
                                                       |
          >>> actual for "E" must be a definite subtype
      
       6. end Swaps;
      

      这是因为String类型是不定的,即特定的String具有特定的长度,并且只能分配给另一个相同长度的String(或String的切片) ;因此,即使您的过程Main 是在没有使用泛型的情况下写出的,它也会在运行时因约束错误而失败。查看Ada.Strings.UnboundedARM A.4.5

      所以,尝试使用明确的类型:

      package Swaps is
         generic
            type E is private;
         procedure Generic_Swap(Left, Right : in out E);
         procedure Character_Swap is new Generic_Swap(Character);
      end Swaps;
      

      很遗憾,

       1. package Swaps is
       2.    generic
       3.       type E is private;
       4.    procedure Generic_Swap(Left, Right : in out E);
       5.    procedure Character_Swap is new Generic_Swap(Character);
             |
          >>> warning: cannot instantiate "Generic_Swap" before body seen
          >>> warning: Program_Error will be raised at run time
      
       6. end Swaps;
      

      解决方案必须单独实例化:也许在库级别,

      with Swaps;
      procedure Character_Swap is new Swaps.Generic_Swap(Character);
      

      让您的用户根据自己的意愿实例化泛型会容易得多。

      【讨论】:

      • 那么您的结论是不可能在包中定义Generic_Swap(Character) 并将其公开给包的所有用户吗?
      • 您可以创建一个实例化包,将通用包作为子包。然后可以对用户隐藏通用包,或者如果他们需要进行额外的实例化,他们可以“使用/使用”它。
      【解决方案4】:

      我已尝试将程序的类型添加到规范中:

      procedure String_Swap (Left, Right : in out String);
      

      但随后 Ada 抱怨该规范缺少正文,并且 swaps.adb 中的定义与之冲突。

      这是个好主意,但实例化无法完成过程声明(编译器会这么说)。您需要的是使用程序重命名:

         procedure String_Swap_Inst is new Generic_Swap (String);
      
         procedure String_Swap (Left, Right : in out String)
           renames String_Swap_Inst;
      

      此外,为了能够将 String 与泛型一起使用,您需要对其进行一些更改以允许不受约束的类型:

      package Swaps is
         generic
            type E (<>) is private;
         procedure Generic_Swap (Left, Right : in out E);
      
         procedure String_Swap (Left, Right : in out String);
      end Swaps;
      
      package body Swaps is
        procedure Generic_Swap (Left, Right : in out E) is
          Temporary : E := Left;
        begin
          Left := Right;
          Right := Temporary;
         end Generic_Swap;
      
         procedure String_Swap_Inst is new Generic_Swap (String);
      
         procedure String_Swap (Left, Right : in out String)
           renames String_Swap_Inst;
      end Swaps;
      

      当然,你只能用这种方式交换等长的字符串,否则你会得到一个 Constraint_Error 就像在赋值中一样:

      X : String (1 .. 2) := "123";  --  Constraint_Error!!!
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-12-19
        • 2018-01-09
        • 1970-01-01
        • 2017-07-09
        • 1970-01-01
        • 1970-01-01
        • 2020-08-27
        • 2015-06-11
        相关资源
        最近更新 更多