Generics
It would be easy to write a package that creates a single stack of 10 Integers,
and lets us Push and Pop on it. However, the code would be about the same
regardless of the size of the stack, and regardless of the type of objects on
the stack. For example, a second package that creates a stack of 50 Dates
would look about the same. We can write one generic package, and instantiate
it for any size and almost any type we need. The specification is:
generic
Size : Positive;
type Dummy is private;
package Stack_Package is
procedure Push(Object : in Dummy);
function Pop return Dummy;
end Stack_Package;
Since both Size and type Dummy are generic, both must be specified when we
instantiate the package:
package Stack_Of_10_Integers is new Stack_Package(10, Integer);
package Stack_Of_50_Dates is new Stack_Package(50, Date);
Generic objects like Size may be given default values. A default value is used
only if we don't specify that parameter in the instantiation. For example:
generic
Size : Positive := 10;
type Dummy is private;
package Stack_Package is
procedure Push(Object : in Dummy);
function Pop return Dummy;
end Stack_Package;
package Stack_Of_10_Integers is new Stack_Package(Dummy => Integer);
package Stack_Of_50_Dates is new Stack_Package(50, Date);
In the above example, we specified a generic type with
type Dummy is private;
Actually, there are a number of ways to specify a generic type. How we specify
the generic type determines what actual types we may use in the instantiation,
and what we may do with objects of that type in the body of our generic package
or subprogram. Let's look at the alternatives.
If the generic part says type Dummy is private; then, in our subprogram or
package body, what we may do with objects of type Dummy is similar to what we
may do with objects of a private type outside an ordinary package: create them,
assign them, test them for equality or inequality, and call other subprograms
already available to us. Although this list is short, we can instantiate with
almost any type at all: Dates, Floats, Strings (see the next paragraph),
Rainbow_Colors, and any type for which we can assign objects and test them for
equality. That means any type except a limited type. We can't instantiate
Stack_Package for type Ada.Text_IO.File_Type (a limited private type), or for
task types (a limited type discussed later), because in our package body we're
allowed to assign and test for equality objects of type Dummy.
We can't create objects of an unconstrained type like String. So, in Ada 83,
if our package or subprogram creates objects of type Dummy, we can't
instantiate it for type String; we must use a constrained subtype of String.
Thus we could write subtype Name is String(1 .. 30); and then instantiate our
package with package Stack_Of_50_Names is new Stack_Package(50, Name);. In
Ada 95 we can instantiate with an unconstrained type if and only if the formal
type name in the generic part of the specification (Dummy in our example) is
followed by (<>). In that case, the subprogram or package body must initialize
any objects of that type that it creates, in order to constrain them.
We can instantiate our package or subprogram even for limited types like
Ada.Text_IO.File_Type if the generic part says type Dummy is limited private;.
However, the only things our package or subprogram body could do with objects
of that type is create them and call other subprograms already available to us.
Ordinarily, that's not very useful.
If the generic part says type Dummy is (<>); then we can instantiate the
package or subprogram for any discrete type. That means any enumeration or
integer type: Character, Boolean, Counter, No_Of_Apples, etc. In the body,
attributes like 'First are available.
If the generic part says type Dummy is range <>; then we can instantiate for
any integer type. In the body, we can do things that can be done with all
integer types, such as add, etc.
If the generic part says type Dummy is digits <>; then we can instantiate for
any floating point type, such as Float or the Real we created.
If the generic part says type Dummy is delta <>; then we can instantiate for
any fixed point type, to be discussed in the section on More Records and Types.
In Ada 95, some additional forms are allowed; beginners shouldn't expect to
grasp them until later. If the generic part says type Dummy is tagged private;
then we can instantiate with any tagged type, to be covered in the section on
More Records and Types.
If the generic part says type Dummy is new X; or type Dummy is new X
with private; then we can instantiate with type X or with any type derived from X.
If with private is included, then we must instantiate with a tagged type.
In both cases above, as with type Dummy is private; and type Dummy
is limited private; we can follow Dummy with (<>) to
allow instantiating with unconstrained types. The body must initialize
any objects of an unconstrained type that it creates. We can also replace
is in the generic part with is private to allow instantiating with
abstract types, to be covered later.
If the generic part says type Dummy is mod <>; then we can instantiate with any
modular type, to be covered in the section on More Records and Types.
Finally, if the generic part says with package Dummy is new P
(<>); where P is the name of a generic package, then we can
instantiate with any package that we obtained by instantiating P.
A generic package body looks just like an ordinary package body:
package body Stack_Package is
procedure Push(Object : in Dummy) is separate;
function Pop return Dummy is separate;
end Stack_Package;
separate (Stack_Package)
procedure Push(Object : in Dummy) is
...
end Push;
(etc.)
Note that we simplified our Stack_Package example by ignoring the possibilities
of stack overflow and underflow. Ordinarily, these would be handled by
exceptions.
The specification of a generic subprogram must be given separately from the
body. For example, we can't eliminate the highlighted line in the following
function:
generic
type Dummy_Float is digits <>;
type Dummy_Vector is array (Integer range <>) of Dummy_Float;
function Sum(V : in Dummy_Vector) return Dummy_Float;
function Sum(V : in Dummy_Vector) return Dummy_Float is
Answer : Dummy_Float := 0.0;
begin
for I in V'Range loop
Answer := Answer + V(I);
end loop;
return Answer;
end Sum;
We can instantiate Sum and call our instantiation as follows:
type Vector is array(Integer range <>) of Float;
V1 : Vector(1 .. 10);
X : Float;
function Sumv is new Sum(Float, Vector);
...
X := Sumv(V1);
Question
generic
type Dummy is range <>;
procedure Display(Item : in Dummy);
type No_Of_Apples is new Integer;
type Counter is range 0 .. 1_000_000;
type Answer is (Yes, No, Maybe);
procedure Display_Apples is new Display(No_Of_Apples); -- 1
procedure Display_Counters is new Display(Counter); -- 2
procedure Display_Answers is new Display(Answer); -- 3
Which commented line in the above program segment is
illegal?
1 - No, when the generic part says type Dummy is range <>;, the procedure may be
instantiated for any integer type. No_Of_Apples is a type derived from
Integer, so it's an integer type.
2 - No, when the
generic part says type Dummy is range <>;, the procedure may be
instantiated for any integer type. The definition of the user-defined
type Counter has the word range, so Counter is an integer type.
3 - You're
right! When the generic part says type Dummy is range
<>;, the procedure may be instantiated for any integer type.
Answer is a discrete type, but not an integer type.
< prev next >