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 
…