Generic parameters

The generic unit declares generic formal parameters, which can be:

  • objects (of mode in or in out but never out)
  • types
  • subprograms
  • instances of another, designated, generic unit.

When instantiating the generic, the programmer passes one actual parameter for each formal. Formal values and subprograms can have defaults, so passing an actual for them is optional.

Generic formal objects

Formal parameters of mode in accept any value, constant, or variable of the designated type. The actual is copied into the generic instance, and behaves as a constant inside the generic; this implies that the designated type cannot be limited. It is possible to specify a default value, like this:

generic
   Object : in Natural := 0;

For mode in out, the actual must be a variable. One limitation with generic formal objects is that they are never considered static, even if the actual happens to be static. If the object is a number, it cannot be used to create a new type. It can however be used to create a new derived type, or a subtype:

generic
   Size : in Natural := 0;
package P is
   type T1 is mod Size; -- illegal!
   type T2 is range 1 .. Size; -- illegal!
   type T3 is new Integer range 1 .. Size; -- OK
   subtype T4 is Integer range 1 .. Size; -- OK
end P;

The reason why formal objects are nonstatic is to allow the compiler to emit the object code for the generic only once, and to have all instances share it, passing it the address of their actual object as a parameter. This bit of compiler technology is called shared generics. If formal objects were static, the compiler would have to emit one copy of the object code, with the object embedded in it, for each instance, potentially leading to an explosion in object code size (code bloat).

(Note to C++ programmers: in C++, since formal objects can be static, the compiler cannot implement shared generics in the general case; it would have to examine the entire body of the generic before deciding whether or not to share its object code. In contrast, Ada generics are designed so that the compiler can instantiate a generic without looking at its body.)

Generic formal types

The syntax allows the programmer to specify which type categories are acceptable as actuals. As a rule of thumb: The syntax expresses how the generic sees the type, i.e. it assumes the worst, not how the creator of the instance sees the type.

This is the syntax of RM 12.5

 formal_type_declaration ::=
   type defining_identifier[discriminant_part] is formal_type_definition;
 
 formal_type_definition ::= formal_private_type_definition
                          | formal_derived_type_definition
                          | formal_discrete_type_definition
                          | formal_signed_integer_type_definition
                          | formal_modular_type_definition
                          | formal_floating_point_definition
                          | formal_ordinary_fixed_point_definition
                          | formal_decimal_fixed_point_definition
                          | formal_array_type_definition
                          | formal_access_type_definiton
                          | formal_interface_type_definition

This is quite complex, so some examples are given below. A type declared with the syntax type T (<>) denotes a type with unknown discriminants. This is the Ada vernacular for indefinite types, i.e. types for which objects cannot be declared without giving an initial expression. An example of such a type is one with a discriminant without default, another example is an unconstrained array type.

In the body we can only use the operations predefined for the type category of the formal parameter. That is, the generic specification is a contract between the generic implementor and the client instantiating the generic unit. This is different to the parametric features of other languages, such as C++.

It is possible to further restrict the set of acceptable actual types like so:

Generic formal type Acceptable actual types
type T (<>) is Definite or indefinite types (loosely speaking: types with or without discriminants, but other forms of indefiniteness exist)
type T (D : DT) is Types with a discriminant of type DT (it is possible to specify several discriminants, too)
type T is Definite types (loosely speaking types without a discriminant or with a discriminant with default value)

Source: Ada Programming


Date
February 28, 2024