Version 1.1 of ais/ai-00299.txt
!standard 8.5.5 (2) 02-06-12 AI95-00299/01
!standard 12.4 (6)
!standard 12.5 (2)
!standard 12.7 (2)
!class amendment 02-06-12
!status work item 02-06-12
!status received 02-06-12
!priority Medium
!difficulty Medium
!subject Defaults for generic formal parameters
!summary
It is proposed to modify the syntax of generic formal type, package, and
"in out" object parameters to permit default expressions. It is also
proposed to modify the syntax of generic renaming declarations to permit
supplying defaults.
!problem
Ada 95 does not have defaults for generic formal types or packages,
nor can generic renamings supply defaults for (some of) the formal
parameters of the renamed generic unit. While not a serious
problem (users can always be forced to supply explicit values for
all parameters), it is sometimes a nuisance and unnecessarily
complicates some generic instantiations. Allowing more kinds of
defaults for generic formal parameters can significantly ease the
use of generic units.
!proposal
1. Defaults for generic formal types.
-------------------------------------
12.5(2) is modified to read
formal_type_declaration ::=
type defining_identifier [discriminant_part] is
formal_type_definition [use subtype_mark];
The idea is that the optional "use subtype_mark" defines a default type
to be taken if an instantiation does not explicitly provide an actual
for that parameter. The type denoted by "subtype_mark" must fulfill all
the constraints/rules as if it occurred as a generic actual in a generic
parameter association.
Note: one might also consider using ":=" instead of "use", but that's
a purely syntactic issue.
2. Defaults for generic formal packages
---------------------------------------
12.7 (2) is modified to read
formal_package_declaration ::=
with package defining_identifier is
new generic_package_name formal_actual_part [:= package_name];
with the intention that the ":= package_name" defines a default package
to be taken if an instantiation does not explicitly provide an actual
for that parameter. The package denoted by "package_name" must be an
instantiation of the generic package denoted by "generic_package_name"
such that it fulfils the constraints imposed by the formal_actual_part.
3. Defaults in generic renamings
--------------------------------
8.5.5(2) is modified to read
generic_renaming_declaration ::=
generic package defining_program_unit_name
renames generic_package_name [generic_actual_part];
|generic procedure defining_program_unit_name
renames generic_procedure_name [generic_actual_part];
|generic function defining_program_unit_name
renames generic_function_name [generic_actual_part];
The generic_actual_part may be incomplete in the sense that it is allowed
to define actuals only for a subset of the generic formal parameters
of the unit denoted by the generic_unit_name following the "renames".
Those actuals given are taken as defaults for the corresponding generic
formals for the renaming. In other word, a generic renaming may supply
additional default values for generic formals that in the renamed unit
did not have defaults, and it may change defaults of formals for which
the declaration of the renamed unit already defined defaults.
The actuals provided in a generic renaming declaration are just defaults;
an instantiation of the unit declared by the generic renaming declaration
may override those defaults by explicitly providing values for these
formal parameters.
4. Defaults for generic formal "in out" objects
-----------------------------------------------
It is proposed to delete paragraph 12.4(6).
As a result, generic formal "in out" objects may have defaults, too.
!discussion
All of these changes aim at making generic instantiations simpler to write,
or rather, to give designers of generic units the means to design their
units such that instantiations can be made simpler than today. It is often
possible to supply reasonable default values for some of the entities to
which the above proposal applies.
I cannot judge up-front how difficult or complex this would be to
implement, but it appears to me to be relatively straight-forward: one
just has to maintain info on what the defaults are, and in an instantiation,
supply these defaults as the actuals for any generic formals that do not
have explicit actuals. The whole instantiation process then should remain
unchanged.
All of these four change items have come up in the development of a library
of container abstractions. A short discussion on comp.lang.ada revealed that
apparently I'm not alone with these ideas; several developers of container
libraries have come across these issues and perceive them as shortcomings
of Ada 95.
!example
An example for defaults for generic formal types
------------------------------------------------
Consider a list container with an operation returning the number of items
currently in the list.
generic
type Item_Type is private;
with function "=" (L, R : in Item_Type) return Boolean;
package Lists is
type List is private;
procedure Append
(L : in out List;
Item : in Item_Type);
function Length
(L : in List)
return Natural;
--
private
type Node;
type Link is access all Node;
type Node is
record
Data : Item_Type;
Next, Prev : Link;
end record;
type List is new Ada.Finalization.Controlled with
record
N : Natural := 0;
Head, Tail : Link;
end record;
--
end Lists;
All operations would maintain L.N, e.g. Append would add 1. When this came
up on comp.lang.ada, some people argued that if one has lists of oranges and
lists of apples, these should be counted using different types to avoid
inadvertantly adding apple counts and orange counts. Hence the interface
should be modified:
generic
type Item_Type is private;
type Item_Count is range <>; --
with function "=" (L, R : in Item_Type) return Boolean;
package Lists is
--
function Length
(L : in List)
return Item_Count;
private
type List is new Ada.Finalization.Controlled with
record
N : Item_Count'Base := 0;
Head, Tail : Link;
end record;
end Lists;
Well, clearly not everybody agrees with that, and some people don't care, and
some may say "well, Item_Count has that funny requirement that zero be included,
so let's use Natural", so in a general container package, it might well make
sense to provide a default (namely Natural) for the Item_Count type. With that,
the generic package could cater for both styles, at no extra cost:
generic
type Item_Type is private;
type Item_Count is range <> use Natural; --
with function "=" (L, R : in Item_Type) return Boolean;
package Lists is
...
end Lists;
The examples below also use defaults for generic formal types.
An example for defaults in generic renamings
--------------------------------------------
Consider a hash table container abstraction:
generic
type Key_Type (<>) is private;
type Item_Type (<>) is private;
with function Hash (Key : in Key_Type) return Natural;
with function "=" (L, R : in Key_Type) return Boolean is <>;
Initial_Default_Size : Positive := 23;
--
package Hash_Tables is
type Hash_Table is private;
procedure Insert
(Table : in out Hash_Table;
Key : in Key_Type;
Item : in Item_Type);
--
end Hash_Tables;
Now suppose for some reason, someone wants a version of this generic package
in which all hash tables initially get some larger size. Easy to do with a
generic renaming, supplying a new default for Initial_Default_Size:
generic package Large_Hash_Tables
renames Hash_Tables (Initial_Default_Size => 101);
And an instantiation like
package My_Small_Hash_Tables is
new Hash_Tables (String, My_Data, My_Hash_Function, "=");
will offer hash tables that initially have a small size (23), whereas an
instantiation like
package My_Large_Hash_Tables is
new Large_Hash_Tables (String, My_Data, My_Hash_Function, "=");
will offer hash tables that initially have a larger size (101).
Granted, the second instantiation could also have been written as
package My_Large_Hash_Tables is
new Hash_Tables (String, My_Data, My_Hash_Function, "=", 101);
so maybe the example is not exactly the best, but it still nicely illustrates
how providing defaults in a renaming may simplify things. Maybe a better
example is to use a renaming to capture the most common case, namely the
one where the key type is String (this example uses the proposed defaults for
generic formal types):
package Hash_Support is
function Hash_String (S : in String) return Natural;
end Hash_Support;
with Hash_Support;
generic package String_Hash_Tables
renames Hash_Tables (Key_Type => String, Hash => Hash_Support.Hash_String);
and then, a hash table package with string keys can be gotten simply by
package My_Hash_Tables is
new String_Hash_Tables (Item_Type => My_Data);
package My_Other_Hash_Tables is
new String_Hash_Tables (Item_Type => My_Other_Data);
An example for defaults for generic formal packages
---------------------------------------------------
Again, consider a generic hash table package, but this time using a signature
package for the key type:
generic
type Key_Type (<>) is private;
with function Hash (Key : in Key_Type) return Natural;
with function "=" (L, R : in Key_Type) return Boolean is <>;
package Hashable is
--
end Hashable;
generic
with package Keys is new Hashable (<>);
type Item_Type (<>) is private;
Initial_Default_Size : Positive := 23;
package Hash_Tables is
type Hash_Table is private;
procedure Insert
(Table : in out Hash_Table;
Key : in Keys.Key_Type;
Item : in Item_Type);
--
end Hash_Tables;
Then, one could get a package for hash tables with strings as keys as
follows:
with Hash_Support;
package String_Keys is
new Hashable (Key_Type => String, Hash => Hash_Support.Hash_String);
generic package String_Hash_Tables
renames Hash_Tables (Keys => String_Keys);
with the same instantiations for finally getting concrete hash tables:
package My_Hash_Tables is
new String_Hash_Tables (Item_Type => My_Data);
package My_Other_Hash_Tables is
new String_Hash_Tables (Item_Type => My_Other_Data);
An example for defaults for generic formal "in out" objects
-----------------------------------------------------------
The motivating example for case #4; i.e. default values for generic formal
"in out" objects; is the storage pool parameter often found in container
libraries:
generic
type Item_Type is private;
with function "=" (L, R : in Item_Type) return Boolean is <>;
Pool : in out System.Storage.Root_Storage_Pool'Class;
package Lists is
type List is private;
--
private
type Node;
type Link is access all Node;
for Link'Storage_Pool use Pool;
type Node is
record
Data : Item_Type;
Next, Prev : Link;
end record;
type List is new Ada.Finalization.Controlled with
record
Head, Tail : Link;
end record;
end Lists;
Many users just don't care about storage pools (or even don't know what they
are be good for). Advanced users, however, may well want to specify the pool
in which the list should allocate its nodes, hence adding the pool as a generic
formal parameter clearly makes sense. Unfortunately, it confuses less advanced
users (and generally complicates instantiation in those cases where one really
doesn't care).
If one could provide a default value for "Pool", this confusion or complication
could be avoided: we could simply declare the package as
generic
type Item_Type is private;
with function "=" (L, R : in Item_Type) return Boolean is <>;
Pool : in out System.Storage.Root_Storage_Pool'Class := The_Default_Pool;
package Lists is
...
I'll leave open what "The_Default_Pool" actually might be; on GNAT, a possible
working implementation might be a pre-written pool that just uses the standard
pool of some arbitrary access type. (Not right in a strict sense, but this is
indeed a way that works -- on GNAT, at least.) See also my other proposal on
"standard storage pool".
But anyway, with defaults for generic formal "in out" objects, an instantiation
of this list package can be as simple as
package My_Lists is new Lists (My_Type);
Inexperienced users or users that chose not to care about storage pools can
use the package without extra hassles, while experienced or concerned users
still have the possibility to provide their own storage pool.
An illegal example for #4 (default for generic formal "in out" object):
declare
type Ptr is access Integer;
procedure Free is new Ada.Unchecked_Deallocation (Integer, Ptr);
P : Ptr := new Integer'(42);
generic
X : in out Integer := P.all;
function Foo return Natural;
function Foo is
begin
return abs (X);
end Foo;
begin
Free (P);
declare
function Bar is new Foo;
begin
Ada.Text_IO.Put_Line (Natural'Image (Bar));
end;
end;
However, I do not consider this case a showstopper for the proposal,
because equally illegal stuff can already be done using a default value
for a generic formal "in" object:
declare
type Ptr is access Integer;
procedure Free is new Ada.Unchecked_Deallocation (Integer, Ptr);
P : Ptr := new Integer'(42);
generic
X : in Ptr := P;
function Foo return Natural;
function Foo is
begin
return abs (X.all);
end Foo;
begin
Free (P);
declare
function Bar is new Foo;
begin
--
Ada.Text_IO.Put_Line (Natural'Image (Bar));
end;
end;
(If the first call to "Free" is commented out, and the second one commented
in, the effects are even more devastating. Try this with GNAT! [On Win NT,
I get a Constraint_Error with the first "Free" (that's ok), while the
program prints "0" with the second one (brrr!)]. That's not a critique of
GNAT, but shows that stupid things can already be done.)
In summary, I think the proposed additions would significantly enhance
the capabilities of generics in Ada and be very useful for designing truly
general and powerful generic abstractions that still could be easily
instantiated in simple cases (because any generic parameters for advanced
features would have sensible defaults). I also believe that these new defaults
incur only a small to moderate implementation cost for compilers (although,
me not being an Ada compiler guru, I might be mistaken).
!ACATS test
!appendix
Editor's note: The original proposal was submitted by Thomas Wolf on
Wednesday, June 12, 2002. It was edited slightly, but otherwise used intact
as the initial draft of this AI.
****************************************************************
From: Randy Brukardt
Sent: Wednesday, June 12, 2002 10:32 PM
Thanks for writing this, it saved me from having to do it. (I was going to
make a similar proposal when I got a round tuit.
> I cannot judge up-front how difficult or complex this would be to
> implement, but it appears to me to be relatively straight-forward: one
> just has to maintain info on what the defaults are, and in an instantiation,
> supply these defaults as the actuals for any generic formals that do not
> have explicit actuals. The whole instantiation process then should remain
> unchanged.
I'm concerned that the generic renaming would be significant work, as the
idea of "partial actual parts" would be new, and would require a new
mechanism to handle. Resolving these could be tricky, especially if they are
mixed positional and named parameters. I have to wonder if these would be
worth the work.
Otherwise, I don't think that there would be significant implementation
problem (if I did, I wouldn't have planned to introduce the idea).
I'm not sure about the syntax, but that just carries over from CLA.
****************************************************************
Questions? Ask the ACAA Technical Agent