One of the features of Ada that is more powerful than C++ templates is the persistence of type conformity throughout the entire generic model. Modula-3 comes pretty close to Ada in this respect because it also requires type-checking (and parameter conformity) of instantiated units. This benefit of Ada is especially important when creating generic units that approximate what we see in C++. That is, in C++, we can instantiate a class with one or more entire templates. Although the C++ syntax is simpler, the conformity checking is less reliable than Ada or Modula-3 where we can instantiate a module (Ada package) with an entire module as the generic formal parameter. In doing this with Ada, you will sometimes have to include some type (view) conversion in the design. We provide the following abbreviated example of a numeric package. Some of the features of the full example have been eliminated in the interest of brevity, but it should be easily seen how this can be useful in creating reusable software components. Also, I realize the original discussion dealt with class-wide types, but Ada's consistency model suggests that the same principles would apply even in the example shown. with Numeric_Model; generic with package Number_Ops is new Numeric_Model(<>); package Number_Operations is type Real is private; type Real_Reference is access all Real; function Zero return Real; function "+" (L, R : Real) return Real; function "-" (L, R : Real) return Real; function "*" (L, R : Real) return Real; function "/" (L, R : Real) return Real; function "=" (L, R : Real) return Boolean; function ">" (L, R : Real) return Boolean; function "<" (L, R : Real) return Boolean; function ">=" (L, R : Real) return Boolean; function "<=" (L, R : Real) return Boolean; function SQRT (V : Real) return Real; -- More Math Operations on Real function Machine_Number (V : Real) return Real; -- More attribute operations as functions function Get return Real; procedure Put (V : Real; Fore, Aft : Natural); -- Specialize the exceptions for this type Real_Constraint_Error : exception; Divide_By_Zero : exception; Outside_Base_Range : exception; private type Real is new Number_Ops.Number; end Number_Operations; In this example we are defining our own Real number type with associated operations. It is possible to actually declare the full type of Real as a tagged type or a simple record using this model, thereby encapsualting some unique properties for a specialized numeric application. The with statement refers to a generic package. In this case, the generic package only contains generic formal parameters for a set of functions. Here is the specification for package Numeric_Model. generic type Number is digits <> with function "+" (L, R : Number) return Number is <> with function "*" (L, R : Number) return Number is <> with function "-" (L, R : Number) return Number is <> with function "/" (L, R : Number) return Number is <> with function "abs" (N : Number) return Number is <> with function "=" (L, R : Number) return Boolean is <> with function ">" (L, R : Number) return Boolean is <> with function "<" (L, R : Number) return Boolean is <> with function ">=" (L, R : Number) return Boolean is <> with function "<=" (L, R : Number) return Boolean is <> package Numeric_Model is end Numeric_Model; At first glance, one might conclude that this is not useable because it has no body. That is exactly what makes it so powerful. It is also what makes it possible to preserve type and profile conformity across the entire compilation process. In this respect it is slightly more complex syntax than that of Modula-3 but also checked at earlier stages of the design process. Notice that the statement, with package Number_Ops is new Numeric_Model(<>); looks like an instantiation itself. In fact, the package for which this is a generic formal parameter will always refer to Number_Ops not to Numeric_Model just as if Number_Ops was a true instantiation. This is frequently a point of confusion for Ada programmers but once you understand it, it becomes quite sensible. How else could we preserve full profile conformity checking throughout the entire compilation process? Some of us have taken to calling this kind of generic, one with only generic formal parameters and no body, as a "generic formal model." We need to find some agreement in the Ada community on what to finally call it so it can be referred to in the literature more concisely. Our purpose in using this package might include declaring our own versions of division and equality checking. This is not an unusual requirement for floating point numbers. Here is a simple procedure with some of the calls "stubbed out" to illustrate the idea. with Numeric_Model; with Number_Operations; procedure Test_Own_Float_Operations is type Own_Float is digits 12; function Divide (L, R : Own_Float) return Own_Float is Result : Own_Float := 0.0; begin -- your own division operation; return Result; end Divide; function Is_Equal (L, R : Own_Float) return Boolean is Result : Boolean := False; begin -- your own equality operator return Result; end Is_Equal; package Math_Ops is new Numeric_Model (Number => Own_Float, "=" => Is_Equal, "/" => Divide); package Own_Math is new Number_Operations (Number_Ops => Math_Ops); use type Own_Math.Real; Data : Own_Math.Real := Own_Math.Zero; Left, Right : Own_Math.Real := Own_Math.Zero; begin Data := Left / Right; Data := Left * Right; if Left = Right then null; end if; end Test_Own_Float_Operations; You will observe that we have two instantiations, one for the "generic formal model" and another for the actual generic package. The model is instantiated with the operations. The generic package is instantiated with an instance of the model. A more interesting case of this would be one where the generic package has multiple generic formal package parameters. This latter case would suggest the potential for generic reusable frameworks, a larger idea than that used in Ada 83. It makes Ada every bit as powerful as the corresponding capability in C++, but safer, much safer. This has already been a long posting, and I apologize for that. However, the discussion originally centered around the notion of type and view conversions. Therefore, the following package body demonstrates at least one example of how the type conversions become useful in the actual implementation. with Ada.Numerics.Generic_Elementary_Functions; with Ada.Text_IO; package body Number_Operations is package GEF is new Ada. Numerics. Generic_Elementary_Functions (Float_Type => Real); package FIO is new Ada.Text_IO.Float_IO(Num => Real); F_Zero : constant := 0.0; function Zero return Real is begin return F_Zero; end Zero; function "+" (L, R : Real) return Real is begin return Real(Number_Ops."+"(Number_Ops.Number(L), Number_Ops.Number(R))); end "+" ; function "-" (L, R : Real) return Real is begin return Real(Number_Ops."-"(Number_Ops.Number(L), Number_Ops.Number(R))); end "-" ; function "*" (L, R : Real) return Real is begin return Real(Number_Ops."*"(Number_Ops.Number(L), Number_Ops.Number(R))); end "*" ; function "/" (L, R : Real) return Real is begin if R = F_Zero then raise Divide_By_Zero; else return Real(Number_Ops."/"(Number_Ops.Number(L), Number_Ops.Number(R))); end if; end "/" ; function "=" (L, R : Real) return Boolean is begin return Number_Ops."="(Number_Ops.Number(L), Number_Ops.Number(R)); end "=" ; function ">" (L, R : Real) return Boolean is begin return Number_Ops.">"(Number_Ops.Number(L), Number_Ops.Number(R)); end ">" ; function "<" (L, R : Real) return Boolean is begin return Number_Ops."<"(Number_Ops.Number(L), Number_Ops.Number(R)); end "<" ; function ">=" (L, R : Real) return Boolean is begin return Number_Ops.">="(Number_Ops.Number(L), Number_Ops.Number(R)); end ">=" ; function "<=" (L, R : Real) return Boolean is begin return Number_Ops."<="(Number_Ops.Number(L), Number_Ops.Number(R)); end "<=" ; function SQRT (V : Real) return Real is begin return GEF.SQRT(V); end SQRT; function Machine_Number (V : Real) return Real is begin return Real'Machine(V); -- renaming does not work for this end Machine_Number; function Get return Real is Result : Real; begin FIO.Get(Result); return Result; end Get; procedure Put (V : Real; Fore, Aft : Natural) is begin FIO.Put(V, Fore => Fore, Aft => Aft); end Put; end Number_Operations; I hope this helps a little bit in understanding generic formal package parameters and some of the issues related to type conversion, where and when.

Contributed by: Richard Riehle

Contributed on: May 14, 1999

License: Public Domain

Back