iov(3) iov(3)

NAME

iov - Adapt type for serialization

SYNOPSIS

#include <cfl/run/iov.hpp>

// binary copyable type - specialize predicate
struct A { int m; };
template <> struct is_binary_copyable <A> : true_type {};

// complex type - specialize iov
struct B
{
    size_t n;
    int * ptr;

    explicit B (size_t n) :
        n (n),
        ptr ((int *) (operator new (sizeof (int) * n))) {}

   ~B () { operator delete (ptr); }
};

DESCRIPTION

The iov type implements serialization. The name refers to the io vector used in POSIX readv and writev.

For binary copyable types it is enough to specialize the is_binary_copyable predicate, as done already for fundamental types, arrays thereof, and empty types. For complex types, the iov type should be specialized.

Serialization is split in two parts, one with static properties of the type, and one with dynamic. The former is sent as a header to prepare the receiver for the latter body.

Serialization is limited to types not requiring intermediate values, i.e. both serialization and deserialization can be done immediately from and into the types. This restriction can be lifted.

The iov type strips cv qualification and references from its template argument and provides as a defaulted second argument. This is to keep the argument type intact, and at the same time avoid specializing over all combinations of cv qualifications and references.

The specification of header chunks is strictly not necessary, but included as a courtesy to the heap, sparing it from repeated reallocation of a small array. The serialized type is U and the deserialized type is type. Here they differ only in cv qualification and reference type, but in the general case they can be completely different. As body_chunks is used by both the send and receive side, this is here left unspecified with the template argument V. The io vector iterator is also left unspecified through the template argument T.

template <U> struct iov <U, B>
{
    typedef B type;

    static void header_chunks (size_t & h)
    {
        // one static entry: B::n
        ++h;
    }

    template <typename V>
    static void body_chunks (V &&, size_t & b)
    {
        // one dynamic entry: the array pointed to by ptr
        ++b;
    }

    template <typename T>
    static void send (U && u, T & h, T & b)
    {
        // point header io vector to u.n
        *h++ = {& static_cast <U &&> (u).n,
                    sizeof (u.n)};

        // point body io vector to u.ptr
        *b++ = {u.ptr, sizeof (int) * u.n};
    }

    template <typename T>
    static void receive_header (type & r, T & h)
    {
        // point to uninitialized storage for B::n
        *h++ = {& r.n, sizeof (r.n)};
    }

    template <typename T>
    static void receive_body (type & r, T & b)
    {
        // have B::n now, allocate storage for array
        r.ptr = (int*) operator new (sizeof (int) * r.n);

        // point to uninitialized storage for array
        *b++ = {r.ptr, sizeof (int) * r.n};
    }
};

With this, a type can be used in calls to other nodes. It covers the generic case and is therefore fairly verbose, but convenience helpers can be introduced.