3.10.1.4 Use of Alternative Implementations

In some cases, none of the approaches described above are adequate. This can occur, for example, if the set of declarations required is radically different for two different configurations.

In this situation, the official Ada way of dealing with conditionalizing such code is to write separate units for the different cases. As long as this doesn’t result in excessive duplication of code, you can do this without creating maintenance problems. The approach is to share common code as far as possible and then isolate the code and declarations that are different. Subunits are often a convenient method for breaking out a piece of a unit that you need to be conditionalized, with separate files for different versions of the subunit for different targets, where the build script selects the right one to give to the compiler.

As an example, consider a situation where a new feature in Ada 2005 allows something to be done in a really nice way. But your code must be able to compile with an Ada 95 compiler. Conceptually you want to say:

if Ada_2005 then
   ... neat Ada 2005 code
else
   ... not quite as neat Ada 95 code
end if;

where Ada_2005 is a Boolean constant.

But this won’t work when Ada_2005 is set to False, since the then clause will be illegal for an Ada 95 compiler. (Recall that although such unreachable code would eventually be deleted by the compiler, it still needs to be legal. If it uses features introduced in Ada 2005, it’s still illegal in Ada 95.)

So instead, we write

procedure Insert is separate;

Then we have two files for the subunit Insert, with the two sets of code. If the package containing this is called File_Queries, then we might have two files

and the build script renames the appropriate file to file_queries-insert.adb and then carries out the compilation.

This can also be done with project files’ naming schemes. For example:

for body ("File_Queries.Insert") use "file_queries-insert-2005.ada";

Note also that with project files, you should use a different extension than ads / adb for alternative versions. Otherwise, a naming conflict may arise through another commonly used feature: declaring as part of the project a set of directories containing all the sources obeying the default naming scheme.

The use of alternative units is certainly feasible in all situations, and for example the Ada part of the GNAT run-time is conditionalized based on the target architecture using this approach. As a specific example, consider the implementation of the AST feature in VMS. There is one spec: s-asthan.ads which is the same for all architectures, and three bodies:

The dummy version s-asthan.adb simply raises exceptions noting that this operating system feature is not available and the two remaining versions interface with the corresponding versions of VMS to provide VMS-compatible AST handling. The GNAT build script knows the architecture and operating system, and automatically selects the right version, renaming it if necessary to s-asthan.adb before the run-time build.

Another style for arranging alternative implementations is through Ada’s access-to-subprogram facility. In case some functionality is to be conditionally included, you can declare an access-to-procedure variable Ref that is initialized to designate a ‘do nothing’ procedure, and then invoke Ref.all when appropriate. Then, in, some library package, set Ref to Proc'Access for some procedure Proc that performs the relevant processing. The initialization only occurs if the library package is included in the program. The same idea can also be implemented using tagged types and dispatching calls.