Rationale for Ada 2005
1.3.5 Overview: Exceptions, numerics, generics etc
As well as the major features discussed above there
are also a number of improvements in various other areas.
There are two small
changes concerning exceptions. One is that we can give a message with
a raise statement, thus
raise Some_Error with "A message";
This is a lot neater
than having to write (as in Ada 95)
Ada.Exceptions.Raise_Exception(Some_Error'Identity, "A message");
The other change concerns
the detection of a null exception occurrence which might be useful in
a package analysing a log of exceptions. The problem is that exception
occurrences are of a limited private type and so we cannot compare an
occurrence with Null_Occurrence to see if
they are equal. In Ada 95 applying the function Exception_Identity
to a null occurrence unhelpfully raises Constraint_Error.
This has been changed in Ada 2005 to return Null_Id
so that we can now write
procedure Process_Ex(X: Exception_Occurrence) is
begin
if Exception_Identity(X) = Null_Id then
-- process the case of a Null_Occurrence
...
end Process_Ex;
Ada 95 introduced modular
types which are of course unsigned integers. However it has in certain
cases proved very difficult to get unsigned integers and signed integers
to work together. This is a trivial matter in fragile languages such
as C but in Ada the type model has proved obstructive. The basic problem
is converting a value of a signed type which happens to be negative to
an unsigned type. Thus suppose we want to add a signed offset to an unsigned
address value, we might have
type Offset_Type is range –(2**31) .. 2**31–1;
type Address_Type is mod 2**32;
Offset: Offset_Type;
Address: Address_Type;
We cannot just add
Offset to
Address
because they are of different types. If we convert the
Offset
to the address type then we might get
Constraint_Error
and so on. The solution in Ada 2005 is to use a new functional attribute
S'Mod which applies to any modular subtype
S and converts a universal integer value to
the modular type using the corresponding mathematical mod operation.
So we can now write
Address := Address + Address_Type'Mod(Offset);
Another new attribute is
Machine_Rounding.
This enables high-performance conversions from floating point types to
integer types when the exact rounding does not matter.
The third numeric change concerns fixed point types.
It was common practice for some Ada 83 programs to define their own multiply
and divide operations, perhaps to obtain saturation arithmetic. These
programs ran afoul of the Ada 95 rules that introduced universal fixed
operations and resulted in ambiguities. Without going into details, this
problem has been fixed in Ada 2005 so that user-defined operations can
now be used.
Ada 2005 has several
new pragmas. The first is
pragma Unsuppress(Identifier);
where the identifier
is that of a check such as
Range_Check.
The general idea is to ensure that checks are performed in a declarative
region irrespective of the use of a corresponding pragma
Suppress.
Thus we might have a type
My_Int that behaves
as a saturated type. Writing
function "*" (Left, Right: My_Int) return My_Int is
pragma Unsuppress(Overflow_Check);
begin
return Integer(Left) * Integer(Right);
exception
when Constraint_Error =>
if (Left>0 and Right>0) or (Left<0 and Right<0) then
return My_Int'Last;
else
return My_Int'First;
end if;
end "*";
ensures that the code always works as intended even
if checks are suppressed in the program as a whole. Incidentally the
On parameter of pragma Suppress
which never worked well has been banished to Annex J.
Many implementations
of Ada 95 support a pragma
Assert and this
is now consolidated into Ada 2005. The general idea is that we can write
pragmas such as
pragma Assert(X >50);
pragma Assert(not Buffer_Full, "buffer is full");
The first parameter
is a Boolean expression and the second (and optional) parameter is a
string. If at the point of the pragma at execution time, the expression
is
False then action can be taken. The action
is controlled by another pragma
Assertion_Policy
which can switch the assertion mechanism on and off by one of
pragma Assertion_Policy(Check);
pragma Assertion_Policy(Ignore);
If the policy is to check then the exception Assertion_Error
is raised with the message, if any. This exception is declared in the
predefined package Ada.Assertions. There are
some other facilities as well.
The pragma
No_Return
also concerns exceptions. It can be applied to a procedure (not to a
function) and indicates that the procedure never returns normally but
only by propagating an exception (it might also loop for ever). Thus
procedure Fatal_Error(Message: in String);
pragma No_Return(Fatal_Error);
And now whenever we call Fatal_Error
the compiler is assured that control is not returned and this might enable
some optimization or better diagnostic messages.
Note that this pragma applies to the predefined procedure
Ada.Exceptions.Raise_Exception.
Another new pragma is
Preelaborable_Initialization.
This is used with private types and indicates that the full type will
have preelaborable initialization. A number of examples occur with the
predefined packages such as
pragma Preelaborable_Initialization(Controlled);
in Ada.Finalization.
Finally, there is the pragma
Unchecked_Union.
This is useful for interfacing to programs written in C that use the
concept of unions. Unions in C correspond to variant types in Ada but
do not store any discriminant which is entirely in the mind of the C
programmer. The pragma enables a C union to be mapped to an Ada variant
record type by omitting the storage for the discriminant.
If the C program has
union {
double spvalue;
struct {
int length;
double* first;
} mpvalue;
} number;
then this can be mapped
in the Ada program by
type Number(Kind: Precision) is
record
case Kind is
when Single_Precision =>
SP_Value: Long_Float;
when Multiple_Precision =>
MP_Value_Length: Integer;
MP_Value_First: access Long_Float;
end case;
end record;
pragma Unchecked_Union(Number);
One problem with pragmas (and attributes) is that
many implementations have added implementation defined ones (as they
are indeed permitted to do). However, this can impede portability from
one implementation to another. To overcome this there are further
Restrictions
identifiers so we can write
pragma Restrictions(No_Implementation_Pragmas, No_Implementation_Attributes);
Observe that one of the goals of Ada 2005 has been
to standardize as many of the implementation defined attributes and pragmas
as possible.
Readers might care to consider the paradox that GNAT
has an (implementation-defined) restrictions identifier No_Implementation_Restrictions.
Another new restrictions
identifier prevents us from inadvertently using features in Annex J thus
pragma Restrictions(No_Obsolescent_Features);
Similarly we can use
the restrictions identifier No_Dependence
to state that a program does not depend on a given library unit. Thus
we might write
pragma Restrictions(No_Dependence => Ada.Command_Line);
Note that the unit mentioned might be a predefined
library unit as in the above example but it can also be used with any
library unit.
The final new general feature concerns formal generic
package parameters. Ada 95 introduced the ability to have formal packages
as parameters of generic units. This greatly reduced the need for long
generic parameter lists since the formal package encapsulated them.
Sometimes it is necessary
for a generic unit to have two (or more) formal packages. When this happens
it is often the case that some of the actual parameters of one formal
package must be identical to those of the other. In order to permit this
there are two forms of generic parameters. One possibility is
generic
with package P is new Q(<>);
package Gen is ...
and then the package
Gen can be instantiated with any package that
is an instantiation of Q. On the other hand
we can have
generic
with package R is new S(P1, P2, ... );
package Gen is ...
and then the package Gen
can only be instantiated with a package that is an instantiation of S
with the given actual parameters P1, P2
etc.
These mechanisms are
often used together as in
generic
with package P is new Q(<>);
with package R is new S(P.F1);
package Gen is ...
This ensures that the instantiation of S
has the same actual parameter (assumed only one in this example) as the
parameter F1 of Q
used in the instantiation of Q to create the
actual package corresponding to P.
There is an example
of this in one of the packages for vectors and matrices in ISO/IEC 13813
which is now incorporated into Ada 2005 (see Section
1.3.6).
The generic package for complex arrays has two package parameters. One
is the corresponding package for real arrays and the other is the package
Generic_Complex_Types from the existing Numerics
annex. Both of these packages have a floating type as their single formal
parameter and it is important that both instantiations use the same floating
type (eg both
Float and not one
Float
and one
Long_Float) otherwise a terrible mess
will occur. This is assured by writing (using some abbreviations)
with ... ;
generic
with package Real_Arrays is new Generic_Real_Arrays(<>);
with package Complex_Types is new Generic_Complex_Types(Real_Arrays.Real);
package Generic_Complex_Arrays is ...
Well this works fine in simple cases (the reader
may wonder whether this example is simple anyway) but in more elaborate
situations it is a pain. The trouble is that we have to give all the
parameters for the formal package or none at all in Ada 95.
Ada 2005 permits only
some of the parameters to be specified, and any not specified can be
indicated using the box. So we can write any of
with package Q is new R(P1, P2, F3 => <>);
with package Q is new R(P1, others => <>);
with package Q is new R(F1 => <>, F2 => P2, F3 => P3);
Note that the existing form (<>)
is now deemed to be a shorthand for (others => <>). As with aggregates, the form <>
is only permitted with named notation.
Examples using this new facility will be given in
a later chapter (see
6.5).
© 2005, 2006 John Barnes Informatics.
Sponsored in part by: