Generic Subprograms & Formal Types
Write generic procedures and functions, instantiate them (including overloaded instances), choose the right formal type category (private, limited private, (<>) indefinite, discrete (<>), range <>, mod <>, digits <>, delta <>, array, access), and use formal subprograms with 'is <>' defaults.
Generics are Ada's mechanism for static polymorphism: you write an algorithm once, parameterized by types, values and subprograms, and instantiate it for concrete types. Unlike C++ templates, an Ada generic is checked completely when it is compiled, against a contract stated by its formal parameters — so instantiation never produces surprising errors deep inside the generic body.
Declaring and Instantiating
A generic unit starts with 'generic' and a list of formal parameters, followed by an ordinary subprogram or package declaration: 'generic type Item is private; procedure Generic_Swap (A, B : in out Item);'. The body is written once. Instantiation creates a real subprogram: 'procedure Swap is new Generic_Swap (Integer);'. Several instances can share a name and overload each other (Swap for Integer and for Character). Actual parameters may be given positionally or by name: new Generic_Max (String, "<" => ">").
• generic <formals> procedure/function/package ... • procedure P is new G (Actuals); — instantiation. • The generic body is fully checked once, independent of instances.
Formal Type Categories — the Contract
The form of a formal type states what the generic may do with it and which actuals are accepted. 'type T is private' — any definite type with assignment and =. 'type T is limited private' — even limited types, but no assignment in the body. 'type T (<>) is private' — also indefinite types such as String or T'Class (the body can't declare uninitialized objects). 'type T is (<>)' — any discrete type ('First, 'Succ, loops). 'type T is range <>' — signed integers (arithmetic). 'type T is mod <>', 'digits <>', 'delta <>' — modular, floating and fixed point. Formal array types ('type Vector is array (Index range <>) of Element') and formal access types complete the set. The more capabilities the generic requires, the fewer types it accepts.
Pick the weakest category that supports what the body needs.
• private / limited private / (<>) private. • (<>) discrete, range <>, mod <>, digits <>, delta <>. • Formal arrays, access types and derived types. • Weakest sufficient contract = most reusable generic.
Formal declaration Accepts Body may use
type T is limited private; any definite type parameters only
type T is private; definite, non-limited :=, =
type T (<>) is private; also String, T'Class :=, = (no uninit. objects)
type T is (<>); any discrete type 'First 'Succ 'Pos, loops
type T is range <>; signed integer types + - * / mod ...
type T is mod <>; modular types + and or xor ...
type T is digits <>; floating point types real arithmetic
type T is new Parent with private; descendants of Parent Parent's components/opsFormal Subprograms
'with function "<" (L, R : Item) return Boolean is <>;' requires an operation. 'is <>' means: if no actual is given, use the subprogram with that name and profile visible at the instantiation (so Max for Integer picks Integer's "<"). A default can also name a specific subprogram ('is Default_Image'), or 'is null' for procedures. Passing a different actual changes the behavior — Generic_Max instantiated with ">" returns the minimum.
• with function F (...) return T; — required operation. • is <> — default to the visible matching subprogram. • Passing different actuals customizes behavior.
Code Examples
Swap is instantiated twice (overloaded). Generic_Max uses (<>) so String is accepted, and its "<" defaults to the visible one unless overridden. Generic_Sum works for any discrete index and integer element type (10 + 20 + 30 = 60). GNAT warns only that Thu and Fri are unused. Output: After Swap: A = 2, B = 1, C1 = y, C2 = x Max (3, 9) = 9 Max (2.5, -1.0) = 2.50000E+00 Generic_Max with "<" => ">" gives the minimum: abc Total score Mon..Wed = 60
Without (<>), a private formal requires a definite type, because the body may declare objects of it. Real GNAT diagnostic: l38_err.adb:11:38: error: actual for "Item" must be a definite subtype generic type Item is private; function Max (A, B: Item) return Item; -- body: if A < B... -- no "<" known for Item generic type Item is private; with function "<" (L, R: Item) return Boolean is <>; function Max (A, B: Item) return Item;
The generic body can only use operations its formals promise.
function Max_Str is new Max (String); -- formal 'type Item is private' type Item (<>) is private; -- in the generic's formal part
Indefinite actuals require a (<>) formal (verified error above).
type Element is private; -- then Total:= Total + E in the body type Element is range <>; -- or pass with function "+"
Arithmetic needs a numeric formal category or a formal "+".
Which formal accepts any discrete type (integer or enumeration)? A. type T is private; B. type T is (<>); C. type T is range <>; D. type T is digits <>;
What does 'is <>' after a formal subprogram mean? A. It's optional and does nothing B. Default to the matching subprogram visible at instantiation C. The subprogram is abstract D. It is null
Which formal allows String as the actual? A. type T is private; B. type T (<>) is private; C. type T is (<>); D. type T is range <>;
When is a generic body checked for errors? A. At each instantiation B. Once, when the generic is compiled C. At run time D. Never Trace the generic subprogram example.
Real output: After Swap: A = 2, B = 1, C1 = y, C2 = x Max (3, 9) = 9 Max (2.5, -1.0) = 2.50000E+00 Generic_Max with "<" => ">" gives the minimum: abc Total score Mon..Wed = 60
Write a generic function Count_If over any array with a formal predicate.
generic type Index is (<>); type Element is private; type Vector is array (Index range <>) of Element; with function Pred (E : Element) return Boolean; function Count_If (V : Vector) return Natural; function Count_If (V : Vector) return Natural is N : Natural := 0; begin for E of V loop if Pred (E) then N := N + 1; end if; end loop; return N; end Count_If;
Formal index, element and array types plus a formal predicate make it reusable for any array.
with Ada.Text_IO; use Ada.Text_IO;
procedure L38_Generic_Sub is
-- A generic procedure: works for any type with assignment
generic
type Item is private;
procedure Generic_Swap (A, B : in out Item);
procedure Generic_Swap (A, B : in out Item) is
Tmp : constant Item := A;
begin
A := B;
B := Tmp;
end Generic_Swap;
-- A formal subprogram with a default: "is <>" picks the visible "<" at instantiation
generic
type Item (<>) is private; -- (<>): indefinite types such as String allowed
with function "<" (L, R : Item) return Boolean is <>;
function Generic_Max (A, B : Item) return Item;
function Generic_Max (A, B : Item) return Item is (if A < B then B else A);
-- A formal array type: the algorithm works for any index and element type
generic
type Index is (<>); -- any discrete type
type Element is range <>; -- any signed integer type
type Vector is array (Index range <>) of Element;
function Generic_Sum (V : Vector) return Element;
function Generic_Sum (V : Vector) return Element is
Total : Element := 0;
begin
…procedure L38_Err is
generic
type Item is private;
function Same (X : Item) return Item;
function Same (X : Item) return Item is
Copy : constant Item := X;
begin
return Copy;
end Same;
function Same_String is new Same (String);
begin
null;
end L38_Err;