The C++98 Stream Helpers

Ice for C++ uses a set of C++ templates and specializations of these templates to marshal and unmarshal C++ types. A type that can be marshaled and/or unmarshaled using this framework is known as a Streamable.

On this page:

Marshaling and Unmarshaling C++ Objects

Ice for C++ marshals C++ objects into streams of bytes, and unmarshals these C++ objects from streams of bytes. Ice for C++ has an internal implementation for these streams, and a publicly available implementation, and both implementations have mostly the same API.  

To marshal a C++ object into a stream, you (or the generated code) can simply call write(obj) on that stream. For basic types, such as int or string, there is a corresponding write function on the stream. For other types, write is an inline template function that delegates to a specialization of the StreamHelper template class:

C++
namespace Ice 
{
    class OutputStream : ... 
    {
    public:
    ...
        template<typename T> inline void write(const T& v)
        {
            StreamHelper<T, StreamableTraits<T>::helper>::write(this, v);
        }
    };
}

Likewise, when unmarshaling a C++ object from a stream, you (or the generated code) can simply call read(obj) on that stream, and the reading of most types delegates to a specialization of this StreamHelper template class:

C++
namespace Ice
{
    class InputStream : ... 
    {
    public:
    ...
        template<typename T> inline void read(T& v)
        {
            StreamHelper<T, StreamableTraits<T>::helper>::read(this, v);
       }
    };
}

Ice looks for the StreamHelper specializations in namespace Ice, so they must all be in namespace Ice.

Each StreamHelper specialization typically provides a static write function to write the C++ object into the steam, and a static read function to read the C++ object from the stream. The stream object in these StreamHelper static functions is represented by a template parameter type, which allows a StreamHelper specialization to work with any stream implementation:

C++
// Typical StreamHelper specialization
namespace Ice 
{
    template<>
    struct StreamHelper<SomeType, StreamHelperCategoryXXX> 
    {
        template<class S> static inline void
        write(S* stream, const SomeType& v)
        {
            ... marshal v...
        }
        template<class S> static inline void
        read(S* stream, SomeType& v) 
        {
           ... unmarshal v ...
        }
    };
}

The base StreamHelper template class is not defined, since there is no default way to read a C++ object from a stream or write this object into a stream:

C++
namespace Ice
{
    template<typename T, StreamHelperCategory st> struct StreamHelper;
}

For most types, Ice for C++ provides or generates the corresponding StreamHelper specialization (or partial specialization), for example all Slice sequences mapped to a C++ vector, C++ list or similar type use this specialization:

C++
namespace Ice
{
    template<typename T>
    struct StreamHelper<T, StreamHelperCategorySequence>
    {
        template<class S> static inline void
        write(S* stream, const T& v)
        {
            stream->writeSize(static_cast<Int>(v.size()));
            for(typename T::const_iterator p = v.begin(); p != v.end(); ++p)
            {
                stream->write(*p);
            }
        }
        template<class S> static inline void
        read(S* stream, T& v)
        {
            Int sz = stream->readAndCheckSeqSize(StreamableTraits<typename T::value_type>::minWireSize);
            T(sz).swap(v);
            for(typename T::iterator p = v.begin(); p != v.end(); ++p)
            {
                stream->read(*p);
            }
        }
    };
}

StreamableTraits

The StreamHelper specializations use specializations of the StremableTraits template class to retrieve some characteristics (or traits) of the C++ type it operates on:

C++
namespace Ice
{
    typedef int StreamHelperCategory;
    const StreamHelperCategory StreamHelperCategoryUnknown = 0;
    const StreamHelperCategory StreamHelperCategoryBuiltin = 1;
    const StreamHelperCategory StreamHelperCategoryStruct = 2;
    const StreamHelperCategory StreamHelperCategoryStructClass = 3;
    const StreamHelperCategory StreamHelperCategoryEnum = 4;
    const StreamHelperCategory StreamHelperCategorySequence = 5;
    const StreamHelperCategory StreamHelperCategoryDictionary = 6;
    const StreamHelperCategory StreamHelperCategoryProxy = 7;
    const StreamHelperCategory StreamHelperCategoryClass = 8;
    const StreamHelperCategory StreamHelperCategoryUserException = 9;
 
    template<typename T, typename Enabler = void>
    struct StreamableTraits 
    {
        static const StreamHelperCategory helper = ...;
        static const int minWireSize = 1;
        static const bool fixedLength = false;
    };
}

In particular, StreamHelper specializations are divided in various categories, to allow a single partial specialization of StreamHelper to handle several similar C++ types. For example, Ice for C++ provides a single StreamHelper partial specialization for all enum types, with category StreamHelperCategoryEnum:

C++
template<typename T>
struct StreamHelper<T, StreamHelperCategoryEnum>
{
    template<class S> static inline void
    write(S* stream, const T& v)
    {
        ...sanity check...
        stream->writeEnum(static_cast<Int>(v), StreamableTraits<T>::maxValue);
    }
    template<class S> static inline void
    read(S* stream, T& v)
    {
        Int value = stream->readEnum(StreamableTraits<T>::maxValue);
        ..sanity check...
        v = static_cast<T>(value);
    }
};

StreamHelperCategory is an integer (int). Values between 0 and 20 are reserved for Ice; you can use other values for your own StreamHelper specializations.

A StreamableTraits is always defined in namespace Ice and provides three static const data members:

  • helper
    The category of StreamHelper associated with this C++ type (see above). The base StreamableTraits template class computes helper automatically for maps, lists, vectors and similar types.

  • minWireSize
    The minimum size (in bytes) of the corresponding Slice type when marshaled using the Ice Encoding. The base StreamableTraits template class provides the default value, 1 byte. This value should be one for sequences and dictionaries, since an empty sequence or dictionary is marshaled as a single byte (size = 0). 

  • fixedLength
    true when the corresponding Slice type is marshaled on a fixed number of bytes, and false when it is marshaled on a variable number of bytes. The default, provided by the base StreamableTraits template class, is false.

Ice provides StreamableTraits specializations for all the C++ types it uses when mapping Slice types to C++. For example, Ice provides a StreamTraits<Ice::Long> (for 64-bit signed integers)  and a StreamableTraits<std::string>

C++
namespace Ice
{
    template<>
    struct StreamableTraits<Long> 
    {
        static const StreamHelperCategory helper = StreamHelperCategoryBuiltin;
        static const int minWireSize = 8;
        static const bool fixedLength = true;
    };
 
    template<>
    struct StreamableTraits<std::string> 
    {
        static const StreamHelperCategory helper = StreamHelperCategoryBuiltin;
        static const int minWireSize = 1;
        static const bool fixedLength = false;
    };
}

For each constructed type, such as Slice structs, the Slice to C++ translator generates the corresponding StreamableTraits specialization. For example:

C++
// Generated C++ code
namespace Ice
{
    template<>
    struct StreamableTraits<::Ice::Identity> 
    {
        static const StreamHelperCategory helper = StreamHelperCategoryStruct;
        static const int minWireSize = 2;
        static const bool fixedLength = false;
    };
}

Marshaling and Unmarshaling Optional Values

As described in Data Encoding for Optional Values, the encoding or wire representation of an optional data member or parameter consists of:

  • An optional format (a 3-bits value) that depends on the associated Slice type. For example, the optional format for a sequence<string> is FSize (or 6).
  • The tag associated with this optional data member or parameter (this tag is a positive integer)
  • The actual value of this optional data member or parameter

To marshal an optional value into a stream, you (or the generated code) call write(tag, obj) on that stream, where tag is the optional's tag (an int), and obj is the optional value. write is a template function that uses a specialization of the StreamOptionalHelper template class when this optional value is set:

C++
namespace Ice 
{
    class OutputStream : ... 
    {
    public:
    ...
        template<typename T> inline void write(int tag, const IceUtil::Optional<T>& v) 
        {
            if(v) 
            {
                writeOptional(tag, StreamOptionalHelper<T,
                                                        StreamableTraits<T>::helper,
                                                        StreamableTraits<T>::fixedLength>::optionalFormat);
                StreamOptionalHelper<T, StreamableTraits<T>::helper, StreamableTraits<T>::fixedLength>::write(this, *v);
            }
        }
    };
}

Likewise, when unmarshaling an optional value from a stream, you (or the generated code) call read(tag, obj) on that stream, which delegates to a specialization of the same StreamOptionalHelper template class when the stream holds a value for this optional data member or parameter:

C++
namespace Ice
{
    class InputStream : ... 
    {
    public:
    ...
        template<typename T> inline void read(int tag, IceUtil::Optional<T>& v)
        {
            if(readOptional(tag, StreamOptionalHelper<T,
                                                      StreamableTraits<T>::helper,
                                                      StreamableTraits<T>::fixedLength>::optionalFormat))
            {
                v.__setIsSet();
                StreamOptionalHelper<T, StreamableTraits<T>::helper, StreamableTraits<T>::fixedLength>::read(this, *v);
            }
            else
            {
                v = IceUtil::None;
            }
        }
    };
}

Each StreamOptionalHelper specialization usually provides static read and write functions, just like StreamHelper specializations, and also an optionalFormat static data member that provides the optional format of the corresponding Slice type.

For many types, the StreamOptionalHelper specialization simply delegates to this type's StreamHelper specialization, and computes optionalFormat with the GetOptionalFormat template:

C++
// GetOptionalFormat is declared but never defined
template<StreamHelperCategory st, int minWireSize, bool fixedLength>
struct GetOptionalFormat;
 
// Base StreamOptionalHelper template class
namespace Ice
{
    template<typename T, StreamHelperCategory st, bool fixedLength>
    struct StreamOptionalHelper
    {
        typedef StreamableTraits<T> Traits;
 
        static const OptionalFormat optionalFormat = GetOptionalFormat<st, Traits::minWireSize, fixedLength>::value;
    
        template<class S> static inline void
        write(S* stream, const T& v)
        {
            stream->write(v);
        }
        template<class S> static inline void
        read(S* stream, T& v)
        {
            stream->read(v);
        }
    };
}

For example, the optional format for a long long int (64-bit signed integer, encoded on exactly 8 bytes) is provided by this specialization of GetOptionalFormat:

C++
namespace Ice
{
    template<>
    struct GetOptionalFormat<StreamHelperCategoryBuiltin, 8, true>
    {
        static const OptionalFormat value = OptionalFormatF8;
    };
}

See Also