| 
 |  | 
Suppose that we want to extend usr records by adding a field of some user-defined type. The type of interest may already be defined in an existing header file; or, it may be a new type that we have yet to define. As we will see, there are only two unavoidable restrictions that G2++ imposes on a user-defined type: (1) the type must have an assignment operator The assignment operator is used by the extractor to assign values to structure members of that type. (2) if the type occurs in an array, it must have a parameterless constructor. This restriction is inherited from C++.
For example, suppose that we decide to add a field named last_login of type Time(C++) to usr records. g2++comp(CP) requires that a user-defined type be explicitly declared with the keyword USER prior to its first use in a record definition. Here, then, is the simplest .g file that will compile without error:
   usr.g
           Time    USER
           usr
                   login   6
                   last_login      Time
                   id
                           usr     LONG
                           grp     SHORT
                   name    20
                   proj
                           4       LONG
Given only the information that Time is a user-defined type, g2++comp(CP) will be forced to make a few assumptions. First, it will assume that there is a file named Time.h that defines a type named Time (this assumption happens to be correct). Accordingly, it generates the following header file:
   usr.h
           #include "Time.h"
           typedef struct USR{
              ...
              Time last_login;
              ...
           }USR;
g2++comp(CP) will also assume that Time.h declares inserters and extractors capable of writing or reading external representations of Time values to or from an ostream(C++) or an istream(C++), respectively. These operators are needed because the USR inserter and extractor delegate insertion and extraction of Time values to them:
        USR u;
        cout << u;  delegates insertion of last_login field to Time::operator<<
        cin >> u;   delegates extraction of last_login field to Time::operator>>
This last assumption is not correct, at least insofar as extraction is concerned, but let us continue anyway. Other assumptions made by g2++comp(CP) in the current example include:
   usr.g
           Time    USER
                   .header Time.h
                   .header Timeio.h
                   .null   Time::MIN
           ...
We will address the specific attributes used in the above example later. First, let's look at the general form of a USER type definition:
USER .header H1 .header H2 ... .null N .isnull I .put P .get G
The significance of each of the attributes is explained below:
 .h, which will be used if no header
attributes are given.
The remaining attributes are allowed to assume the existence
of definitions exported by the transitive closure of header files named by
header attributes.
That is, if H1, H2, ... are files named in
header attributes, then the closure consists of H1+H2+...
plus any header files included by those files, and so-on, transitively.
 .h, which will be used if no header
attributes are given.
The remaining attributes are allowed to assume the existence
of definitions exported by the transitive closure of header files named by
header attributes.
That is, if H1, H2, ... are files named in
header attributes, then the closure consists of H1+H2+...
plus any header files included by those files, and so-on, transitively.
 .
Its value will be used as the null value for  type
.
Its value will be used as the null value for  type  .
Omitting the
null attribute implies a contract that a parameterless constructor
.
Omitting the
null attribute implies a contract that a parameterless constructor
 () exists, and its value will be used as the null value.
() exists, and its value will be used as the null value.
int I(const& t);
This function is expected to return 1 if its argument is null, and 0 otherwise.
If the isnull attribute is omitted,
g2++comp(CP)
will generate code to explicitly
test for equality with the null value defined by the
null attribute; this means that omitting the isnull
attribute implies a contract that there exists,
somewhere in the header file closure
implied by header attributes, the declaration of an equality operator
for type  .
. 
 into an output stream.
Including
a put attribute implies a contract that there exists, somewhere
in the header file closure implied by header attributes,
a declaration
of the  form:
 into an output stream.
Including
a put attribute implies a contract that there exists, somewhere
in the header file closure implied by header attributes,
a declaration
of the  form:
ostream& P(ostream& os,const& t);
P is expected to insert an external representation of t into stream os. To preserve the integrity of the record, the external representation must not contain tabs, newlines, or other nonprintable ASCII characters.
If the
put attribute is omitted,
g2++comp(CP)
will call
 ::operator<< to do the insertion.
This means that
omitting the put attribute implies a contract that there exists,
somewhere in the header file closure implied by header attributes,
the declaration of an inserter for type
::operator<< to do the insertion.
This means that
omitting the put attribute implies a contract that there exists,
somewhere in the header file closure implied by header attributes,
the declaration of an inserter for type  .
. 
 from an input stream.
Including
a get attribute implies a contract that there exists, somewhere
in the header file closure implied by header attributes,
a declaration
of the  form:
 from an input stream.
Including
a get attribute implies a contract that there exists, somewhere
in the header file closure implied by header attributes,
a declaration
of the  form:
istream& G(istream& is,& t);
G is expected to extract an external representation
from stream is, construct an object of type  , and assign
it to t.
The function must extract only the characters constituting
the external representation and leave the stream positioned
so that the first character
extracted by a subsequent extraction will be the first character following the  external
representation of type
, and assign
it to t.
The function must extract only the characters constituting
the external representation and leave the stream positioned
so that the first character
extracted by a subsequent extraction will be the first character following the  external
representation of type  .
If G cannot construct
an object of type
.
If G cannot construct
an object of type  , it should assign a  null value to t and
clear the error bits (see
ios(C++)).
If the
get attribute is omitted,
g2++comp(CP)
will call
, it should assign a  null value to t and
clear the error bits (see
ios(C++)).
If the
get attribute is omitted,
g2++comp(CP)
will call
 ::operator>> to do the extraction.
This means that omitting
the get attribute implies a contract that there exists, somewhere
in the header file closure implied by
header attributes, the declaration
of an extractor for type
::operator>> to do the extraction.
This means that omitting
the get attribute implies a contract that there exists, somewhere
in the header file closure implied by
header attributes, the declaration
of an extractor for type  .
. 
We cannot overemphasize that each piece of information furnished via an attribute (or implied by omitting an attribute) merely creates a contract between the record definer and the software developers responsible for providing the named or implied facilities. g2++comp(CP) cannot check or enforce these contracts. For example, it does not check for the existence of files named in header attributes, nor does it typecheck any expressions in null attributes. It is only later, when the application is compiled and linked, that the named or implied facilities must actually exist. This delayed binding allows applications to be developed in parallel with their infrastructure.