Class-Wide Types & Dynamic Dispatching
Use class-wide types T'Class for polymorphic parameters, objects and access types, understand when calls dispatch (controlling operands of class-wide type), heterogeneous collections, membership tests with 'Class, tags ('Tag, Ada.Tags.Expanded_Name), and checked view conversions down the hierarchy.
Polymorphism in Ada is explicit. The type Shape'Class denotes Shape and all its descendants, and a call to a primitive operation dispatches — chooses the implementation from the run-time tag — only when the controlling operand is class-wide. Parameters of a specific type never dispatch. This makes it clear from the source which calls are dynamic, simplifying analysis and certification.
Class-Wide Types
T'Class is an unconstrained type: like String, objects of it must be initialized (their specific type is fixed at creation), and parameters of type T'Class accept any descendant. Class-wide access types ('type Shape_Access is access Shape'Class;') enable heterogeneous collections: an array of Shape_Access can hold circles, squares and colored squares. Containers such as Ada.Containers.Indefinite_Vectors can store class-wide values directly without explicit pointers.
• T'Class = T and all descendants (unconstrained). • Class-wide objects must be initialized; tag fixed at creation. • access T'Class / Indefinite containers for heterogeneous collections.
Dispatching Rules
A call to a primitive operation dispatches when its controlling operand is class-wide: S.Area where S : Shape'Class, or Area (S.all) where S designates Shape'Class. The implementation is selected from the tag at run time. If the operand has a specific type, the call is statically bound. Class-wide operations (with T'Class parameters, like Report and Total_Area) are written once and work for every present and future descendant.
• Class-wide controlling operand → dynamic dispatch. • Specific type → static call. • Write algorithms once as class-wide operations.
Tags, Membership and Conversions
X'Tag gives the tag; Ada.Tags.Expanded_Name (X'Tag) returns the full type name. 'X in Square'Class' tests whether X is a Square or a descendant; 'X'Tag = Square'Tag' tests the exact type. Converting upward (Shape (C)) is always safe. Converting a class-wide value downward (Square (X)) performs a tag check and raises Constraint_Error if X is not in Square'Class. Needing many downward conversions is usually a design smell: add a dispatching operation instead.
• X'Tag, Ada.Tags.Expanded_Name. • X in T'Class — descendant test. • Downward conversion checked → Constraint_Error on mismatch.
Code Examples
The array holds three different specific types. Report and Total_Area dispatch on each element's tag; Colored_Square inherits Area from Square but overrides Kind. Converting the Colored_Square to Square succeeds; converting the Circle fails the tag check. Output: a: circle, area 3.14159E+00, tag SHAPES.CIRCLE b: square, area 4.00000E+00, tag SHAPES.SQUARE [is a kind of Square] c: colored square, area 9.00000E+00, tag SHAPES.COLORED_SQUARE [is a kind of Square] Total area: 1.61416E+01 d: circle, area 1.25664E+01, tag SHAPES.CIRCLE Converted to Square, side 3.00000E+00 Converting a Circle to Square raised Constraint_Error (tag check) procedure Report (S: Shape); -- expecting S.Kind to dispatch procedure Report (S: Shape'Class);
Only class-wide operands dispatch.
X: Shape'Class; -- uninitialized X: Shape'Class:= Circle'(Name => 'x', R => 1.0);
Class-wide objects are indefinite and must be initialized.
if S'Tag = Circle'Tag then ... elsif S'Tag = Square'Tag then ...
Total:= Total + S.Area; -- add a dispatching primitive Tag switches defeat extensibility; let dispatching choose the behavior.
When does a call to a primitive dispatch? A. Always B. When the controlling operand is class-wide C. Never D. Only with pointers
What does 'X in Square'Class' test? A. X is exactly Square B. X is Square or a descendant of Square C. X is a parent of Square D. X is non-null
What happens when converting a Circle (as Shape'Class) to Square? A. Silent garbage B. Constraint_Error (tag check) C. Compile error D. Returns null
Which container can hold values of Shape'Class directly? A. Ada.Containers.Vectors B. Ada.Containers.Indefinite_Vectors C. Bounded_Vectors D. None
B. Indefinite containers support indefinite (class-wide) element types.
Trace the class-wide example.
Real output: a: circle, area 3.14159E+00, tag SHAPES.CIRCLE b: square, area 4.00000E+00, tag SHAPES.SQUARE [is a kind of Square] c: colored square, area 9.00000E+00, tag SHAPES.COLORED_SQUARE [is a kind of Square] Total area: 1.61416E+01 d: circle, area 1.25664E+01, tag SHAPES.CIRCLE Converted to Square, side 3.00000E+00 Converting a Circle to Square raised Constraint_Error (tag check)
-- file: shapes.ads
package Shapes is
type Shape is tagged record
Name : Character := '?';
end record;
function Area (S : Shape) return Float is (0.0);
function Kind (S : Shape) return String is ("shape");
type Circle is new Shape with record
R : Float;
end record;
overriding function Area (C : Circle) return Float is (3.14159 * C.R * C.R);
overriding function Kind (C : Circle) return String is ("circle");
type Square is new Shape with record
Side : Float;
end record;
overriding function Area (S : Square) return Float is (S.Side * S.Side);
overriding function Kind (S : Square) return String is ("square");
type Colored_Square is new Square with record
Color : Character := 'R';
end record;
overriding function Kind (S : Colored_Square) return String is ("colored square");
-- Area is inherited from Square
end Shapes;
-- file: l34_classwide.adb
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Tags;
with Shapes; use Shapes;
procedure L34_Classwide is
-- Class-wide type Shape'Class covers Shape and all its descendants.
type Shape_Access is access Shape'Class;
type Shape_List is array (Positive range <>) of
…