The template class Stack
specified below
is an example of the specification of a template class.
This example is simple in the sense that it has minimal expectations
of its type parameter.
It does, however, illustrate some of the subtle features of working
with contained objects in a template.
The expects-clause below says that the trait
contained_objects(T)
(see section 7.5 Contained Objects)
must be passed as a trait actual parameter when this template
is instantiated.
A discussion on how this expected trait
is used to define contained_objects
for instances of the Stack
template is found after the example.
// @(#)$Id: SimpleStack.lh,v 1.18 1998/09/23 16:01:14 ruby Exp $ template <class T /*@ expects contained_objects(T) @*/ > class Stack { public: //@ uses SimpleStackTrait(T for E, Stack<T> for C); Stack() throw(); //@ behavior { //@ constructs self; //@ ensures liberally self' = empty; //@ } void push(T e) throw(); //@ behavior { //@ modifies self; //@ ensures liberally self' = push(e, self^); //@ } T pop() throw(); //@ behavior { //@ modifies self; //@ ensures self' = pop(self^); //@ } T top() const throw(); //@ behavior { //@ requires ~isEmpty(self^); //@ ensures result = top(self^); //@ } };
An instantiation of SimpleStack
,
such as the following
SimpleStack<int& /*@ using MutableObj(int) @*/>
must pass a trait that satisfies the specification
of the expected trait, when the actual parameters are substituted for
the formals.
In this case, the LSL sort for int&
is Obj[int]
,
and thus the trait expected is contained_objects(Obj[int])
.
The actual trait passed, MutableObj(int)
satisfies the theory of this expected trait.
See section 8.3 Instantiation of Templates for more about instantiations.
Note that the uses-clause for this example is, contrary to our
usual practice to this point, found inside the class definition.
This is because the trait used depends on the particular type parameter.
Trying to put the uses clause outside the template definition
would result in an error, because then the type parameter T
would not be visible as a type.
However, because the trait used depends on the type parameter,
clients that instantiate Stack
will themselves have to
use an appropriate trait.
(See section 8.3 Instantiation of Templates for an example.)
The trait for SimpleStack
is given below.
It simply uses the trait Stack
from Guttag and Horning's
LSL Handbook (page 170 of [Guttag-Horning93]),
and specifies the trait function contained_objects
.
It includes the trait PureValue(C)
to specify that
eval
on stack values should be the identity function
(see section 6.2.1 State Functions),
and that the potential subobjects within a stack should not
affect the implicit preconditions for member functions
(see section 6.2.2 Allocated and Assigned).
See section 7.5 Contained Objects for the trait PureValue
.
% @(#)$Id: SimpleStackTrait.lsl,v 1.2 1997/07/31 04:02:26 leavens Exp $ SimpleStackTrait(E, C): trait includes Stack, container_objs(top for head, pop for tail), PureValue(C)
The trait container_objs
is often useful in specifying
templates that are containers.
(In this context, a container is an object whose abstract values
satisfy the LSL Handbook trait Container
,
see page 177 of [Guttag-Horning93].)
It defines the contained objects of a stack to be the contained objects
of the elements of the stack.
For example, if the actual template parameter used for T
is an object type, such as int&
,
then all of those elements of the stack will be contained objects.
Similarly, if the actual template parameter used for T
is an pointer type, such as int *
,
then the objects pointed at by the elements of the stack
will be the contained objects.
% @(#)$Id: container_objs.lsl,v 1.8 1995/08/24 17:31:16 leavens Exp $ % This trait is useful for defining contained_objects for generic containers, % such as Set<T>, List<T>, etc. container_objs(E,C): trait includes contained_objects(C) % introduces the signature assumes Container(E,C), contained_objects(E) asserts \forall e: E, c: C, st: State, o: Object contained_objects(c, st) == if isEmpty(c) then {} else contained_objects(head(c), st) \U contained_objects(tail(c), st) implies with_member_objs(E,C) converts contained_objects: C, State -> Set[TypeTaggedObject]
If one has a sort of abstract values with a membership test,
but nothing like the trait functions head
and tail
,
then one can use the following weaker trait instead of container_objs
.
% @(#)$Id: with_member_objs.lsl,v 1.4 1995/08/23 22:58:34 leavens Exp $ % This trait is useful for defining contained_objects for generic containers, % such as Set<T>, List<T>, etc, for which the trait function \in is defined. % See container_objs for a stronger trait that assumes more. with_member_objs(E,C): trait includes contained_objects(C) assumes HasMembership(E,C), contained_objects(E) asserts \forall e: E, c: C, st: State (e \in c) => (contained_objects(e,st) \subseteq contained_objects(c, st))
The trait HasMemberhip
assumed by the above trait is
given below.
% @(#)$Id: HasMembership.lsl,v 1.1 1994/12/08 21:39:46 leavens Exp $ HasMembership(E,C): trait introduces __ \in __ : E, C -> Bool
Go to the first, previous, next, last section, table of contents.