Previous | Next | Table of Contents | Index | Program List | Copyright

15.4 System Structures: Class-Wide Types

We used Programs 15.1 through 15.7 to illustrate a hierarchy of tagged types, defining Person, Employee, Professional, Sales, and Clerical. We declared one variable of each type, George, Mary, Martha, Virginia, and Herman, demonstrating the appropriate constructors and selectors for each. The time has come to answer two questions left open in Section 15.2:

Class-Wide Types

Each tagged type T has an attribute T'Class, which represents the entire type hierarchy for which T is the parent. In our example, a variable of type Person'Class can hold a value of any of our five types or indeed of any type derived from any of these in the future. Person'Class is known as a class-wide type, and the variable is known as a class-wide variable.

We are getting closer to answering our questions. However, there is a small "catch": A class-wide variable cannot simply be declared but must be immediately initialized to a specific value of one of the types, and thereafter, the variable can change its value but not its type.

This rule is analogous to the rules for constrained variant records. A tagged type can be extended indefinitely, with an unknown number derived types, each with an unknown number of extension fields. The compiler cannot possibly know which types might be derived--added to T'Class--in the future, and so it cannot even guess at the size of a variable of such an unknown type. This problem does not arise with ordinary variant records because all the possible variants are known when the type declaration is compiled--an ordinary variant record cannot be extended later without rewriting and recompiling the original type declaration.

This is not very helpful when we contemplate setting up a "data base"--an array, say--of tagged objects such as employees. Since there are different types of employee, each element of the array could be of a different type. Furthermore, these elements could not all be immediately initialized because we might obtain the employee data interactively or from an external file. Moreover, we might later wish to add new types of employees without having to modify the data base structure. Indeed, the possibility of future modifications is exactly what first motivated our use of tagged types.

Continuing the analogy with variant records, is there a tagged-type analogue to an unconstrained variant record, that is, a variable whose type--within a class--can be left initially unspecified and can change over time?

The answer here is Yes, but the solution is not quite as simple as that for variant records. The difference is that by the time an unconstrained variant object is declared, the compiler knows all the possible variants and can therefore know how to arrange for the space to be allocated. In contrast, as we have just seen, a class-wide variable can be declared and the class later extended.

Class-Wide General Access Types and Heterogeneous Arrays

We solve the problem in Program 15.9, using general access types. We omit the output of this program, because it is identical to that of Program 15.4.

Program 15.9
Creating an Array of Payroll Records

WITH Ada.Text_IO; USE Ada.Text_IO;
WITH Currency; USE Currency;
WITH Dates; USE Dates;
WITH Persons; USE Persons;
WITH Personnel; USE Personnel;
WITH Payroll; USE Payroll;
PROCEDURE Payroll_Array IS
------------------------------------------------------------------------
--| Demonstrates the use of classwide general access types
--| and dispatching operations
--| Author: Michael B. Feldman, The George Washington University 
--| Last Modified: September 1995                                     
------------------------------------------------------------------------
  
  George  : ALIASED Person;
  Mary    : ALIASED Employee;
  Martha  : ALIASED Professional;
  Virginia: ALIASED Sales;
  Herman  : ALIASED Clerical;
  -- These values can now be designated by general access values
  
  TYPE PayrollPointer IS ACCESS ALL Person'Class;
  -- a PayrollPointer value can designate a value of type
  -- Person, or of any type derived from Person, such as
  -- Employee, Sales, Professional, or Clerical
  
  TYPE PayrollArray IS ARRAY (1..5) OF PayrollPointer;
  -- We can put all our employees in an array by designating
  -- them with PayrollPointer values
  
  Company: PayrollArray;

BEGIN

  -- first construct all the people, as before
  
  George := Persons.Constructors.MakePerson(
             Name      => "George",
             Gender    => Male,
             BirthDate => Date_Of(1971,Nov,2));

  Mary := Personnel.Constructors.MakeEmployee(
             Name      => "Mary",  
             Gender    => Female,
             BirthDate => Date_Of(1950,Oct,21),
             ID        => 1234,
             StartDate => Date_Of(1989,Jul,1));
 
  Martha := Payroll.Constructors.MakeProfessional(
             Name        => "Martha",
             Gender      => Female,
             BirthDate   => Date_Of(1947,Jul,8),
             ID          => 2222,
             StartDate   => Date_Of(1985,Jun,6),
             MonthSalary => MakeCurrency(50000.00));

  Virginia := Payroll.Constructors.MakeSales(
             Name       => "Virginia",
             Gender     => Female,
             BirthDate  => Date_Of(1955,Feb,1),
             ID         => 3456,
             StartDate  => Date_Of(1990,Jan,1),
             WeekSalary => MakeCurrency(2500.00),
             CommRate   => 0.25); 
 
  Herman := Payroll.Constructors.MakeClerical(
             Name       => "Herman",
             Gender     => Male,
             BirthDate  => Date_Of(1975,May,13),
             ID         => 1557,
             StartDate  => Date_Of(1991,Jul,1),
             HourlyWage => MakeCurrency(7.50));

  -- Now put the people into the company; each array element is
  -- a different type!
  
  Company := (Herman'Access, Martha'Access, Virginia'Access,
              Mary'Access, George'Access);
              
  -- Now display them all. Note that each time Put is invoked,
  -- precisely the appropriate Put is "dispatched".

  FOR Which IN Company'Range LOOP
    Put(Company(Which).ALL);
    Ada.Text_IO.Put_Line(Item => "------------------------");
  END LOOP;  
 
END Payroll_Array; 

Here our five people are declared as in Program 15.4, but now they are ALIASED. We further declared a general access type PayrollPointer and an array of values of this type:

    TYPE PayrollPointer IS ACCESS ALL Person'Class;
    TYPE PayrollArray IS ARRAY (1..5) OF PayrollPointer;
The access type can designate any type in Person'Class; each array element is a value of that access type. We can now declare a variable
    Company: PayrollArray;
and, after constructing all the people as in Program 15.4, we can put them into the company, using an array aggregate:
    Company := (Herman'Access, Martha'Access, Virginia'Access,
                Mary'Access, George'Access);
The type PayrollArray is an example of how Ada allows you to create a heterogenous array, that is, an array, each of whose values is a different type. Strictly speaking, the values in Company are all just class-wide access values, but each designated value is a different type, so the desired behavior is obtained. Our questions are answered.


Previous | Next | Table of Contents | Index | Program List | Copyright

Copyright © 1996 by Addison-Wesley Publishing Company, Inc.