John Volan's

answers to
FREQUENTLY ASKED QUESTIONS
about

Ada95's
"With-ing" Problem

ACHIEVING SEPARATE ENCAPSULATION
DESPITE MUTUAL DEPENDENCY

Version 2.06 -- 3-June-1997

Table of Contents

  1. What is the "with-ing" problem?
  2. Why can't two package specs "with" each other?
    [Answer: Ada's linear elaboration model.]
  3. Why would a programmer want to have two package specs "with" each other?
    [Answer: Mutually-dependent, but separately-encapsulated object-classes.]
    1. Dependent object-classes
    2. Mutually dependent object-classes
    3. Mutually dependent object-class interfaces
    4. Mutually dependent primitive object-class interfaces
    5. Mutually dependent primitive object-class interfaces, with static strong type checking
    6. Mutually dependent primitive object-class interfaces, with static strong type checking, yet still separately encapsulated
  4. What's so bad about declaring mutually-dependent tagged types in the same package?
    [Answer: Transitive breakdown of encapsulation.]
  5. Is Ada95 missing a feature?
    [Answer: Incomplete forward declarations that cross package boundaries.]
    1. Ada OY Proposal: Package Abstracts
    2. Ada OY Proposal: Forward Incomplete Types
    3. Ada OY Proposal: Liberalized Usage of Incomplete Types
    4. Comparison with C++
  6. Is there a workaround in Ada95?
    [Answer: Use generics to "forward declare" a class, and then later "bind" the forward declaration.]
    1. Generic package Forward
    2. Instantiate the Forward package to forward-declare each object-class
    3. Declare separate object-class interfaces (package specs) that "with" each other's Forward packages
    4. Bind each object-class interface to its Forward.Reference_Type by instantiating the nested Binding package
    5. Declare object-class implementations (package bodies) that "with" each other's interfaces (specs)
    6. Sample client code
  7. Despite your assurances of type-safety, I really don't like the fact that you used Unchecked_Conversion. Isn't there some other way?
    [Answer: Sure! :-) ]
  8. Revision History
  9. Credits
  10. Copyright Notice


[1] What is the "with-ing" problem?

The so-called "with-ing" problem, also known as Ada's "chicken-and-egg" dilemma, occurs when someone tries to have two package specifications import each other via "with" clauses. This is illegal in Ada (83 or 95).

(Up to Table of Contents)


[2] Why can't two package specs "with" each other?

If "with" clauses only represented static semantic dependencies, then compilers could easily resolve even circular dependencies, by taking as many passes through the source code as they needed to. However, a "with" clause in Ada represents not just a static semantic dependency, but a dynamic semantic dependency as well. This dynamic dependency imposes an order of elaboration at runtime.

The concept of elaboration is an important aspect of Ada's dynamic semantic model. Every declaration in an Ada program gets "elaborated" at run time. This means that, for each declaration, there's a specific point during execution when that declaration "takes effect." Moreover, Ada's elaboration model is linear, which means that declarations "take effect" in the order in which they appear in the source code.

Under this linear model, when one compilation unit X imports another compilation unit Y via a "with" clause, the dependency between X and Y requires that the spec of Y be elaborated before X. Therefore, if two package specs could "with" each other, each would need to get elaborated before the other---a logical impossibility.

(Up to Table of Contents)


[3] Why would a programmer want to have two package specs "with" each other?

The programmer might wish to implement two (or more) object-classes as (private) tagged types encapsulated in separate packages, yet the two object-classes may be mutually dependent in their primitive interfaces, and the programmer does not wish to compromise Ada 95's static strong typing. That's quite a mouthful---but don't worry, the subsections below will break this statement down and examine it one aspect at a time.

First, however, let's illustrate the problem with an example: Suppose our problem domain is a medical application of some kind. (Disclaimer: The only purpose of this example is to illustrate certain programming patterns in an approachable way. It doesn't even attempt to reflect a realistic requirement analysis or design for an actual medical application.)

Here's the premise:

  1. We have a "Doctor" object-class, which we want to implement as a tagged type called "Doctor_Type" in a package called "Doctors".
  2. We have a "Patient" object-class, which we want to implement as a tagged type called "Patient_Type" in a package called "Patients".
  3. The Doctors package includes primitive Doctor_Type procedures such as "Treat_Patient" and "Bill_Patient" which need to take parameters of Patient_Type.
  4. The Patients package includes primitive Patient_Type procedures such as "Visit_Doctor" and "Pay_Doctor" which need to take parameters of Doctor_Type.
  5. We want Patients.Visit_Doctor to invoke Doctors.Treat_Patient.
  6. We want Doctors.Bill_Patient to invoke Patients.Pay_Doctor.

This leads us right into the "with-ing" problem, once we discover that the following is illegal:

... -- other dependencies
with Patients; -- ILLEGAL! CIRCULAR DEPENDENCY!
package Doctors is

  type Doctor_Type is tagged limited private;
      
  procedure Treat_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patients.Patient_Type'Class);
             
  procedure Bill_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patients.Patient_Type'Class);
     
  ... --other Doctor_Type primitive subprograms
  
private
  type Doctor_Type is tagged limited
    record
      ...
    end record;
end Doctors;
... -- other dependencies
with Doctors; -- ILLEGAL! CIRCULAR DEPENDENCY!
package Patients is
    
  type Patient_Type is tagged limited private;
      
  procedure Visit_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctors.Doctor_Type'Class);
             
  procedure Pay_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctors.Doctor_Type'Class);

  ... --other Patient_Type primitive subprograms
  
private
  type Patient_Type is tagged limited
    record
      ...
    end record;
end Patients;

The "with" clause on the Doctors spec requires the Patients spec to be elaborated first, but the "with" clause on the Patients spec requires the Doctors spec to be elaborated first. Chicken and egg!

Our intended package bodies become, unfortunately, a moot point:

package body Doctors is -- never gets to compile

  procedure Treat_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patients.Patient_Type'Class) is
  begin
    ...
  end Treat_Patient;
             
  procedure Bill_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patients.Patient_Type'Class) is
  begin
    -- do something appropriate with Doctor...
    ...
    Patients.Pay_Doctor (Patient, Doctor); 
  end Bill_Patient;
  
  ... -- other Doctor_Type subprogram bodies
end Doctors;
package body Patients is -- never gets to compile
    
  procedure Visit_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctors.Doctor_Type'Class) is
  begin
    -- do something appropriate with Patient...
    ...
    Doctors.Treat_Patient (Doctor, Patient);
  end Visit_Doctor;
             
  procedure Pay_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctors.Doctor_Type'Class) is
  begin
    ...
  end Pay_Doctor;
  
  ... -- other Patient_Type subprogram bodies
end Patients;

(Up to Table of Contents)


Now let's break the problem down into pieces. The "with-ing" problem arises when we want to implement two object-classes (tagged types) which are:

[3.1] Dependent object-classes

Some of the primitive operations of one tagged type must be able to reference the other tagged type.

[3.2] Mutually dependent object-classes

For one reason or another, the dependency goes both ways. Primitive operations for both types need to reference the opposite type. On the other hand, if the dependency were only one way or the other, there would be no problem---only one package spec would have to "with" the other.

For instance, in our medical example, if we didn't need the procedures Doctors.Treat_Patient and Doctors.Bill_Patient, then the Doctors package spec would not need to "with" the Patients package spec. The Patients package spec could "with" the Doctors package spec without trouble.

Conversely, if we didn't need the procedures Patients.Visit_Doctor and Patients.Pay_Doctor, then the Patients package spec wouldn't need to "with" the Doctors package spec. The Doctors package spec could "with" the Patients package spec without trouble.

However, our premise requires all of those procedures, so we face a "with-ing" dilemma.

(Up to Table of Contents)


[3.3] Mutually dependent object-class interfaces

For one reason or another, the mutually-dependent operations must be visible features of the interfaces of both object-classes. In other words, they have to be declared in their respective package specs. On the other hand, if we could somehow hide the mutual dependency as an implementation detail buried within the package bodies, then there wouldn't be any problem. The package bodies can "with" each other's specs with no difficulty.

So for instance, in our medical application, if we could somehow get away with keeping those mutually-dependent procedures out of the interfaces, then the two package specs could be totally independent of each other:

... -- other dependencies
package Doctors is

  type Doctor_Type is tagged limited private;     
  ...
end Doctors;
... -- other dependencies
package Patients is
    
  type Patient_Type is tagged limited private;
  ...
end Patients;

If we can simply hide those procedures in the package bodies, then the mutual "with" clauses won't present a problem:

with Patients; -- no problem doing this in the body
package body Doctors is

  procedure Treat_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patients.Patient_Type'Class) is
  begin
    ...
  end Treat_Patient;
             
  procedure Bill_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patients.Patient_Type'Class) is
  begin
    ... -- but can't invoke Patients.Pay_Doctor, not visible here
  end Bill_Patient;
    ...
end Doctors;
with Doctors; -- no problem doing this in the body
package body Patients is
    
  procedure Visit_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctors.Doctor_Type'Class) is
  begin
    ... -- but can't call Doctors.Treat_Patient, not visible here
  end Visit_Doctor;
             
  procedure Pay_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctors.Doctor_Type'Class) is
  begin
    ...
  end Pay_Doctor;
    ...
end Patients;

However, as is clear above, hiding these procedures prevents them from calling each other. Patients.Visit_Doctor would somehow have to be written in terms of Doctor_Type procedures that don't include Patient_Type parameters. Likewise, Doctors.Bill_Patient would somehow have to be written in terms of Patient_Type procedures that don't include Doctor_Type parameters.

But since our premise is that these procedures will call each other, they have to be made visible in the package specs. And that leads us back to the "with-ing" problem.

(Up to Table of Contents)


[3.4] Mutually dependent primitive object-class interfaces

The "with-ing" problem arises because, for one reason or another, the mutually-dependent operations have to be primitive, inheritable operations declared in the same package spec with their tagged types. On the other hand, suppose our software design didn't require these mutually-dependent subprograms to be primitive operations. In that case, there would be no absolute necessity to keep them in the same package specs with their tagged types. Instead, we could off-load them to the specs of other packages, where no circular "with" clauses would be needed. In fact, these "other" packages could even be child packages.

For instance, in our medical example, we could keep the package specs independent as we did in section 3.3 above. Then we could introduce a child of package Doctors that can legally "with" package Patients, and a child of package Patients that can "with" package Doctors:

with Patients; -- no problem doing this in a child spec
package Doctors.And_Patients is

  procedure Treat_Patient
    (Doctor  : in out Doctor_Type'Class;
     Patient : in out Patients.Patient_Type'Class);
             
  procedure Bill_Patient
    (Doctor  : in out Doctor_Type'Class;
     Patient : in out Patients.Patient_Type'Class);
    ...
end Doctors.And_Patients;
with Doctors; -- no problem doing this in a child spec
package Patients.And_Doctors is
    
  procedure Visit_Doctor
    (Patient : in out Patient_Type'Class;
     Doctor  : in out Doctors.Doctor_Type'Class);
             
  procedure Pay_Doctor
    (Patient : in out Patient_Type'Class;
     Doctor  : in out Doctors.Doctor_Type'Class);
    ...
end Patients.And_Doctors;

Because the Doctors.And_Patients package is a child of package Doctors, it has visibility to the private implementation details of the Doctor_Type, so the Treat_Patient and Bill_Patient procedures can directly manipulate their Doctor parameters, just as if they were procedures declared immediately within the Doctors package. Likewise, the Patients.And_Doctors package has visibility to the implementation of Patient_Type, so the Visit_Doctor and Pay_Doctor procedures can directly manipulate their Patient parameters, just as if they were procedures declared immediately within the Patients package.

Since these procedures are publicly visible in their child package specs, they can invoke each other, as long as the bodies of these two child packages "with" each other's specs:

with Patients.And_Doctors; -- no problem doing this in the body
package body Doctors.And_Patients is

  procedure Treat_Patient
    (Doctor  : in out Doctor_Type'Class;
     Patient : in out Patients.Patient_Type'Class) is
  begin
    ...
  end Treat_Patient;
             
  procedure Bill_Patient
    (Doctor  : in out Doctor_Type'Class;
     Patient : in out Patients.Patient_Type'Class) is
  begin
    -- do something appropriate with Doctor...
    ...
    Patients.And_Doctors.Pay_Doctor (Patient, Doctor); 
  end Bill_Patient;
    ...
end Doctors.And_Patients;
with Doctors.And_Patients; -- no problem doing this in the body
package body Patients.And_Doctors is
    
  procedure Visit_Doctor
    (Patient : in out Patient_Type'Class;
     Doctor  : in out Doctors.Doctor_Type'Class) is
  begin
    -- do something appropriate with Patient...
    ...
    Doctors.And_Patients.Treat_Patient (Doctor, Patient);
  end Visit_Doctor;
             
  procedure Pay_Doctor
    (Patient : in out Patient_Type'Class;
     Doctor  : in out Doctors.Doctor_Type'Class) is
  begin
    ...
  end Pay_Doctor;
    ...
end Patients.And_Doctors;

However, because these procedures are no longer located in the same package spec with their respective tagged type, by definition they are no longer "primitive" operations. Presumably, Doctor_Type is going to be the root type for a hierarchy of derived types (i.e. a "type class") representing different kinds of doctors. (Perhaps we wanted different kinds of doctors to provide patients with different kinds of treatments.) The way we have it now, types in the Doctor_Type'Class hierarchy will no longer inherit the Treat_Patient and Bill_Patient procedures. Therefore the only way to make these procedures applicable to all the types in the Doctor_Type'Class hierarchy is to make them "classwide" procedures; i.e., they now take parameters of Doctor_Type'Class rather than just Doctor_Type. But this means that the implementations of these procedures are fixed for the whole class hierarchy. Types derived from Doctor_Type can no longer override these procedures with different implementations, and we can no longer get dynamic dispatching to these different implementations based on the run-time type-tag of a Doctor object.

Similarly, Patient_Type is presumed to be the root type for a derivation hierarchy representing different types of patients. These derived types will no longer inherit the Visit_Doctor and Pay_Doctor procedures, so those procedures have to be made classwide (they now take parameters of Patient_Type'Class). The implementations of these procedures are fixed for the whole Patient_Type'Class hierarchy; they cannot be overridden for types derived from Patient_Type, and we can no longer get dynamic dispatching.

As long as our software design can tolerate making mutually-dependent operations such as these into classwide operations, this design pattern is quite workable; it neatly side-steps the "with-ing" problem. However, the original premise of this example was that these subprograms in fact had to be primitive operations whose implementations could be overridden by derived types and then dynamically dispatched to base on run-time type tags. To satisfy that design, we'd have no choice but to put these procedures back into their original package specs, in which case we'd once again face the dilemma of circularly-dependent package specs.

(Thanks to Robert Eachus for pointing out this aspect.)

(Up to Table of Contents)


[3.5] Mutually dependent primitive object-class interfaces, with static strong typing

The "with-ing" problem arises because we want mutually-dependent primitive operations to declare, statically, exactly which object-class their mutually-dependent parameters will be expecting, so that we can avail ourselves of Ada 95's static, strong type-checking facilities. On the other hand, if we're willing to defeat Ada's static checks, then we could easily avoid circular "with" clauses. Suppose we had some universal root abstract tagged type, called "Object_Type", declared in a package called "Objects". Any tagged types that needed to participate in mutual dependencies (e.g., Doctor_Type and Patient_Type) could be derived from Objects.Object_Type. Then, wherever a primitive subprogram for one of these types needs to take a parameter of another mutually-dependent type, we could just use Objects.Object_Type'Class for this parameter.

... -- other dependencies
with Objects;
-- no direct dependency on Patients
package Doctors is

  type Doctor_Type is new Objects.Object_Type with private;
      
  procedure Treat_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Objects.Object_Type'Class);
             
  procedure Bill_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Objects.Object_Type'Class);
     
  ... --other Doctor_Type primitive subprograms
  end Doctors;
... -- other dependencies
with Objects;
-- no direct dependency on Doctors
package Patients is
    
  type Patient_Type is new Objects.Object_Type with private;
      
  procedure Visit_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Objects.Object_Type'Class);
             
  procedure Pay_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Objects.Object_Type'Class);

  ... --other Patient_Type primitive subprograms
end Patients;

In the bodies of these subprograms, we'd have to use "narrowing" (type-safe downward type-casting) to turn these Object_Type'Class parameters into the specific opposite type-class, before we could invoke class-specific operations on them.

with Patients; -- no problem doing this in the body
package body Doctors is

  procedure Bill_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Objects.Object_Type'Class) is
  begin
    -- do something appropriate with Doctor...
    ...
    Patients.Pay_Doctor
      (Patient => Patients.Patient_Type'Class(Patient), -- narrowing
       Doctor  => Doctor); -- implicitly widened to Object_Type'Class
  end Bill_Patient;
  
  ... -- other Doctor_Type subprogram bodies
end Doctors;
with Doctors; -- no problem doing this in the body
package body Patients is
    
  procedure Visit_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Objects.Object_Type'Class) is
  begin
    -- do something appropriate with Patient...
    ...
    Doctors.Treat_Patient
      (Doctor  => Doctors.Doctor_Type'Class(Doctor), -- narrowing
       Patient => Patient); -- implicitly widened to Object_Type'Class
  end Visit_Doctor;
             
  ... -- other Patient_Type subprogram bodies
end Patients;

Unfortunately, although Ada 95 guarantees this to be type-safe, this scheme defers the actual type-checking until run-time, rather than compile-time. If the actual parameter passed into a call turned out to be the wrong class of object (i.e., its run-time type-tag didn't have one of the expected values), then Constraint_Error would be raised on the attempt to "narrow" it to the specific class. This would be unfortunate because in theory most such type errors could have been detected by a compiler, long before execution time. Presumably the software designer knew, at a static level, what the specific type-class should be; but this couldn't be expressed statically in the source code.

(Side Note: This is essentially the scheme used by languages such as Smalltalk which lack static type checking. All variables and parameters in Smalltalk are typeless; Smalltalk makes no assertion about them except that they contain objects. Type-checking is done only at run-time, when method-resolution is performed. Smalltalk avoids anything comparable to Ada95's "with-ing" problem by not even expressing circular type dependencies explicitly.)

Given the proven utility of static strong type checking, most Ada95 programmers are unlikely to be satisfied with compromising Ada95's type system in this way. But the moment we attempt to explicitly declare the type-class of mutually-dependent parameters, we're forced back into a circular spec dependency.

(Up to Table of Contents)


[3.6] Mutually dependent primitive object-class interfaces, with static strong typing, yet still separately encapsulated

Here's the crux of the matter: The "with-ing" problem arises because, despite all of the above, we still want to keep the two tagged types separately encapsulated in their own packages.

On the other hand, if we could simply see our way through to declaring the two types within the same package, we wouldn't have any trouble with "with-ing" at all:

package Medical is

  type Doctor_Type  is tagged limited private;
  type Patient_Type is tagged limited private;
   
  -- Doctor_Type primitive subprograms:   
      
  procedure Treat_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patient_Type'Class);
             
  procedure Bill_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patient_Type'Class);
     
  ... --etc.
  
  -- Patient_Type primitive subprograms:   
      
  procedure Visit_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctor_Type'Class);
             
  procedure Pay_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctor_Type'Class);

  ... --etc.
  
end Medical;

(Up to Table of Contents)


[4] What's so bad about declaring mutually-dependent tagged types in the same package?

Co-encapsulating mutually-dependent types may be perfectly fine for some applications, where the types are very tightly coupled and really do constitute a single, non-decomposable abstraction. But not all cases of mutual dependency fall into this category. What if your design concept is for these object-classes to represent distinct abstractions in their own right, only loosely coupled to each other? Then co-encapsulation becomes a liability.

For one thing, encapsulating two types in the same package means breaking down information hiding between them: The operations of each type would have visibility to the implementation details of the other type. Short of willpower and vigilance, we have no way of guaranteeing that the operations of one type will not violate the intended abstraction of the other type.

More seriously, co-encapsulating the two types compromises object-oriented modularity in a transitive way. For example, in our medical application, suppose we expand our viewpoint a bit and consider another object-class that might be part of the domain: "Insurer". Let's assume the following additional premises:

  1. The Insurer class and the Patient class are mutually dependent.
    1. The Patient class includes primitive subprograms such as File_With_Insurer which take parameters of the Insurer class.
    2. The Insurer class includes primitive subprograms such as Reimburse_Patient which take parameters of the Patient class.
    3. Patients.File_With_Insurer will invoke Insurers.Reimburse_Patient.
  2. The Doctor class and the Insurer class are totally independent object-classes and have nothing to do with each other. (Well, I warned you this wasn't necessarily a realistic application... :-) )

Given this premise, we might be able to tolerate being forced to put Doctor_Type and Patient_Type into the same package, or Patient_Type and Insurer_Type into the same package, but to be forced to put Doctor_Type, Patient_Type, and Insurer_Type into the same package is simply intolerable:

package Medical is

  type Doctor_Type  is tagged limited private;
  type Patient_Type is tagged limited private;
  type Insurer_Type is tagged limited private;
  ... -- and who knows what else... ?
   
  -- Doctor_Type primitive subprograms:   
      
  procedure Treat_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patient_Type'Class);
             
  procedure Bill_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patient_Type'Class);
     
  ... --etc.
  
  -- Patient_Type primitive subprograms:   
      
  procedure Visit_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctor_Type'Class);
             
  procedure Pay_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctor_Type'Class);
     
  procedure File_Claim_With_Insurer
    (Patient : in out Patient_Type;
     Insurer : in out Insurer_Type'Class);

  ... --etc.
  
  -- Insurer_Type primitive subprograms:   
      
  procedure Reimburse_Patient
    (Insurer : in out Insurer_Type;
     Patient : in out Patient_Type'Class);
             
  ... --etc.
  
end Medical;

So much for "divide-and-conquer"!

Suppose we have a large software design with a lot of object-classes, and each object-class participates in several mutual dependencies. The transitivity effect might force us to put all (or most) of our object-classes into one huge, monolithic package. Every class would be able to see the implementation details of every other class, despite the fact that: (1) each class is only directly dependent on a few others---not all others; and (2) "dependency" only means that one class needs access to the interface of another class---not access to its implementation.

Taken to its logical extreme, this transitivity effect can be disastrous for object-oriented modularity. We lose any notion of using object-classes as units of modularity and abstraction in breaking down the work of developing and maintaining a large system.

(Up to Table of Contents)


[5] Is Ada95 missing a feature?

Yes. What is missing here is some form of incomplete forward declaration which can span package boundaries.

An important thing to realize about any two mutually-dependent object-classes X and Y, is that the interface of X does not really need access to the full interface of Y. All that X needs at the interface level is some kind of incomplete forward declaration of Y, something that asserts that Y "exists" and will represent an object-class. This would then allow X's interface to declare primitive subprograms that include parameters of class Y. We only need the full interface of Y once we get to the impementation of X, where X's operations actually need to invoke Y's operations.

Ada's packaging mechanism only allows us to break up the definition of an object-class into two parts: an interface (package spec) and an implementation (package body). But if we could divide an object-class definition into three parts---forward declaration, interface, and implementation---then we could establish a three-tier system of dependencies that solves the "with-ing" problem:

3-tier dependency diagram

As you can see, the class interfaces (package specs) wouldn't be dependent on each other, they would only be dependent on each other's forward declarations. Only their implementations (package bodies) would be dependent on each other's interfaces (i.e. "with" each other's specs).

Note that this sort of scheme was hinted at in section 3.3 above: Those "with" clauses that import the specs have been pushed down into the package bodies. But we still need something prior to the specs which the specs themselves can "with".

(Up to Table of Contents)


[5.1] AdaOY Proposal: Package Abstracts

I have proposed (on comp.lang.ada in 1995) extending Ada95 with a new kind of separate compilation unit, which I've coined a package abstract. The "abstract" of a package would be an optional third syntactic division of the package, logically "prior" to both the package spec and the package body, within which incomplete type declarations could be tolerated without necessarily being completed there.

(I'm suggesting calling this construct the "abstract" of a package, by analogy with the "abstract" of a paper or thesis. We might have called this a "package forward", but "package abstract" has the benefit of not introducing any new reserved words.)

Here are a possible set of language rules regarding package abstracts. (Note: Since these proposed rules are hypothetical they are certainly open for discussion. I encourage readers to post comments and suggestions on comp.lang.ada.)

(1) A package abstract could have the following syntax:

package abstract package_name is
  declarations
end package_name;

In other words, a package abstract would look much like a package spec, but with the extra reserved word abstract, and without any private part.

(2) A package abstract would be considered a declarative region contiguous with the public part of its package spec, in the same way that the package spec is considered a declarative region contiguous with its package body. Declarations appearing in a package abstract would be directly visible within the following package spec (and the body), just as declarations in a package spec are directly visible within the following body.

(3) A package abstract is an optional feature. This means that we must syntactically distinguish a package spec that is preceded by an abstract from one that is not. For the latter, no syntax change is required. For the former, I suggest recycling the reserved word "with", marking such a package spec by means of the following syntax:

 package package_name with abstract is 
   public_declarations
[private
   private_declarations]
 end package_name;

(4) A given package abstract is either mandatory or prohibited, depending on the form of its following package spec. (This is analogous to the new Ada95 rule that prohibits a package body if not required by its prior package spec.) An ordinary, unmarked package spec makes a prior package abstract illegal. A package spec marked by "with abstract" semantically depends on its prior abstract (as implied by the use of the reserved word "with"), so such a package spec is illegal if the abstract is not already present in the library.

(I've tried to word the previous paragraph so that you could interpret it either in terms of order-of-compilation rules, or in terms of GNAT-style source-file-dependency rules.)

(5) Because a package abstract lacks a private part, it would not be allowed to contain any declarations that must be completed within the private part of a package spec. In other words, private type declarations and deferred constant declarations would be prohibited in a package abstract.

(Note: This is a change from my original proposal on comp.lang.ada. I originally suggested allowing private types within an abstract. However, disallowing private types significantly simplifies the semantics of package abstracts, and the following rule concerning incomplete types is sufficient to solve the "with-ing" problem.)

(6) Otherwise, a package abstract could contain any kind of declaration allowed in the public part of a package spec. In particular, a package abstract could contain incomplete type declarations. An incomplete type declared in a package abstract does not need to be completed within the package abstract. A completion is only required by the end of the public part of the following package spec. The rules pertaining to incomplete types in the Ada95 Language Reference Manual [RM95], section 3.10.1 (with one exception, see paragraph (9) below) would apply to any incomplete type declared within a package abstract. So, for instance, such incomplete types can be used to declare access types and access parameters.

For example, we could now have:

with Generic_Sets;
package abstract Patients is

  type Patient_Type;
  -- incomplete type not completed in this compilation unit
   
  type Patient_Access_Type is access all Patient_Type'Class;
  -- see paragraph (9)
   
  type Patient_Index_Type is new Positive;
  type Patient_Array_Type is
    array (Patient_Index_Type range <>) of Patient_Access_Type;
  
  package Patient_Sets is new Generic_Sets (Patient_Access_Type);

end Patients;
package Patients with abstract is

  type Patient_Type is tagged limited private;
  -- completion of incomplete type from prior abstract
  ...
private
  -- completion of private type from public part above:
  type Patient_Type is tagged limited
    record
      ...
    end record;
end Patients;

(7) If a client compilation unit imports a package via an ordinary "with" clause, and that package has an abstract, then the client is given access to, and is made dependent upon, both the abstract and the specification of that package. However, a client unit may choose to import a package abstract alone, without also importing its corresponding package spec. This requires an alternate syntax for the "with" clause:

with abstract package_name;

Such a "with abstract" clause only gives the client access to, and only makes it dependent upon, the given package abstract, and not the package spec.

(8) If an abstract includes any incomplete type(s), then any library unit which imports this abstract via a "with abstract" clause is required (eventually) to "complete" the incomplete type(s) by "with-ing" the package in full. This must be done before the end of the library unit. However, the "with abstract" clause could appear in the spec of the library unit, and the full "with" clause could appear in the body.

This requirement would not necessarily be transitive: If such a "with abstract" client were in turn imported by a secondary client via a "with" clause, the secondary client would be forced to "with" the full spec for the abstract only if it actually called a subprogram from the primary client with a parameter of an incomplete type from the abstract.

(9) Currently, the rule in RM95 3.10.1(9) only allows the 'Class attribute to be applied to an incomplete type within the same library unit where it is declared. This rule would be relaxed to allow "with abstract" clients to apply 'Class as well. (In all other respects, however, the classwide type would still be limited to the same uses as the incomplete type, and use of the 'Class attribute would still require that the completion be a tagged type.)

This relaxation can be justified on the grounds that a "with abstract" client is required to "complete" the incomplete type declaration by becoming a full "with" client at some point, so the legality of the incomplete type will be verified by the end of the client library unit.

These rules would then allow us to use package abstracts to forward declare the object classes in our problem domain, without needing to mentioning any interface details:

package abstract Doctors is
  type Doctor_Type;
  type Doctor_Access_Type is access all Doctor_Type'Class;
  ... -- etc.
end Doctors;
package abstract Patients is
  type Patient_Type;
  type Patient_Access_Type is access all Patient_Type'Class;
  ... -- etc.
end Patients;

Then we could declare our object-class interfaces as package specs. At this level, all inter-class dependencies could be expressed as "with abstract" clauses:

with abstract Patients;
package Doctors with abstract is

  type Doctor_Type is tagged limited private;
    
  procedure Treat_Patient
    (Doctor  : access Doctor_Type;
     Patient : access Patients.Patient_Type'Class);
             
  procedure Bill_Patient
    (Doctor  : access Doctor_Type;
     Patient : access Patients.Patient_Type'Class);
  ...
end Doctors;
with abstract Doctors;
package Patients with abstract is

  type Patient_Type is tagged limited private;
    
  procedure Visit_Doctor
    (Patient : access Patient_Type;
     Doctor  : access Doctors.Doctor_Type'Class);
             
  procedure Pay_Doctor
    (Patient : access Patient_Type;
     Doctor  : access Doctors.Doctor_Type'Class);
  ...
end Patients;

Since the Doctors spec depends on the Patients abstract, and the Patients abstract declares Patient_Type as an incomplete type, the body of Doctors must "complete" this type by "with-ing" the Patients package in full. This then allows the body of Doctors to invoke Patient_Type operations:

with Patients; -- provides completion for Patients.Patient_Type
package body Doctors is
  ...
  procedure Bill_Patient
    (Doctor  : access Doctor_Type;
     Patient : access Patients.Patient_Type'Class) is
  begin
    ...
    Patients.Pay_Doctor (Patient, Doctor);
  end Bill_Patient;
  ...
end Doctors;

Similarly, the body of the Patients package must fully "with" the Doctors package in order to "complete" the incomplete Doctor_Type declared in the Doctors abstract:

with Doctors; -- provides completion for Doctors.Doctor_Type   
package body Patients is
  ...
  procedure Visit_Doctor
    (Patient : access Patient_Type;
     Doctor  : access Doctors.Doctor_Type'Class) is
  begin
    ...
    Doctors.Treat_Patient (Doctor, Patient);
  end Visit_Doctor;
  ...
end Patients;

(10) In Ada, what can be done with program units at the library level can generally be done within a nested scope as well, and vice versa. Package abstracts could easily be made to satisfy this design principle: They would be a new kind of compilation unit, but they could also be a new kind of declarative item, just like package specs and package bodies. Even when nested inside some enclosing package, abstracts could still help maintain separate encapsulation between different object-class abstractions.

For instance, this feature would allow us to have:

package Medicine is


  package abstract Doctors is 
    type Doctor_Type;
    type Doctor_Access_Type is access all Doctor_Type'Class;
    ... -- etc.
  end Doctors;

  package abstract Patients is
    type Patient_Type;
    type Patient_Access_Type is access all Patient_Type'Class;
    ... -- etc.
  end Patients;
  

  package Doctors with abstract is

    type Doctor_Type is tagged limited private;
    
    procedure Treat_Patient
      (Doctor  : access Doctor_Type;
       Patient : access Patients.Patient_Type'Class);
             
    procedure Bill_Patient
      (Doctor  : access Doctor_Type;
       Patient : access Patients.Patient_Type'Class);
    ...
  end Doctors;
  

  package Patients with abstract is

    type Patient_Type is tagged limited private;
    
    procedure Visit_Doctor
      (Patient : access Patient_Type;
       Doctor  : access Doctors.Doctor_Type'Class);
             
    procedure Pay_Doctor
      (Patient : access Patient_Type;
       Doctor  : access Doctors.Doctor_Type'Class);
    ...
  end Patients;
  

end Medicine;
package body Medicine is


  package body Doctors is
    ...
    procedure Bill_Patient
      (Doctor  : access Doctor_Type;
       Patient : access Patients.Patient_Type'Class) is
    begin
      ...
      Patients.Pay_Doctor (Patient, Doctor);
    end Bill_Patient;
    ...
  end Doctors;
  

  package body Patients is
    ...
    procedure Visit_Doctor
      (Patient : access Patient_Type;
       Doctor  : access Doctors.Doctor_Type'Class) is
    begin
      ...
      Doctors.Treat_Patient (Doctor, Patient);
    end Visit_Doctor;
    ...
  end Patients;


end Medicine;

The changes suggested above would be sufficient to eliminate the "with-ing" problem. (However, package abstracts might be made even more convenient to use if the ideas in section 5.3 below were applied to them.)

(Note: Norman Cohen, on comp.lang.ada in 1995, proposed a concept similar to package abstracts, but much more general an flexible: package parts. Under Norman's scheme, Ada's traditional package spec and package body would be eliminated, and in their place a package would be composed of an arbitrary number of "parts" in an ordered sequence, each with its own public and private section. Subprogram bodies could be hidden within the private section of any package part. Package parts could solve the "with-ing" problem, but this is by far not the only issue that they would be useful for. However, package parts represent a rather abrupt departure from the design of Ada, and introduce many more complexities than can be adequately addressed within the scope of this discussion.)

(Up to Table of Contents)


[5.2] AdaOY Proposal: Forward Incomplete Types

(Credits: The following discussion represents a proposal originally made by Tucker Taft on comp.lang.ada in 1996, with some modifications and additions suggested by several other people on comp.lang.ada, including yours truly.)

Tucker Taft has proposed another possible language extension which can solve the "with-ing" problem: the "with type" clause. I propose a slight generalization of this notion, which I am coining as "forward incomplete types".

A forward incomplete type can be defined as a new kind of incomplete type which forward-declares not only a type, but also the package which will (eventually) declare the completion of that type. This mechanism effectively allows an incomplete type to cross package boundaries, without requiring any semantic dependency on the package spec that encapsulates it.

I propose two new language constructs which can introduce a forward incomplete type:

  1. Tucker's "with type" clause, which introduces a forward incomplete type within a context clause.
  2. The forward incomplete type declaration, which introduces a forward incomplete type within a declarative region.

Here are some possible language rules regarding forward incomplete types. (Comments and suggestions are welcomed.)

(1) In its simplest form, a "with type" clause (per Tucker's proposal) could have the following syntax:

with type package_identifier.type_identifier;

The syntax of a forward incomplete type declaration would be identical to that of a "with type" clause, but without the reserved word "with":

type package_identifier.type_identifier;

(2) A "with type" clause can appear in the context clause of a compilation unit. It introduces both the package identifier and the type identifier into the context of that compilation unit, but without introducing any semantic dependence on the package so identified. The package does not even need to exist within the library at the time the "with type" is compiled.

For instance, we could now have:

with type Patients.Patient_Type;
package Doctors is
  type Doctor_Type is tagged limited private;
  ...
end Doctors;
with type Doctors.Doctor_Type;
package Patients is
  type Patient_Type is tagged limited private;
  ...
end Patients;

The Patients package need not be present in the library when the Doctors package spec is compiled. Likewise, the Doctors package need not be present in the library when the Patients package spec is compiled.

(3) The rules pertaining to incomplete types in RM95 3.10.1 would also apply to any forward incomplete type declared via a "with type" clause. For instance, such incomplete types could not be used to declare objects, but they could be used to declare access types and access parameters. As I described before for "with abstract" clients (see paragraph 5.1(9) above), "with type" clients likewise should be allowed to apply the 'Class attribute to a forward incomplete type, on the grounds that such clients are required to eventually complete the type by "with-ing" the package spec in full.

This now lets us have:

with type Patients.Patient_Type; -- forward incomplete type
package Doctors is

  type Doctor_Type is tagged limited private;
    
  procedure Treat_Patient
    (Doctor  : access Doctor_Type;
     Patient : access Patients.Patient_Type'Class);
             
  procedure Bill_Patient
    (Doctor  : access Doctor_Type;
     Patient : access Patients.Patient_Type'Class);
  ...
end Doctors;
with type Doctors.Doctor_Type; -- forward incomplete type
package Patients is

  type Patient_Type is tagged limited private;
    
  procedure Visit_Doctor
    (Patient : access Patient_Type;
     Doctor  : access Doctors.Doctor_Type'Class);
             
  procedure Pay_Doctor
    (Patient : access Patient_Type;
     Doctor  : access Doctors.Doctor_Type'Class);
  ...
end Patients;

(4) Like any other incomplete type declaration, a completion is eventually required for any forward incomplete type introduced via a "with type" clause. Tucker proposes that this completion be provided via a conventional "with" clause for the package which was forward-declared by the "with type" clause. Such a "with" clause completion would be required in two places:

  1. in the body of any unit which included a "with type" clause in its spec;
  2. in any of that unit's clients who wished to call a subprogram that included the forward incomplete type as a formal parameter or return result.

So, for instance, the bodies of our Doctors and Patients packages would be required to "with" each other's specs:

with Doctors;  -- provides completion for Doctors.Doctor_Type
package body Patients is
    
  procedure Visit_Doctor    
    (Patient : access Patient_Type;
     Doctor  : access Doctors.Doctor_Type'Class) -- requires completion
  is
  begin
    -- do something appropriate with Patient ...
    ...
    Doctors.Treat_Payment (Doctor, Patient); -- requires completion
  end Visit_Doctor;
        
  ... -- implementation of other Patient_Type primitives
end Patients;
with Patients; -- provides completion for Patients.Patient_Type
package body Doctors is
    
  procedure Bill_Patient
    (Doctor  : access Doctor_Type;
     Patient : access Patients.Patient_Type) -- requires completion
  is
  begin
    -- do something appropriate with Doctor ...
    ...
    Patients.Pay_Doctor (Patient, Doctor); -- requires completion
  end Treat_Patient;

  ... -- implementation of other Doctor primitives
end Doctors;

A third-party client calling a subprogram from one of these packages with a forward incomplete type parameter would have to complete that type by "with-ing" the other package too:

with Patients; -- forward declares Doctors.Doctor_Type
with Doctors;  -- provides completion for Doctors.Doctor_Type
procedure Medical_Application is
  ...
begin
  ...
  Patients.Visit_Doctor (Patient, Doctor); -- requires completion
  ...
end Medical_Application;

(5) Tucker's proposal also provided for the possibility that the package to be forward-declared might be a child of another package already in the library. A more complete syntax for the "with type" clause could be:

with type [library_package_name.]package_identifier.type_identifier;

For example, instead of being stand-alone packages, let's imagine that the Doctors package is part of a subsystem of "service provider" child units rooted at a "Providers" parent package, while the Patients package is part of a subsystem of "service consumer" child units rooted at a "Consumers" parent package:

with type Consumers.Patients.Patient_Type;
package Providers.Doctors is
  type Doctor_Type is tagged limited private;
  ...
end Providers.Doctors;
with type Providers.Doctors.Doctor_Type;
package Consumers.Patients is
  type Patient_Type is tagged limited private;
  ...
end Consumers.Patients;

In the first case, it is only the final "Patients" package identifier that is forward-declared in package Providers.Doctors. The parent package name "Consumers" must represent a library unit already present in the library, and Providers.Doctors will semantically depend upon this parent unit, just as if it had been mentioned in an ordinary "with" clause. Similarly, it is only the final "Doctors" package identifier that is forward-declared in package Consumers.Patients, and the Consumers.Patients package will semantically depend upon the parent Providers package, which must already be present in the library.

(6) Forward incomplete type declarations would provide capabilities similar to "with type" clauses, but within declarative regions rather than at the library level. A forward incomplete type introduced by a declaration would have to be completed by a package spec declaration for the package which it forward-declares. This package spec must appear later on within the same immediately enclosing declarative region as the forward incomplete type declaration.

For example, this feature would allow us to do the following:

package Medicine is


  type Doctors.Doctor_Type;
  type Patients.Patient_Type;
  
  type Doctor_Access_Type  is access all Doctors.Doctor_Type'Class;
  type Patient_Access_Type is access all Patients.Patient_Type'Class;
  

  package Doctors is

    type Doctor_Type is tagged limited private;
    
    procedure Treat_Patient
      (Doctor  : access Doctor_Type;
       Patient : access Patients.Patient_Type'Class);
             
    procedure Bill_Patient
      (Doctor  : access Doctor_Type;
       Patient : access Patients.Patient_Type'Class);
    ...
  end Doctors;
  

  package Patients with abstract is

    type Patient_Type is tagged limited private;
    
    procedure Visit_Doctor
      (Patient : access Patient_Type;
       Doctor  : access Doctors.Doctor_Type'Class);
             
    procedure Pay_Doctor
      (Patient : access Patient_Type;
       Doctor  : access Doctors.Doctor_Type'Class);
    ...
  end Patients;
  

end Medicine;

(7) One difficulty that forward incomplete types present is the fact that each one only provides a client with a forward declaration for one primary type from some package. However, this primary type might be accompanied within its package spec by auxiliary types based upon it (for example, an access type). Unfortunately, a "with type" clause would not provide the client with access to such auxiliary types. The client, left to its own devices, might be tempted to declare its own auxiliary access type, but this practice would unfortunately proliferate unnecessary extra type declarations and would force unnecessary extra type conversions.

One workaround for this problem is to avoid placing these auxiliary types within the same package spec as the primary type. Instead, one or more auxiliary package(s) can be created to hold these auxiliary types. In fact, such auxiliary packages can be "with type" clients. For instance, we might have:

with type Patients.Patient_Type; -- forward incomplete type
with Generic_Sets;
package Patients_Access is

  type Patient_Access_Type is access all Patients.Patient_Type'Class;
   
  type Patient_Index_Type is new Positive;
  type Patient_Array_Type is
    array (Patient_Index_Type range <>) of Patient_Access_Type;
  
  package Patient_Sets is new Generic_Sets (Patient_Access_Type);

end Patients_Access;

Alternatively, various people have suggested on comp.lang.ada that the "with type" syntax be enhanced in specialized ways. For instance, the following special syntax has be proposed:

with type package_id.type_id is access [all] package_id.type_id['Class];

Within this syntax, the designated type would be a forward incomplete type, whereas the access type would be forward-declared...yet complete! This appears to add more complexity to the language with only a very limited gain in utility.

(Up to Table of Contents)


[5.3] Ada OY Proposal: Liberalized Usage of Incomplete Types

Tucker's original "with type" proposal actually incorporated a second element somewhat orthogonal to the notion of forward incomplete types: He suggested that the rules in RM95 3.10.1, which restrict the use of incomplete types, be relaxed to allow incomplete types as in, out, and in out parameters in subprogram specs, and return result types in function specs. The justification for such liberalization is that, before any such subprogram could actually be invoked, the rules for the forward incomplete types would already have forced them to be completed via full "with" clauses.

The interesting thing to note is that this liberalized usage could be applied just as easily to package abstracts as to forward incomplete types. Here is an example of the former:

with abstract Patients; -- declares incomplete Patient_Type
package Doctors with abstract is

  type Doctor_Type is tagged limited private;
    
  procedure Treat_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patients.Patient_Type'Class);
     -- Patient no longer needs to be access
             
  procedure Bill_Patient
    (Doctor  : in out Doctor_Type;
     Patient : in out Patients.Patient_Type'Class);
     -- Patient no longer needs to be access
  ...
end Doctors;
with abstract Doctors; -- declares incomplete Doctor_Type
package Patients with abstract is

  type Patient_Type is tagged limited private;
    
  procedure Visit_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctors.Doctor_Type'Class);
     -- Doctor no longer needs to be access
             
  procedure Pay_Doctor
    (Patient : in out Patient_Type;
     Doctor  : in out Doctors.Doctor_Type'Class);
     -- Doctor no longer needs to be access
  ...
end Patients;

Under the forward incomplete type proposal, these package specs would appear almost identical to the above:

with type Patients.Patient_Type; -- forward incomplete type
package Doctors is
  ... -- etc., as above
end Doctors;
with type Doctors.Doctor_Type; -- forward incomplete type
package Patients is
  ... -- etc., as above
end Patients;

This change in the usage rules for incomplete types is not strictly necessary in order to resolve the "with-ing" problem, but it does add significant flexibility for applying either proposal.

(Up to Table of Contents)


[5.4] Comparison with C++

It is instructive at this point to compare the AdaOY proposals described above with the situation in C++. C++ does not suffer from anything comparable to Ada's "with-ing" problem, and the reasons why this is so are enlightening.

As Tucker Taft has pointed out (see Ada Programmer's FAQ, section 5.1), C++'s class construct combines roles which are provided by orthogonal language features in Ada. A C++ class plays the same role as an Ada type: It provides a data abstraction, defining a set of data values and a set of operations applicable to those values. At the same time, a C++ class provides functionality similar to an Ada package: It acts as a unit of modularity that provides a scope for encapsulation, information hiding, and namespace partitioning.

However, an Ada package can also act as a fundamental unit of compilation, whereas a C++ class, per se, does not. In C++, any arbitrary source file can act as a separate compilation unit. More precisely, any arbitrary output from the C++ preprocessor (after textual source-file inclusion and textual macro expansion) can act as a unit of compilation. Within such a file (or preprocessor output), a class is just a kind of declarative item.

In practice, it is customary to place the declaration ("specification") of a class within a "header file" (*.h), and the definitions ("bodies") of its member functions ("primitive subprograms") all together in a single "code file" (*.cc). Such a header file would be somewhat analogous to a library-level package spec; while the corresponding code file would be somewhat analogous to the corresponding library-level package body. However, this arrangement is only a customary practice; there is nothing in the rules of C++ that requires or enforces this practice.

The C++ preprocessor's #include directive is somewhat analogous to Ada's "with" clause: A client source file in C++ can import a header file via a #include directive, just as a client complation unit in Ada can import a package spec via a "with" clause. However, an #include directive only expresses a textual dependency between source files: a verbatim copy of the text of one source file is to be inserted within the text of another, before compiling. An #include directive does not express the same sort of semantic dependency between compilation units expressed by a "with" clause.

Because C++ does not establish any semantic connection between a header file and its corresponding code file, the programmer must explicitly tell the code file to #include the text of its own header file. From the client's side, it is not unusual for one client compilation to indirectly #include the same header file multiple times (the compilation may #include several files which all individually #include the same header file). Therefore, the designer of a given header file must use a conditional-compilation trick (using the preprocessor directives #ifndef ... #define ... #endif) to guarantee that the header file will be textually-expanded only once per compilation (otherwise there will be a compilation error when the same items are found to be declared more than once). In fact, if we make sure that the #includes are nested inside the #ifndefs, then this trick even makes it possible for two header files to #include each other: The potential infinitely-recursive mutual textual inclusion is stopped as soon as both header files have been expanded once.

However, we shouldn't ever need to have our header files #include each other, even if they happened to contain mutually-recursive classes. This is because C++ tolerates incomplete class declarations that can span source files. The same way that one Ada type can initially be declared incompletely in order to resolve a recursive dependency with another type, so too one C++ class can be forward-declared in order to resolve a recursive dependency with another class. However, unlike Ada, C++ places few requirements on where and when one must declare the completion for such a forward-declared class. One header file for one class can forward-declare another class without necessarily declaring a completion for the other class within the same header file.

Thus, we could write header files that declare C++ classes that implement the notional classes from our medical application, as follows:

// Doctor.h

#ifndef __DOCTOR_H
#define __DOCTOR_H

#include ... // other inclusions

// Note: no need for #include <Patient.h> here

class Patient; // forward class declaration
// does not need to be completed within this header
// but does need to be completed within file Doctor.cc,
// (and within certain clients), by means of
// #include <Patient.h>

class Doctor {
   ...
public:

  void treatPatient (Patient& patient);
  void billPatient  (Patient& patient);

  // Because class Patient is forward declared, these
  // member functions cannot pass these parameters by value, 
  // but pass-by-reference is quite legal and actually
  // desired here.

  ... // other Doctor member functions
};

#endif __DOCTOR_H

As you can see, in the declaration of class Doctor, the member functions treatPatient and billPatient could be declared using the forward-declaration of class Patient to specify parameters of type reference-to-Patient (Patient&). If the programmer so chose, these parameters could also have been of type pointer-to-Patient (Patient*), but they could not have been simply of type Patient (i.e., pass-by-value). The Doctor.h header file was not required to declare an actual completion for class Patient, nor is it forced to #include the Patient.h header file.

//  Patient.h

#ifndef __PATIENT_H
#define __PATIENT_H

#include ... // other inclusions

// Note: no need for #include <Doctor.h> here

class Doctor; // forward class declaration
// does not need to be completed within this header
// but does need to be completed within file Patient.cc,
// (and within certain clients), by means of
// #include <Doctor.h>

... // other forward class declarations

class Patient {
  ...
public:

  void visitDoctor (Doctor& doctor);
  void payDoctor   (Doctor& doctor);

  // Because class Doctor is forward declared, these
  // member functions cannot pass these parameters by value,
  // but pass-by-reference is quite legal and actually
  // desired here.

  ... // other Patient member functions
};

#endif __PATIENT_H

Similarly, in the declaration of class Patient, the member functions visitDoctor and payDoctor could be declared using the forward-declaration of class Doctor to specify parameters of type reference-to-Doctor (Doctor&). If the programmer so chose, these parameters could also have been of type pointer-to-Doctor (Doctor*), but they could not have been simply of type Doctor (i.e., pass-by-value). The Patient.h header file was not required to declare an actual completion for class Doctor, nor is it forced to #include the Doctor.h header file.

This arrangement appears to be directly analogous to the pattern that would be allowed in Ada0Y if the forward incomplete types proposal described in section 5.2 above were adopted: For instance, the Doctors package spec could forward-declare Patients.Patient_Type via a "with type" clause, and then use this type to declare access-mode parameters (analogous to pointer-to-class parameters in C++).

If the proposal to liberalize the usage of incomplete types described in section 5.3 above were also adopted, then these parameters could also be of mode in out or out (analogous to reference-to-class parameters in C++) or of mode in (analogous to const-reference-to-class parameters in C++). This assumes that Patients.Patient_Type is a pass-by-reference type, which would be the case if it were completed as a tagged type or a limited type (or both). It might be a reasonable restriction to require that forward incomplete types be completed only by such pass-by-reference types. Perhaps such a restriction would simplify the implementation model for these constructs. However, at this hypothetical stage, this issue is not clear, and is certainly open for discussion.

C++ requires that the forward declaration of class Patient in header file Doctor.h be completed in code file Doctor.cc, by #include-ing the header file Patient.h, before the definitions ("bodies") of the Doctor member functions can make calls to Patient member functions:

// Doctor.cc

#include <Doctor.h>
#include <Patient.h> // completes class Patient declaration
#include ...         // other inclusions

void Doctor::treatPatient (Patient& patient) {
  ...
}

void Doctor::billPatient (Patient& patient) {
  Doctor& doctor = *this;
  ...
  patient.payDoctor (doctor);  // requires completion of class Patient 
}

... // implementations of other Doctor member functions

Similarly, the forward declaration of class Doctor in header file Patient.h must be completed in code file Patient.cc, by #include-ing the header file Doctor.h, before the definitions ("bodies") of the Patient member functions can make calls to Doctor member functions:

// Patient.cc

#include <Patient.h>
#include <Doctor.h> // completes class Doctor declaration
#include ...        // other inclusions

void Patient::visitDoctor (Doctor& doctor) {
  Patient& patient = *this;
  ...
  doctor.treatPatient (patient); // requires completion of class Doctor
}

void Patient::payDoctor (Doctor& doctor) {
  ...
}

... // implementations of other Patient member functions

This is directly analogous to the requirement in Ada0Y that a client package include a "with" clause in its body to complete a forward incomplete type declared via a "with type" clause in its spec.

In short, it appears that forward incomplete types in Ada0Y would be a close analogy for the simplest arrangement that C++ allows for separately encapsulating mutually-recursive classes in different source files.

(Up to Table of Contents)


[6] Is There a Workaround in Ada95?

Yes. In a word: generics.

Think about it: What is a generic, anyway? Loosely speaking, a generic is a partially "incomplete" piece of software, with certain "blanks" that must be "filled in" later. This quality makes a generic ideal for two very important uses, one which is quite well-known and frequently hyped, and another which gets very little press at all:

  1. Reuse: Just "fill in the blanks" as many times as you like ...
  2. Deferred Coupling: Write whatever you can now, and worry about "filling in the blanks" later, when you know more ...

"Deferred coupling" is precisely what is required to solve the "with-ing" problem. Consider: What we need is some kind of type which can designate instances of a given class of objects. Yet we must be able to declare this type at a point before the interface for this class can be declared. We eventually want to associate this type with its actual designated object type, but at first this association must be deferred, in order to avoid circular dependencies.

An access type designating an incomplete type would fit the bill, if only it were possible to defer the completion of an incomplete type to a later compilation unit. Unfortunately, that's not possible in Ada95.

With generics, however, we can declare a "forward reference" type that can act as the moral equivalent of such an access type, even before the interface for its designated type can be established. Later, once the designated object type and its interface exist, we can use instantiation to "fill in the blanks", and actually bind the reference type to its object type.

The following solution exploits both uses of generics, reuse as well as deferred coupling:

(Up to Table of Contents)


[6.1] Generic package Forward

First, install the following reusable generic package into your library:

------------------------------------------------------------------------
--                                                                    --
--                          F O R W A R D                             --
--                                                                    --
--                             S p e c                                --
--                  Revision 2.04 - 31 May, 1997                      --
--                                                                    --
--  Copyright (c) 1995, 1997, John G. Volan, All Rights Reserved.     --
--                                                                    --
--  This program is free software; you can redistribute it and/or     --
--  modify it under the terms of the "Ada Community License" which    --
--  comes with this Library.  This program is distributed in the      --
--  hope that it will be useful, but WITHOUT ANY WARRANTY; without    --
--  even the implied warranty of MERCHANTABILITY or FITNESS FOR A     --
--  PARTICULAR PURPOSE.  See the Ada Community License for more       --
--  details.  You should have received a copy of the Ada Community    --
--  License with this library, in the file named "Ada Community       --
--  License". If not, contact the author of this library for a copy.  --
--                                                                    --
------------------------------------------------------------------------
generic
  -- Note: Ada allows parameterless generics
package Forward is

  ----------------------------------------------------------------------
  --  Forward.Reference_Type
  ----------------------------------------------------------------------

  type Reference_Type is private;

  -- Each instance of this generic package introduces a new
  -- Forward.Reference_Type, representing "forward references" that 
  -- will designate (presumably) a new class of objects.  This
  -- Reference_Type can eventually be "bound" to its designated object
  -- type by instantiating the nested generic package below. However,
  -- until this is done, the Reference_Type is initially "opaque", in
  -- that there is no way to actually generate any reference value
  -- other than Null_Reference, and the only operations available are
  -- assignment ( := ), predefined test for equality ( "=" ) and
  -- predefined test for inequality ( "/=" ).

  ----------------------------------------------------------------------
  --  Forward.Null_Reference
  ----------------------------------------------------------------------

  Null_Reference : constant Reference_Type;

  -- Forward.Null_Reference is a reference to no object at all.
  -- Note: Reference_Type values are guaranteed to be initialized,
  -- by default, to Null_Reference, mimicking the way access types
  -- are default initialized to null.

  ----------------------------------------------------------------------
  --  Forward.Binding
  ----------------------------------------------------------------------

  generic
    type Object_Type (<>) is abstract tagged limited private;
    type Object_Access_Type is access all Object_Type'Class;
  package Binding is

    -- An instance of this nested generic package "binds" the
    -- Reference_Type to a given arbitrary tagged type (Object_Type)
    -- and an associated general-access-to-classwide type
    -- (Object_Access_Type).  The functions below allow an instance of
    -- Object_Type to be bound to a Reference_Type value, and later 
    -- retrieved from it again.
    
    -- IMPORTANT NOTE: Only one instance of Binding is allowed per 
    -- instance of Forward. Any attempt to instantiate Binding more 
    -- than once for the same instance of Forward will result in an 
    -- exception being raised during elaboration. The intent is that
    -- there should be a strict one-to-one correspondence between each
    -- Forward.Reference_Type and a single Object_Type'Class that it is
    -- bound to. Binding a Forward.Reference_Type to more than one 
    -- Object_Type'Class would serously compromise type-safety, since
    -- the operations below would then allow a reference designating
    -- one Object_Type'Class to be re-interpreted as a reference 
    -- designating a different Object_Type'Class.

    --------------------------------------------------------------------
    --  Forward.Binding.Refer
    --------------------------------------------------------------------

    function Refer
      (Object : in Object_Access_Type)
       return Reference_Type;
    pragma Inline (Refer);

    -- This function generates an "opaque" Reference_Type value bound
    -- to a given "transparent" Object_Access_Type value. This function
    -- is renamed as a unary "+" operator as a convenience.

    function "+"
      (Object : in Object_Access_Type)
       return Reference_Type
    renames Refer;

    --------------------------------------------------------------------
    --  Forward.Binding.Deref
    --------------------------------------------------------------------

    function Deref
      (Reference : in Reference_Type)
       return Object_Access_Type;
    pragma Inline (Deref);

    -- This function retrieves the Object_Access_Type value bound to
    -- the given Reference. This function is renamed as a unary "+"
    -- operator as a convenience.

    function "+"
      (Reference : in Reference_Type)
       return Object_Access_Type
    renames Deref;

    --------------------------------------------------------------------
    --  Forward.Binding.Deref
    --------------------------------------------------------------------

    function Deref
      (Reference : in Reference_Type)
       return Object_Type'Class;
    pragma Inline (Deref);

    -- This function retrieves a constant view of the instance of
    -- Object_Type'Class bound to the given Reference.
    -- (Constraint_Error is raised if Reference = Null_Reference.)
    -- This function is renamed as a unary "+" operator as a
    -- convenience.

    function "+"
      (Reference : in Reference_Type)
       return Object_Type'Class
    renames Deref;

    --------------------------------------------------------------------
  end Binding;

private

  ----------------------------------------------------------------------
  --  Forward.Reference_Type
  ----------------------------------------------------------------------
  
  type Dummy_Type is abstract tagged limited null record;
  type Reference_Type is access all Dummy_Type'Class;

  -- This implementation of Forward exploits Unchecked_Conversion to 
  -- interconvert the "opaque" Reference_Type with the "transparent" 
  -- access type which it is eventually bound to.  This choice is
  -- controversial, but it has the benefit of being highly efficient.
  -- It avoids certain run-time checks which should be unnecessary as
  -- long as a one-to-one correspondence can be guaranteed.

  -- Just about any implementation for the Forward.Reference_Type will
  -- do, as long as it can successfully hold the bits representing a
  -- general-access-to-classwide type, without any loss of information.
  -- Implementing it as a general-access-to-classwide type designating
  -- a Dummy_Type'Class is a convenient way to do this, for two
  -- reasons:

  -- (1) It is quite likely that a given implementation of Ada95 will
  -- implement all general-access-to-classwide types with the same
  -- 'Size attribute, so this implementation is likely to be portable.
  -- (If this assumption turns out to be false, this will be detected
  -- by a test during the elaboration of an instance of Binding, and
  -- an exception will be raised.)

  -- (2) Implementing Forward.Reference_Type as an access type
  -- automatically implements the default initialization to
  -- Null_Reference, directly in terms of very thing it is meant to
  -- mimmic: the default initialization of an access type to null.

  ----------------------------------------------------------------------
  --  Forward.Null_Reference
  ----------------------------------------------------------------------

  Null_Reference : constant Reference_Type := null;

end Forward;


------------------------------------------------------------------------
--                                                                    --
--                          F O R W A R D                             --
--                                                                    --
--                             B o d y                                --
--                  Revision 2.04 - 31 May, 1997                      --
--                                                                    --
--  Copyright (c) 1995, 1997, John G. Volan, All Rights Reserved.     --
--                                                                    --
--  This program is free software; you can redistribute it and/or     --
--  modify it under the terms of the "Ada Community License" which    --
--  comes with this Library.  This program is distributed in the      --
--  hope that it will be useful, but WITHOUT ANY WARRANTY; without    --
--  even the implied warranty of MERCHANTABILITY or FITNESS FOR A     --
--  PARTICULAR PURPOSE.  See the Ada Community License for more       --
--  details.  You should have received a copy of the Ada Community    --
--  License with this library, in the file named "Ada Community       --
--  License". If not, contact the author of this Library for a copy.  --
--                                                                    --
------------------------------------------------------------------------
with Ada.Unchecked_Conversion; -- Now don't have a heart-attack! :-)

package body Forward is

  Already_Bound       : Boolean := False;
  Dual_Binding_Error  : exception;

  -- This variable and exception guarantee type-safety by ensuring
  -- that each Forward.Reference_Type will be bound to no more than one
  -- Object_Type'Class (see package initialization block below).
   
  Portability_Problem : exception;

  -- This exception is raised if it turns out that the implementation
  -- of Forward.Reference_Type can not faithfully store the value of an
  -- Object_Access_Type without loss of information.

  ----------------------------------------------------------------------
  --  Forward.Binding
  ----------------------------------------------------------------------

  package body Binding is

    --------------------------------------------------------------------
    --  Forward.Binding.Refer
    --------------------------------------------------------------------

    function Convert_To_Reference is new Ada.Unchecked_Conversion
      (Source => Object_Access_Type,
       Target => Reference_Type);

    function Refer
      (Object : in Object_Access_Type)
       return Reference_Type
    renames Convert_To_Reference;
    
    -- Given that Refer is inlined and implemented as a renaming of an
    -- instance of an Intrinsic, it is likely that an optimizing compiler
    -- can reduce the overhead of this function to zero!

    --------------------------------------------------------------------
    --  Forward.Binding.Deref
    --------------------------------------------------------------------

    function Convert_To_Object_Access is new Ada.Unchecked_Conversion
      (Source => Reference_Type,
       Target => Object_Access_Type);

    function Deref
      (Reference : in Reference_Type)
       return Object_Access_Type
    renames Convert_To_Object_Access;

    -- Given that Deref is inlined and implemented as a renaming of an
    -- instance of an Intrinsic, it is likely that an optimizing compiler
    -- can reduce the overhead of this function to zero!

    --------------------------------------------------------------------
    --  Forward.Binding.Deref
    --------------------------------------------------------------------

    function Deref
      (Reference : in Reference_Type)
       return Object_Type'Class
    is
    begin
      return Deref(Reference).all;
    end Deref;

    --------------------------------------------------------------------

  begin -- Forward.Binding

    if Reference_Type'Size /= Object_Access_Type'Size then

      raise Portability_Problem;

      -- If this occurs, then we were wrong in assuming that all
      -- general-access-to-classwide types are implemented in the
      -- same number of bits. Some other implementation of this
      -- package will need to be used instead.

    elsif Already_Bound then

      raise Dual_Binding_Error;

      -- If this occurs, then someone is attempting to violate
      -- type-safety by instantiating Binding more than once for
      -- the same instance of Forward.  Find out who is doing that,
      -- get a big mallet, and pound them over the head. :-)

      -- It would be nice if there were some way to catch this kind
      -- of violation at compile-time rather than during execution,
      -- but at least it is caught very *early* in execution---right
      -- during elaboration. There is no way that this error would
      -- go unnoticed to lie dormant in a fielded system.

    else

      Already_Bound := True;

      -- This locks out any further instantiations of Binding for the
      -- given instance of Forward.
      
      -- Note: There is a potential race condition here if multiple
      -- tasks attempt to instantiate Binding simultaneously for the
      -- same instance of Forward. The Already_Bound flag would need
      -- to be wrapped in a protected object to make this completely
      -- re-entrant. However, the overhead of a protected object is
      -- not justified, since the intention is that Forward and 
      -- Binding instantiations only be done at the library level.

    end if;

  end Binding;

end Forward;

(Up to Table of Contents)


[6.2] Instantiate the Forward package to forward-declare each object class

For every object-class X that you wish to introduce into your application, "forward-declare" the object-class by declaring an instance of the Forward generic, perhaps called Xs_Forward. This will introduce a type called Xs_Forward.Reference_Type which can be used to represent "opaque forward references" designating X objects, even though the interface to the X class has yet to be established.

For example, we could do a few quick instantiations for the object classes in our medical application domain:

with Forward;
package Doctors_Forward is new Forward;
with Forward;
package Patients_Forward is new Forward;
with Forward;
package Insurers_Forward is new Forward;
... -- other similar instantiations for other object classes

(Note that each of these would be separate compilation units.)

(Up to Table of Contents)


[6.3] Declare separate object-class interfaces (package specs) that "with" each other's Forward packages

For every object-class X, declare a package spec (perhaps called Xs) which encapsulates a (preferably limited, private) tagged type (perhaps called Xs.X_Type). local renaming of Xs_Forward to Xs.Forward will prove to be a nice convenience.) Declare primitive X_Type subprograms within this package spec as you would usually do. However, wherever a primitive X_Type operation needs a parameter of some other object-class Y, don't try to " with" the spec of package Ys at this point. Instead, "with" the Ys_Forward package, and declare the parameter as an instance of Ys_Forward.Reference_Type:

with Doctors_Forward;
with Patients_Forward;
...
package Doctors is

  package Forward renames Doctors_Forward;

  type Doctor_Type (<>) is tagged limited private;
  type Doctor_Access_Type is access all Doctor_Type'Class;
  ...
  procedure Treat_Patient
    (Doctor  : access Doctor_Type;
     Patient : in     Patients_Forward.Reference_Type);

  procedure Bill_Patient
    (Doctor  : access Doctor_Type;
     Patient : in     Patients_Forward.Reference_Type);
  ...
private

  type Doctor_Type is tagged limited
    record
      ...
    end record;

end Doctors;
with Patients_Forward;
with Doctors_Forward;
with Insurers_Forward;
...
package Patients is

  package Forward renames Patients_Forward;
  
  type Patient_Type (<>) is tagged limited private;
  type Patient_Access_Type is access all Patient_Type'Class;
  ...
  procedure Visit_Doctor
    (Patient : access Patient_Type;
     Doctor  : in     Doctors_Forward.Reference_Type);

  procedure Pay_Doctor
    (Patient : access Patient_Type;
     Doctor  : in     Doctors_Forward.Reference_Type);

  procedure File_With_Insurer
    (Patient : access Patient_Type;
     Insurer : in     Insurers_Forward.Reference_Type);
  ...
private

  type Patient_Type is tagged limited
    record
      ...
    end record;

end Patients;
with Insurers_Forward;
with Patients_Forward;
...
package Insurers is

  package Forward renames Insurers_Forward;

  type Insurer_Type (<>) is tagged limited private;
  type Insurer_Ptr_Type is access all Insurer_Type'Class;

  procedure Reimburse_Patient
    (Insurer : access Insurer_Type;
     Patient : in     Patients_Forward.Reference_Type);
   ...
   
private

  type Insurer_Type is tagged limited
    record
      ...
    end record;

end Insurers;

(Up to Table of Contents)


[6.4] Bind each object-class interface to its Forward.Reference_Type by instantiating the nested Bindings package

For each object-class X, once you have established its interface, declare a package called Xs.Binding, by instantiating the generic package Xs.Forward.Binding with the actual object type (Xs.X_Type) and its associated general-access-to-classwide type (Xs.X_Access_Type). The subprograms in the instantiation will provide the mechanism whereby "transparent" Xs.X_Access_Type values can be translated into "opaque" Xs.Forward.Reference_Type values, and then translated back again:

package Doctors.Binding is new Doctors.Forward.Binding
  (Object_Type        => Doctor_Type,
   Object_Access_Type => Doctor_Access_Type);
package Patients.Binding is new Patients.Forward.Binding
  (Object_Type        => Patient_Type,
   Object_Access_Type => Patient_Access_Type);
package Insurers.Binding is new Insurers.Forward.Binding
  (Object_Type        => Insurer_Type,
   Object_Access_Type => Insurer_Ptr_Type);
... -- other similar instantiations for other object classes

(Up to Table of Contents)


[6.5] Declare object-class implementations (package bodies) that "with" each other's interfaces (specs)

For each object-class X, implement its package body in the normal way. For every other object-class Y which X is mutually-dependent with, go ahead and have the Xs package body "with" the Ys.Binding package. (This will implicitly "with" the spec of package Ys as well.)

To get visibility to the "+" overloadings in the binding, have package body Xs "use" the Ys.Binding package as well. (Those who are generally allergic to "use" clauses may be raising objections at this point. However, in this case, these "use" clauses should do little harm to understandability, as long as the design pattern sketched above has been adhered to. The one-to-one correspondence between each object class and a single instance of Forward should make it clear which Forward.Reference_Type is involved in a given call to "+". Note that a "use" clause on the child package Ys.Binding will not automatically "use" the Ys package as well.)

Then, whenever an X subprogram must invoke a Y_Type subprogram, use the Ys.Binding.Deref function (or the equivalent "+" overloading) to convert a Ys_Forward.Reference_Type value into the corresponding Ys.Y_Access_Type value or Ys.Y_Type'Class constant view, which can then be passed into the Y subprogram:

with Doctors.Binding;  use Doctors.Binding;
with Patients.Binding; use Patients.Binding;
...
package body Doctors is

  procedure Treat_Patient
    (Doctor  : access Doctor_Type;
     Patient : in     Patients_Forward.Reference_Type) is
  begin
    ...
  end Treat_Patient;

  procedure Bill_Patient
    (Doctor  : access Doctor_Type;
     Patient : in     Patients_Forward.Reference_Type) is
  begin
    ...
    Patients.Pay_Doctor (+Patient, +Doctor);
  end Bill_Patient;

  ...
end Doctors;
with Patients.Binding; use Patients.Binding;
with Doctors.Binding;  use Doctors.Binding;
with Insurers.Binding; use Insurers.Binding;
...
package body Patients is

  procedure Visit_Doctor
    (Patient : access Patient_Type;
     Doctor  : in     Doctors_Forward.Reference_Type) is
  begin
    ...
    Doctors.Treat_Patient (+Doctor, +Patient);
  end Visit_Doctor;

  procedure Pay_Doctor
    (Patient : access Patient_Type;
     Doctor  : in     Doctors_Forward.Reference_Type) is
  begin
    ...
  end Pay_Doctor;

  procedure File_With_Insurer
    (Patient : access Patient_Type;
     Insurer : in     Insurers_Forward.Reference_Type) is
  begin
    ...
    Insurers.Reimburse_Patient (+Insurer, +Patient);
  end File_With_Insurer;
  ... 
end Patients;
with Insurers.Binding; use Insurers.Binding;
with Patients.Binding; use Patients.Binding;
...
package body Insurers is

  procedure Reimburse_Patient
    (Insurer : access Insurer_Type;
     Patient : in     Patients_Forward.Reference_Type) is
  begin
    ...
  end Reimburse_Patient;

end Insurers;

(Up to Table of Contents)


[6.6] Client Code Example

Presumably we want to write client code that avails itself of the mutually-dependent relationships between our various classes. Whenever this happens, we'll need to make calls to the Xs.Binding.Refer function (or the equivalent "+" overloading) in order to pack an Xs.X_Access_Type value into an Xs_Forward.Reference_Type value. For instance:

with Doctors.Binding;  use Doctors.Binding;
with Patients.Binding; use Patients.Binding;
with Insurers.Binding; use Insurers.Binding;
...
procedure Silly_Example is

  -- generating Reference_Type values from access type values
  -- via Refer (i.e., "+"):

  Doctor_Array :
    constant array (1..3) of Doctors.Forward.Reference_Type := 
      (others => +new Doctors.Doctor_Type);

  Patient_Array :
    constant array (1..3) of Patients.Forward.Reference_Type :=
      (others => +new Patients.Patient_Type);

  Insurer_Array :
    constant array (1..3) of Insurers.Forward.Reference_Type :=
      (others => +new Insurers.Insurers_Type);
      

  procedure Transaction
    (Doctor  : in Doctors.Forward.Reference_Type;
     Patient : in Patients.Forward.Reference_Type;
     Insurer : in Insurers.Forward.Reference_Type)
  is
  begin

    -- retrieving access values from Reference_Type values
    -- via Deref (i.e., "+"):
    
    Patients.Visit_Doctor       -- invokes Doctors.Treat_Patient
      (+Patient, Doctor);

    Doctors.Bill_Patient        -- invokes Patients.Pay_Doctor
      (+Doctor,  Patient);

    Patients.File_With_Insurer  -- invokes Insurers.Reimburse_Patient
      (+Patient, Insurer);

  end Transaction;

begin
  for Doctor_Index in Doctor_Array'Range loop
    for Patient_Index in Patient_Array'Range loop
      for Insurer_Index in Insurer_Array'Range loop
        Transaction
          (Doctor  => Doctor_Array(Doctor_Index),
           Patient => Patient_Array(Patient_Index),
           Insurer => Insurer_Array(Insurer_Index));
      end loop;
    end loop;
  end loop;
end Silly_Example;

And there you have it! No more "with-ing" problem!

(Up to Table of Contents)


[7] Despite your assurances of type-safety, I really don't like the fact that you used Unchecked_Conversion. Isn't there some other way?

Certainly! Take another look back at section 3.5. The suggestion was made there that we could derive all our tagged types from a universal root abstract tagged type, Objects.Object_Type, and then just use the classwide type Objects.Object_Type'Class whenever we encountered any mutual-dependency problems. This wasn't very satisfying because it gave up on the notion of static strong typing. However, this would work very well as the underlying mechanism for an alternative implementation of the generic Forward package.

Actually, to distinguish this implementation of generic forwarding from the previous one, and to reinforce how this version depends on the root Objects package, let's make this version of the Forward package a child of the Objects package. (This also makes it slightly more convenient to write.)

Here's a sketch:

package Objects.Forward is

  type Reference_Type is private;
  
  Null_Reference : constant Reference_Type;
  
  generic
    type Item_Type (<>) is abstract new Object_Type with private;
    type Item_Access_Type is access all Item_Type'Class;
  package Binding is
  
    function Refer
      (Item : in Item_Access_Type)
       return Reference_Type;
    pragma Inline (Refer);

    function "+"
      (Item : in Item_Access_Type)
       return Reference_Type
    renames Refer;

    function Deref
      (Reference : in Reference_Type)
       return Item_Access_Type;
    pragma Inline (Deref);

    function "+"
      (Reference : in Reference_Type)
       return Item_Access_Type
    renames Deref;

    function Deref
      (Reference : in Reference_Type)
       return Item_Type'Class;
    pragma Inline (Deref);

    function "+"
      (Reference : in Reference_Type)
       return Item_Type'Class
    renames Deref;

  end Binding;

private
  type Reference_Type is access all Object_Type'Class;
  Null_Reference : constant Reference_Type := null;
end Objects.Forward;
-- no bloody Unchecked_Conversion! :-)
package body Objects.Forward is

  Already_Bound       : Boolean := False;
  Dual_Binding_Error  : exception;

  package body Binding is

    function Refer
      (Item : in Item_Access_Type)
       return Reference_Type
    is
    begin
      return Reference_Type(Item); -- "widening" conversion
    end Refer;

    function Deref
      (Reference : in Reference_Type)
       return Item_Access_Type
    is
    begin
      return Item_Access_Type(Reference); -- "narrowing" conversion
    end Deref;

    function Deref
      (Reference : in Reference_Type)
       return Item_Type'Class
    is
    begin
      return Item_Access_Type(Reference).all;
    end Deref;

  begin
    if Already_Bound then
      raise Dual_Binding_Error;
    else
      Already_Bound := True;
    end if;
  end Binding;
end Objects.Forward;

The advantage of this implementation is that it's totally portable (so there would be no need for that 'Size portability check during instance elaboration). Moreover, this technique is guaranteed type-safe. Strictly speaking, there would not even be any need to guard against dual binding. If a client were foolhardy enough generate a forward reference value designating one type of object, but then attempt to extract a different type of object from it, the tag check already built into Ada 95's "narrowing" conversion would wind up raising Constraint_Error. Unfortunately, this kind of bug could lie dormant in a fielded system until the offending code was actually executed, so it would be wise to keep the dual-binding check anyway.

One disadvantage is that the "narrowing" conversion would entail extra run-time overhead for the tag check, which theoretically would be redundant once the dual-binding check was done. Pragma Suppress could probably solve this, however.

A bigger disadvantage to this implementation is that it requires all of our tagged types (or at least those which need to participate in mutual dependencies) to be derived from a single universal root type (Objects.Object_Type). This may make it difficult to incorporate mutual dependencies into types derived from third-party reusable libraries. For this reason alone, I prefer going with the Unchecked_Conversion scheme I presented above.

However, consider the case of CORBA in Ada: When the CORBA IDL-to-Ada95 mapping was being designed, the generic forwarding pattern I have described proved to be an extremely simple solution to the issue of how to map mutually-dependent IDL interfaces, and forward IDL interface declarations, into Ada95. The result was the specification of the CORBA.Forward generic package. As it turns out, the variation of forcing all object-classes to inherit from a common universal root type was not a burden; in fact, it was already being imposed for other reasons. Thus, the CORBA.Object.Ref type is the common universal root type from which a CORBA IDL-to-Ada compiler derives all the distributed-object reference types it generates from IDL interface specs. The CORBA.Forward package exploits this type hierarchy to declare the private implementation of an X_Forward.Ref type as just another derivative of CORBA.Object.Ref. It's (apparently) a simple matter to convert between some X_Forward.Ref type and some specific X.Ref type visibly derived from CORBA.Object.Ref. For more details, see http://ms.ie.org/corba-ada/abwg-omg.html#Results.

(Up to Table of Contents)


[8] Revision History

Rev Num TBD
Rev Date TBD
Author TBD
Comment Add a reference to the workaround described in the Norman Cohen's book Ada as a Second Language, where inheritance is exploited instead of generics. Describe the lack of orthogonality in that technique, leading to a potential "inheritance collision" problem.

Rev Num 2.06
Rev Date 3-June-1997
Author John G. Volan
Comment Adjusted color scheme for MS Internet Explorer.

Rev Num 2.05
Rev Date 1-June-1997
Author John G. Volan
Comment Clarified some awkward wording in section 5.4 (comparison with C++). In section 7, rewrote the paragraph on the CORBA.Forward package to eliminate some confusing wording. Added the "TBD" Revision.

Rev Num 2.04
Rev Date 31-May-1997
Author John G. Volan
Comment Changed all source code excerpts and revision history entries to tables. Changed color scheme. Added WithingAdas.gif (and other gifs). Added section 5.4, comparing Ada0Y proposals with C++ forward class declarations. Changed copyright notice on the header of the Forward package from the LGPL to the Ada Community License. In section 7, changed the alternate version of the Forward package to be a child of package Objects. Corrected yet more typos.

Rev Num 2.03
Rev Date 29-May-1997
Author John G. Volan
Comment Cosmetic improvements. General spiffing-up. Changed 3-tier diagram to transparent GIF89. Corrected more typos.

Rev Num 2.02
Rev Date 28-May-1997
Author John G. Volan
Comment Corrected minor (but embarrassing :-) ) typos. Added comments to the Forward package explaining its type-safety guarantee.

Rev Num 2.01
Rev Date 27-May-1997
Author John G. Volan
Comment Converted to HTML. Converted 3-tier dependency drawing to JPEG. Removed references to "identity" concept, replaced with "forward" concept. Changed "Identity" package to "Forward", updated naming convention. Added discussion of package abstracts and forward incomplete types ("with type" clauses). Added substantial fleshing out in section 3.

Rev Num 1.02
Rev Date 29-August-1996
Author John G. Volan
Comment Corrected typographical errors. Per Ed Falis (Alsys/Thomson/Aonix), fleshed out sketch in 6.6, to avoid suggesting a situation that would violate a static accessibility check.

Rev Num 1.00
Rev Date 25-August-1995
Author John G. Volan
Comment Initial release on comp.lang.ada.

(Up to Table of Contents)


[9] Credits

The following persons have contributed, directly or indirectly (e.g. through comp.lang.ada), to the information gathered in this FAQ: Kenneth Almquist, Jon S. Anthony, John Barton, R. William Beckwith, Adam Beneschan, Mark A. Biggar, Norman H. Cohen, Tim Coslet, Cyrille Comar, Robert Dewar, Robert A. Duff, Robert I. Eachus, Ed Falis, Fergus Henderson, Matt Kennel, Magnus Kempe, Harry Koehnemann, David Kristola, Jay Martin, Tucker Taft.

(Up to Table of Contents)


[10] Copyright Notice

This FAQ is Copyright ©1995-1997 by John G. Volan. It may be freely redistributed as long as it is completely unmodified and no attempt is made to restrict any recipient from redistributing it on the same terms. It may not be sold or incorporated into commercial documents without the explicit written permission of the copyright holder.

Permission is granted for this document to be made available under the same conditions for file transfer from sites offering unrestricted file transfer on the Internet and from Forums on e.g. Compuserve and Bix.

This document is provided as is, without any warranty.

(Up to Table of Contents)


John G.Volan -- johnvolan@sprintmail.com