The client programmer has a freedom to choose the data structures for the object properties. The choice can be based on the performance considerations or just personal preferences and coding experience. The polymake server in its turn must keep the properties in a format accessible for perl subroutines. Finally, the objects are stored in human readable data files which assumes some serialized encoding constrained to printable characters. Putting all this together, there must be mechanism for automatic conversion between different binary and textual representations of object properties.

The GenericIO layer introduced here serves exactly this purpose: it allows to store and retrieve the C++ data structures in an uniform way. For the client programmer the front side of this layer, incorporated into the Poly class, is important. It is represented by two generic class families, defined in namespace pm :

template <typename BackEnd> class GenericInput;

An abstract data source. It can be used in input operators and constructors. The give and lookup methods are currently the only suppliers of GenericInput objects.

template <typename BackEnd> class GenericOutput;

An abstract data sink. It can be used in output operators. The only supplier of these objects is the take method.

Currently there is one back end engine in polymake, converting to and from printable representation as used in the data files. In the next releases, further converters appear for XML and perl internal representation. Since it will most probably stay under the competence of polymake core development team, the details about back end are not described here.

If the standard containers like std::vector or std::list, and basic PTL classes suffice your needs, you can skip this page by the first reading. What follows is important to you if you are going to deploy your own or third party's data structures in the clients.

Data type classification

GenericIO reduces the whole diversity of C++ classes to three main categories: atoms (scalars), containers, and tuples (composites). Let's start with the simplest case.

Containers

A data type must be classified as a container if it complies with the STL specifications. If the data structure of your choice does, the chances are good that all you need is to declare this fact providing an appropriate specialization of the pm::spec_object_traits class template:

namespace pm { template <> struct spec_object_traits< my_class > : spec_object_traits<is_container> { }; template <typename T, ...> struct spec_object_traits< my_class_template<T,...> > : spec_object_traits<is_container> { }; }

The public derivation from spec_object_traits<is_container> provides your description with all necessary definitions. You may want to overwrite the default assumptions in some special cases by including the following definitions in the struct body:

static const bool is_ordered=false;
if the class being described does not allow insertion at the given position (before the given iterator).
Examples are std_ext::hash_set and std_ext::hash_map.
static const bool allow_sparse=true;
if the data are expected to come in one of two fashions: dense and sparse. The sparse representation is a sequence of pairs (index value) , followed by (dimension) . The converter in GenericOutput is allowed to choose the sparse representation automatically, if it considers it to be more efficient.
Examples are SparseMatrix and SparseVector.

The printable representation of a container is a sequence of its elements. The delimiter between the elements will be the line break if the elements are in turn containers, or white space otherwise. In any case the last element is followed by the line break. This way an empty container is represented by an empty line.

There are two alternative printable representations, enabled by following definitions:

static const IO_separator_kind=IO_sep_inherit;
Separate elements by white spaces until the representation of elements itself contains the line breaks. Enclose the whole sequence in curly braces { ... }
Examples are std::list and Set.
static const IO_separator_kind=IO_sep_enforce;
Always separate elements by line breaks.

Naturally, this whole business with spaces and line breaks will become absolutely meaningless for the anticipated XML data format.

High-dimensional containers

GenericIO have ready-to-use conversion operators only for one-dimensional containers. Such classes as Matrix or IncidenceMatrix have to be declared as containers too, but they require explicit conversion operators. If you are considering to deploy another matrix-like class, you must provide both the model declaration with a dimension definition and the conversion operators:

namespace pm { template <typename ElementType> struct spec_object_traits< my_matrix<ElementType> > { typedef is_container model; static const int dimension=2; }; }

The operators are described in more details later.

Composite

A composite is a tuple of data elements of possibly different types, like std::pair or plain C struct.

Since the C++ compilers unfortunately don't provide any means of class introspection, you have to describe the data members of the class by yourself. The following example explains the two most important declaration components: the element list and the visiting function.

namespace std { template <typename T1, typename T2> class pair { T1 first; T2 second; ... }; } namespace pm { template <typename T1, typename T2> struct spec_object_traits< pair<T1,T2> > : spec_object_traits<is_composite> { typedef cons<T1,T2> elements; template <typename Me, typename Visitor> static void visit_elements(Me& me, Visitor& v) { v << me.first << me.second; } }; }

The elements definition must list all element types. If there are more than two elements, the cons pairs should be nested: cons<T1, cons<T2, T3> >

The function visit_elements must give the visitor object v access to the elements in the same order as they are described in elements. The argument v must be a non-const reference. Making the type of argument me a template parameter is not necessary, but comfortable: it allows to handle both the input ( Me==std::pair ) and output ( Me==const std::pair ) operations.

If the initialization of an object of your composite class solely consists of construction steps for each of its elements, you don't need to write anything else but such a specialization. GenericInput makes the job alone.

But if the class constructor does some further work, like consistency checks or creation of private data members, you must define the specialized input operator. For the output, you can still use the operator supplied by GenericOutput.

The printable representation of a composite object is a sequence of elements in the order of the elements definition, separated by white spaces or line breaks, and enclosed in parentheses.

Scalars

Everything not declared as container or composite is a scalar from the GenericIO point of view. This category comprises all built-in C++ data types (int, bool, double, etc.), classes acting as scalars (Integer, Rational, std::string), as well as black-box classes with complex internal organization.

If the data type has a canonical textual representation and can be read and written via standard C++ I/O streams (std::basic_iostream and derivatives), you have nothing to do: GenericIO uses the std::basic_iostream operators as a fallback for all scalar data types. Otherwise you have to define specializations for the input and output operators.

Specialized I/O operators

template <typename BackEnd> Input& operator>> (GenericInput<BackEnd>& is, my_class& x) { ... return is.top(); } template <typename BackEnd> Output& operator<< (GenericOutput<BackEnd>& os, const my_class& x) { ... return os.top(); }

These operators can be defined, at your choice, in the namespace pm or in your class' definition namespace. In the latter case the class names GenericInput and GenericOutput must be naturally prefixed by pm::.

The return operator is obligatory; it allows to chain the input/output operations in the same manner as for std::iostream.

There are several possible implementation styles for these operators. If the class is developed on your own, you can incorporate the operators as friends into the class definition and let them operate on the object guts directly. If your class comes from a third party class library, the input operator may get the necessary components and call the constructor or publicly available set methods. Similarly, the output operator can retrieve the components using the public get methods and hand them over to the GenericOutput object.

In any case, as soon as you have to handle sequences of data items, your input operator will contain fragments like this:

ElementType x; typename BackEnd::template list_cursor<Archetype>::type c=is.top().begin_list((Archetype*)0); while (!c.at_end()) { c >> x; ... // insert x into the object }
Get a homogeneous sequence of items of type ElementType. The Archetype should be some container whose printable representation should be mimicked, e.g. std::list<ElementType> for a sequence enclosed in { ... } or std::vector<ElementType> for a sequence concluded by a line break.
ElementType1 x; ElementType2 y; typename BackEnd::template composite_cursor<Archetype>::type c=is.top().begin_composite((Archetype*)0); c >> x >> y; ... // assign x, y to the object
Get a sequence of items of different types encoded as a tuple. Archetype should be a composite type described by means of spec_object_traits as above, with elements definition containing ElementType1, ElementType2, etc. in the proper order.

The analogous fragments for the output operator will look like:

ElementType x; typename BackEnd::template list_cursor<Archetype>::type c=os.top().begin_list((Archetype*)0); while (...) { ... // retrieve x c << x; } ElementType1 x; ElementType2 y; ... // retrieve x, y typename BackEnd::template composite_cursor<Archetype>::type c=os.top().begin_composite((Archetype*)0); c << x << y;

More elaborated examples of list_cursor and composite_cursor use may be found in the GenericIO definition file GenericIO.h as well as in various complex PTL classes like SparseMatrix.h, Graph.h or FacetList.h.