A generic subprogram is not an ordinary subprogram: for example it cannot be called; it is rather a template for all (ordinary) subprograms that can be obtained by associating specific actual parameters with the generic formal parameters. Similarly a generic package is a template for (ordinary) packages.
A specific program unit that corresponds to a given template is created by a declaration called a generic instantiation. This has the effect of creating a named instance. In the case of a subprogram, for example, this named instance can then be called in the usual way. Thus, apart from parameterization, generic declaration is for nongeneric program units what a type declaration is for data objects:
data objects | program units | ||||
---|---|---|---|---|---|
|
|
|
In this section...
12.2.1 Generic Formal Parts |
generic type ITEM is private; |
declares the generic formal type ITEM. We find this generic formal part in the declaration of the following generic procedure:
generic type ITEM is private; procedure EXCHANGE(LEFT, RIGHT : in out ITEM); procedure EXCHANGE(LEFT, RIGHT : in out ITEM) is OLD_LEFT : constant ITEM := LEFT; begin LEFT := RIGHT; RIGHT := OLD_LEFT; end; |
In this example, LEFT and RIGHT are the ordinary (that is, nongeneric) parameters: each procedure obtained by instantiation of this template will have these parameters, which are subject to dynamic replacement. In contrast, the type ITEM given in the generic formal part is a generic formal parameter which is to be substituted for at compilation time. This generic parameter may appear in the body of the generic subprogram; here it is used in the declaration of the constant OLD_LEFT.
procedure SWAP_INT is new EXCHANGE(ITEM => INTEGER); procedure SWAP_CHAR is new EXCHANGE(ITEM => CHARACTER); procedure SWAP_COLOR is new EXCHANGE(ITEM => COLOR); |
Each resultant program unit is an ordinary procedure, applicable to actual parameters of the corresponding type. The resulting procedure specifications are as follows:
procedure SWAP_INT (LEFT, RIGHT : in out INTEGER); procedure SWAP_CHAR (LEFT, RIGHT : in out CHARACTER); procedure SWAP_COLOR (LEFT, RIGHT : in out COLOR); |
In each case, the name of the generic procedure has been replaced by the name given in the instantiation, the formal type by the actual type, and everything else remains the same - the names and modes of the formal parameters of the instantiation are the same as those of the generic procedure.
The fact that these procedures are obtained by generic instantiation does not preclude overloading of their names:
procedure SWAP is new EXCHANGE(ITEM => INTEGER); procedure SWAP is new EXCHANGE(ITEM => CHARACTER); procedure SWAP is new EXCHANGE(ITEM => COLOR); |
Calls of these procedures will be as usual; for example:
SWAP(I, J); -- for integers SWAP(SHADE, TINT); -- for colors |
In general, a generic instantiation for a procedure has the form
procedure identifier is new name [(generic_association {, generic_association})]; |
The syntax of generic associations is similar to that of parameter associations for subprogram calls. Note that both named associations and positional associations are possible, as usual. Thus our previous example can be written equivalently in positional form as:
procedure SWAP is new EXCHANGE(INTEGER); procedure SWAP is new EXCHANGE(CHARACTER); procedure SWAP is new EXCHANGE(COLOR); |
A program unit obtained by generic instantiation can be viewed as a copy of the corresponding generic unit where each formal parameter has been replaced by the corresponding actual parameter. For example, the declaration of SWAP_INT produces a procedure equivalent to
procedure SWAP_INT(LEFT, RIGHT : in out INTEGER) is OLD_LEFT : constant INTEGER := LEFT; begin LEFT := RIGHT; RIGHT := OLD_LEFT; end; |
A generic instantiation need not appear in the same declarative part as the corresponding generic declaration - it may appear at any point where the name of the generic unit is visible.
The rule followed for the identification of names within a generic unit is similar to that used for subprograms: All non-local identifiers of the body of a generic unit are identified in the context of the generic declaration. In contrast, the actual parameters given in the generic associations must be interpreted in the context of the generic instantiation.
Note that this rule differs from a simple textual substitution. In the latter case all identifiers, including non-local ones, would be interpreted in the context of the instantiation. Hence it would not be possible in general to obtain the effect of generic program units by a simple (context-free) macro facility; and this was our reason for referring to context-sensitive macro expansion, earlier in the introduction.
To summarize, the generic parameter names (and the name of the unit itself) are the only unresolved identifiers in the body of a generic program unit. For any generic instantiation, replacements must be provided for all generic parameters. These replacements are to be interpreted in the context of the instantiation.
In general when a generic formal type is specified as being private, no operations are assumed to be available aside from assignment, the predefined comparison for equality and inequality, and certain attributes such as SIZE. Furthermore, if the generic formal type is declared as limited private, then not even assignment and the comparison for equality and inequality are available.
For such types - whether limited or not - each operation that is used within the generic body must be specified by another generic formal parameter, namely, a generic formal subprogram. As an example, consider the generic function:
generic type ELEM is limited private; with function "*" (LEFT, RIGHT : ELEM) return ELEM; function SQUARING(X : ELEM) return ELEM; function SQUARING(X : ELEM) return ELEM is begin return X * X; end; |
Since nothing is known a priori about the type ELEM, it would not be possible to write X * X if the specification of "*" were not provided explicitly by a generic formal parameter (this specification is prefixed by the reserved word with to distinguish it syntactically from the generic function itself and thus to show that we are still in the generic formal part):
with function "*" (LEFT, RIGHT : ELEM) return ELEM;
Instances of SQUARING are created by supplying the corresponding actual parameters. For example, for the instantiation
function SQUARE is new SQUARING(INTEGER, "*");
the operation "*" used in the body is the operation defined as
function "*" (LEFT, RIGHT : INTEGER) return INTEGER;
that is, the normal integer multiplication. Thus the generic instantiation produces a function body equivalent to the following:
function SQUARE(X : INTEGER) return INTEGER is begin return X * X; end; |
Of course, other instantiations are possible. For example, we may want to use SQUARING for matrices, to extend the existing component-by- component multiplication
function MULT(X, Y : MATRIX) return MATRIX;
Thus with the generic instantiation
function SQUARE is new SQUARING(ELEM => MATRIX, "*" => MULT);
we obtain a function that performs component-by-component squaring of a matrix.
type BASE is (<>); -- discrete type INT is range <>; -- integer type FIXED is delta <>; -- fixed point type MASS is digits <>; -- floating point |
In each case, the box symbolizes what is not there, what is left unspecified. So for example, the type INT will stand for any integer type, with any possible range; the type BASE will stand for any discrete type, whether an enumeration or an integer type.
Each formal type specifies minimum requirements for the corresponding actual types, and the specification and body of the generic unit can rely on these minimal assumptions. For example, for a formal type such as BASE, we can count on the availability of all properties of discrete types:
As an example, consider the treatment of sets. In Pascal, sets are dealt with by means of a specific language feature. In Ada a specific feature is unnecessary since sets can be defined by a generic package:
generic type BASE is (<>); -- any discrete type package ON_SETS is type SET is array (BASE) of BOOLEAN; EMPTY : constant SET := (BASE => FALSE); FULL : constant SET := (BASE => TRUE); type SEQUENCE is array (POSITIVE range <>) of BASE; function SET_OF(S : SEQUENCE) return SET; function "+" (LEFT : SET; RIGHT : SET) return SET; -- set union function "+" (LEFT : SET; RIGHT : BASE) return SET; -- element insertion -- other set operations: end ON_SETS; |
The declaration of the formal type BASE requires the actual type to be discrete and we are clearly using this assumption when using BASE as index subtype for the type SET; and similarly when using BASE as a choice for the aggregates that give the values of EMPTY and FULL.
We can now use ordinary set operations with a chosen discrete type by instantiation of this generic package. For example, for the enumeration type:
type DAY is (MON, TUE, WED, THU, FRI, SAT, SUN);
we can create the instance
package DAY_SETS is new ON_SETS(BASE => DAY);
and then
use DAY_SETS; S : SET; ... S := SET_OF((SUN, TUE, WED)); S := S + SAT; ... S := S + SET_OF((MON, THU)); |
The actual parameter of SET_OF is a SEQUENCE - an array of values of the base type, indexed by the positive numbers 1, 2, and 3 - and the function constructs the corresponding set. The next statements then use set addition. A sketch of the generic body is given below:
package body ON_SETS is ... function SET_OF(S : SEQUENCE) return SET is RESULT : SET := EMPTY; begin for N in S'RANGE loop RESULT(S(N)) := TRUE; end loop; return RESULT; end; function "+" (LEFT : SET; RIGHT : SET) return SET is begin return LEFT or RIGHT; end; function "+" (LEFT : SET; RIGHT : BASE) return SET is RESULT : SET := LEFT; begin RESULT(RIGHT) := TRUE; return RESULT; end; ... end ON_SETS; |
On top of these type patterns with boxes we can construct other type patterns by means of array type definitions and access type definitions. For example, a generic sorting procedure could be specified as:
generic type ITEM is private; type INDEX is (<>); type ROW is array (INDEX range <>) of ITEM; with function "<" (LEFT, RIGHT : ITEM) return BOOLEAN; procedure SORT(R : in out ROW); |
An instantiation would have to meet the minimum requirements established by this generic formal part. For example, consider:
type MEETING is ... -- some record type type AGENDA is array (DAY range <>) of MEETING; function LESS_IMPORTANT(X, Y : MEETING) return BOOLEAN; procedure ORDER is new SORT(ITEM => MEETING, INDEX => DAY, ROW => AGENDA, "<" => LESS_IMPORTANT); MY_WEEK : AGENDA(MON .. FRI); ... ORDER(MY_WEEK); |
The matching types are clearly shown by the named parameter associations. Consider for example the definition of the formal type ROW:
array (INDEX range <>) of ITEM;
After replacing ITEM by MEETING, and INDEX by DAY, we obtain the type definition
array (DAY range <>) of MEETING;
which is exactly the way AGENDA is defined, so that AGENDA matches ROW correctly. Similarly, the function LESS_IMPORTANT matches the operator "<", once we have replaced ITEM by MEETING.
The types used in this example are certainly not the usual types we find in ordinary programming, and yet the instantiation works because we have limited our required assumptions to the minimum. We did not assume anything about ITEM, apart from the ability to assign, which is needed for sorting; the only assumption that we made about the index type was that it was discrete (assuming an integer type would have been overspecification); finally we assumed the existence of an order relation for ITEMS. Had we assumed more than is strictly needed (for example, real items, integer indices) the instantiation would have failed.
generic type ELEM is private; with function "*" (LEFT, RIGHT : ELEM) return ELEM is <>; function SQUARING(X : ELEM) return ELEM; function SQUARING(X : ELEM) return ELEM is begin return X * X; end; |
The specification of the formal function "*" indicates, by means of a box, that a corresponding actual parameter need not be present in instantiations of the generic function SQUARING. For example, we can write:
function SQUARE is new SQUARING(INTEGER);
As usual, the box stands for what is missing - in this case it is a function whose specification is obtained by replacing ELEM by INTEGER:
function "*" (LEFT, RIGHT : INTEGER) return INTEGER;
and since there is such an operation for the type INTEGER, this integer multiplication is used - by default - in place of the actual parameter.
This form of default corresponds to very good programming practice: We have a natural notation, such as "*" for multiplication, and we expect users to make natural use of this notation. With this form we can specify the box for the corresponding formal parameter so that instantiations will select by default the "natural" operation.
Naturally, we can always override the default by providing an explicit actual parameter:
function SQUARE is new SQUARING(ELEM => MATRIX, "*" => MULT);
There is another form of default, which names the default actual subprogram; for example:
with procedure STEP(X : in out INTEGER) is INCREMENT;
where the procedure INCREMENT is a procedure visible at the place of the formal parameter declaration, and whose profile matches that of the formal procedure:
procedure INCREMENT(N : in out INTEGER);
For this second form of default, the actual parameter is to be found in the context of the generic declaration, whereas in the case of the box, the default was to be found in the context of the generic instantiation.