|
|
SerializationDataflow Iterators |
source => 8 bit bytes => 6 bit integers => encode to base64 characters => insert line breaks => destination
We would prefer the solution that is:
typedef
insert_linebreaks< // insert line breaks every 76 characters
base64_from_binary< // convert binary values to base64 characters
transform_width< // retrieve 6 bit integers from a sequence of 8 bit bytes
const char *,
6,
8
>
>
,76
>
base64_text; // compose all the above operations in to a new iterator
std::copy(
base64_text(address),
base64_text(address + count),
ostream_iterator<CharType>(os)
);
Indeed, this seems to be exactly the kind of problem that iterator adaptors are
intended to address. The Iterator Adaptor library already includes
modules which can be configured to implement some of the operations above. For example,
included is
transform_iterator, which can be used to implement 6 bit integer => base64 code.
iterator_adaptor which
fulfills a small set of additional requirements.
Templated constructor have the form:
template<class T>
dataflow_iterator(T start) :
iterator_adaptor(Base(start))
{}
When these constructors are applied to our example of above, the following code is generated:
std::copy(
insert_linebreaks(
base64_from_binary(
transform_width(
address
),
)
),
insert_linebreaks(
base64_from_binary(
transform_width(
address + count
)
)
)
ostream_iterator<char>(os)
);
The recursive application of this template is what automatically generates the
constructor base64_text(const char *) in our example above. The original
Iterator Adaptors include a make_xxx_iterator to fulfill this function.
However, I believe these are unwieldy to use compared to the above solution using
Templated constructors.
remove_whitespace iterator is to increment past the initial
whitespaces when the iterator is constructed. This will fail if the iterator passed to the
constructor "points" to the end of a string. The
filter_iterator is implemented
in this way so it can't be used in our context. So, for implementation of this iterator,
space removal is deferred until the iterator actually is dereferenced.
iterator_adaptor just
invokes the equality operator on the base iterators. Generally this is satisfactory.
However, this implies that other operations (E. G. dereference) do not prematurely
increment the base iterator. Avoiding this can be surprisingly tricky in some cases.
(E.G. transform_width)
An iterator whose output elements do not line up with its input ones cannot
always avoid it. transform_width
learns that the input is over only by reading past the end of it, which is
undefined behaviour, and it does so whenever the length of the input is not a
common multiple of the two widths. Decoding base64, any length of the form
4n + 1 ends in six bits which do not make up a byte, and those six bits send
the iterator looking for the two it is missing.
Passing the end of the input as a second constructor argument avoids this.
The iterator then stops there, discarding whatever is left over rather than
reading on for it. Both
transform_width and
remove_whitespace accept it. The one
argument form behaves as it always has, and remains the one to use for a
composed sequence whose length divides evenly, which is how the archives
themselves use these iterators.
Iterators which fulfill the above requirements should be composable and the above sample code should implement our binary to base64 conversion.
boost::archive::iterators included here are:
boost::filter_iterator
The standard stream iterators don't quite work for us. On systems which implement wchar_t
as unsigned short integers (E.G. VC 6) they didn't function as I expected. I also made some
adjustments to be consistent with our concept of Dataflow Iterators. Like the rest of our
iterators, they are found in the namespace boost::archive::interators to avoid
conflicts with the standard library versions.
© Copyright Robert Ramey 2002-2004. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)