Click here to Skip to main content
15,881,424 members
Articles / Programming Languages / C++

IoBind, a serializer code factory.

Rate me:
Please Sign up or sign in to vote.
4.89/5 (10 votes)
29 Jun 20037 min read 78.8K   765   29  
IoBind proposes a new approach to object serialization.
<h2>Introduction</h2>
<p>IoBind is a highly flexible library for serializing objects. It uses 
	meta-programming and policies to create reader and writer for complex objects. 
	In oder words, IoBind is a code factory that produces customized readers and 
	writers: you give the action to take and the objects to process and it will 
	generate the corresponding code.
</p>
<h2>Quick examples</h2>
Let's start with some quick examples to see what IoBind is about.
<h3>Converting a string to a ... string</h3>
Let see a first snippet:<br />
<pre lang="c++">
string s("&lt;xml/&gt;");
// we convert s to a string
s = <b>encode</b>( s, <b>to_string()</b> );
cerr&lt;&lt;s&lt;&lt;endl;
</pre>
<pre>
-- output
&lt;xml/&gt;
</pre>
<p>This seems to be useless since I'm converting a string to string. Anyway, let's 
	see what's in the second line:
</p>
<ul>
	<li>
		<code>encode</code> is a template function that applies a conversion policy 
		(see below) to <code>s</code>,</li>
	<li>
		<code>to_string</code> is a <b>conversion policy</b> that takes a value and 
		converts it to a string ( using <code>boost::format</code> library ).</li>
</ul>
<h3>Escaping string to XML</h3>
<p>Now, suppose that you need to store this string into an xml document. You need 
	replace escape characters (<code>&lt;</code>, <code>&gt</code>, etc..) by <code>&amp;lt;</code>,
	<code>&amp;gt;</code>, etc:</p>
<pre lang="c++">
s=encode(s, <b>escape_to_xml()</b> );
cerr&lt;&lt;s&lt;&lt;endl;
</pre>
<pre>
-- output
&lt;xml/&gt;
</pre>
<p>What has happened here: the string has been converted to an escaped string using 
	the <code>escape_to_xml</code> conversion policy.
</p>
<p>Hence using predefined policies you can easily transform a string:
</p>
<ul>
	<li>
		base64,</li>
	<li>
		encryption,</li>
	<li>
		zipping,</li>
	<li>
		url,</li>
	<li>
		etc...</li>
</ul>
<h3>Combining policies</h3>
Did I talk about about code factory ? This is where meta-programming takes 
place.<br />
Suppose that you have a vector of string, you want to convert it to string and 
to escape this string to xml:
<pre lang="c++">
vector&lt;string&gt; v_string;
// filling v_int
v_string.push_back("a string");
v_string.push_back("&lt;string/&gt;");

// transforming to escaped string:
s = encode( 
       v_string.begin(), 
       v_string.end(), 
       escape_to_xml() <b>+ sequence_to_string_p</b> 
       );
cerr&lt;&lt;s&lt;&lt;endl;
</pre>
<pre>
-- output
a string,&lt;string/&gt;
</pre>
<p>In this call, sequence_to_string_p is a policy that converts an iterator range 
	to a string. The operator + combines the two policies (similar to function 
	composition) and <b>creates a new conversion policy</b>:
</p>
<center><B>a+b</B> applied to a value <b>v</b> is equivalent to <b>a( b( v ) )</b></center>
<p>Note that you can also specify a policy to be applied to each value of the 
	container. This, combined with a pair policy, can be used to serializes 
	associatives containers.</p>
<h3>Reading back data</h3>
<p>All "to" policies have "from" counter part to read data. For example, we can 
	easily read a <code>vector&lt;int&gt;</code>:
	<pre lang="c++">
vector&lt;int&gt; v_int;
string str("0,1,2,3,4");

// reading vector of ints
v_int = encode(str,
               <b>sequence_from_string_p
                   % v_int
                   &lt;&lt; from_string<int>() </b>
        );
</pre>
	where
	<ul>
		<li>
			<code>sequence_from_string_p</code> is a generic conversion policy that read a 
			sequence of strings. This conversion policy is a template class that depends on 
			two template paramters:
			<ul>
				<li>
					<code>Container</code>, the type of container to fill,</li>
				<li>
					<code>Policy</code>, the conversion policy to apply to each element,</li>
			</ul>
			,
		</li>
		<li>
			the operator <code>%</code> build a new <code>sequence_from_string</code> conversion 
			policy that adds values to a container of same type as <code>v_int</code>. 
			Concretely, this operator replaces the <code>Container</code> template paramter 
			with the type of <code>v_int</code>,</li>
		<li>
			the operator <code>&lt;&lt;</code> is similar to <code>%</code> but it works on 
			the policy.</li>
	</ul>
<p>Ok the above is rather confusion little confusion, let's deeper see how policies 
	are build step by steps:
	<pre lang="c++">
sequence_from_string_p
                   % v_int
                   &lt;&lt; from_string<int>()
</pre>
	<ol>
		<li>
			<code>sequence_from_string_p</code> = <b>A</b>: this is a pre-build sequence 
			conversion policy: it reads a sequence of strings separated by commas,
			<ul>
				<li>
					Container = vector&lt;string&gt;</li>
				<li>
					Policy = from_string&lt;string&gt;()</li>
			</ul>
		</li>
		<li>
			<code><b>A</b> % v_int</code> = <b>B</b>: the container has been replaced by <code>v_int</code>
			type:
			<ul>
				<li>
					Container = <code>vector&lt;int&gt;</code></li>
				<li>
					Policy = <code>from_string&lt;string&gt;()</code></li>
			</ul>
		</li>
		<li>
			<code><b>B</b> &lt;&lt; from_string<int>() </int></code>: the conversion poliy 
			applied to the elements reads a <code>int</code> from a string:
			<ul>
				<li>
					Container = <code>vector&lt;int&gt;</code></li>
				<li>
					Policy = <code>from_string&lt;int&gt;()</code></li>
			</ul>
		</li>
	</ol>
<p>So with this little statement, we have build a new conversion policy which is a 
	complex mixture of A and B.</p>
<h2>Compiling and installation</h2>
You need Boost (see [1]) and a good compiler (don't think VC6 will manage to 
compile). IoBind heavily uses boost: it uses <em>type_traits, mpl, call_traits, 
	spirit, (regex optional) and format</em>. The headers can be included by
<pre lang="c++">
#include &lt;iobind/iobind.hpp&gt;
</pre>
Note also that all the IoBind classes are in the <b>iobind</b> namespace.
<h2>The <code>encode</code> method</h2>
<code>encode</code> is a template method that applies a conversion policy to an 
object. It's return type depends on the policy return type. It comes with two 
overloads:
<pre lang="c++">
template&lt;
	typename Value,
	typename Policy
&gt;
typename Policy::return_type encode(Value value_, Policy const& policy_);

// range version
template&lt;
	typename Iterator,
	typename Policy
&gt;
typename Policy::return_type encode(Iterator begin_, Iterator end_, Policy const& policy_);
</pre>
This is the front end of IoBind to the user. Example of use of this method are 
given in the secion above.
<h2>Conversion policies</h2>
<left><em>The individual steering behaviors [...] are components of a larger structure, 
		like notes of a melody or words of a story.</em> Craig Reynolds, Steering Behaviors for Autonomous Characters, GDC99.</left>
Conversion policies are the constitutive piece of IoBind. You can combine them 
to create complex serializers.
<h3>Simple string</h3>
These are basic string conversion policies not very useful used alone, but they 
become quite handy when combining with others.
<ul>
	<li>
		<pre lang="c++">struct <b>to_string()</b>;</pre>
		converts the value using <em>boost::format</em>. If the vale does not support 
		this, the compiler will fail.</li>
	<li>
		<pre lang="c++">
template&lt;typename Value&gt;
struct <b>from_string</b>;</pre>
		transform a string to Value using <code>ostringstream</code>,
	</li>
</ul>
Example:
<pre lang="c++">
int i;
string str=encode(i, to_string() );
i=encode(str, from_string<int>() );
</pre>
<h3>Base64</h3>
<ul>
	<li>
		<pre lang="c++">struct <b>to_base64</b>;</pre>
		converts a stream to base64 notation,
	</li>
	<li>
		<pre lang="c++">struct <b>from_base64</b>;</pre>
		converts back a stream from base64 notation,
	</li>
</ul>
Example:
<pre lang="c++">
string str="test";
str=encode( str, to_base64());
str=encode( str, from_base64());
</pre>
These policies are base on the base64 iostream converter from Konstant 
Pilipchuk:
<pre>
//  base64.hpp 
//  Autor Konstantin Pilipchuk
//  mailto:lostd@ukr.net
</pre>
<h3>XML escaping</h3>
This policy takes care of transforming a string to XML conformant string. It 
replaces reserved characters &lt;,&gt;,... by &amplt;, &ampgt;, etc...
<ul>
	<li>
		<pre lang="c++">struct <b>escape_to_xml</b>;</pre>
		escapes an string to XML (&lt; to &amp;lt;),
	</li>
	<li>
		<pre lang="c++">struct <b>unescape_from_xml</b>;</pre>
		unescapes an string from XML (&amp;lt; to &lt;),
	</li>
</ul>
Their usage is straight forward and similar to <code>to_base64</code>, <code>from_base64</code>.
<h3>Sequence container</h3>
This policy hangles sequence containers such as <code>vector</code>, <code>list</code>, etc 
(as you will see later, it can also be used for associative containers).

<ul>
<li>
<pre lang="c++">template&lt;
	typename Policy
&gt;
struct sequence_to_string;</pre> converts a sequence to a string. This policy has the following constructor:
<pre lang="c++">
sequence_to_string(
	policy_const_reference policy_,
	string_param_type begin_ = "",
	string_param_type delimiter_ = ",",
	string_param_type end_ = ""
)
</pre>
	where <code>policy_</code> is the policy applied sequence elements, 
	and the other parameters are used to separated the data. In fact, the core of 
	the reader is:
	<pre lang="c++">output
  &lt;&lt;m_begin
  ..
  &lt;&lt;m_policy.encode(value)&lt;&lt;m_delimiter, // this is done multiple times
  ..
  &lt;&lt;m_end;
</pre>
</li>
<li>
<pre lang="c++">template&lt;
	typename Container,
	typename Policy
&gt;
struct sequence_from_string;</pre> reads a sequence from a string and fills a container. 
This policy has the following constructor:
<pre lang="c++">
sequence_from_string(
	policy_const_reference policy_,
	string_param_type begin_ = "",
	string_param_type delimiter_ = ",",
	string_param_type end_ = ""
)
</pre>
	where the parameters have similar behavior as above. The policy is used to transform the string before adding  it
	to the container.
</li>
</ul>

These policies support new operators that take care of policy, container change:
<ul>
<li><code>&lt;&lt;</code>, changes the policy,</li>
<li><code>%</code>, changes the container type (only for <code>pair_from_string</code>).</li>
</ul>

Example converting elements of a vector&lt;float&gt; to base64 and back:
<pre lang="c++">
vector&lt;float&gt; v_f;
for (i=1;i<5;++i)
	v_f.push_back( 1/static_cast<float>(i) );

str=encode(
		v_f.begin(), 
		v_f.end(), 
		sequence_to_string_p &lt;&lt; to_base64()	
	);
cerr&lt;&lt;"\tv (to_string, base64): "&lt;&lt;str&lt;&lt;endl;
cerr&lt;&lt;"\tv is cleared..."&lt;&lt;endl;
v_f.clear();
v_f=encode(
	str,
	sequence_from_string_b( v_f,  from_string&lt;float&gt;() + from_base64() )
	);
cerr&lt;&lt;"\tv (from_string from base64): "&lt;&lt;encode(
		v_f.begin(), 
		v_f.end(), 
		sequence_to_string_p
		)
	&lt;&lt;endl;
</pre>
<pre>
-- output
        v (to_string, base64): MQA=,MC41AA==,MC4zMzMzMzMA,MC4yNQA=
        v is cleared...
        v (from_string from base64): 1,0.5,0.333333,0.25
</pre>
<h3>Pair</h3>
These policies handle the famous <code>std::pair</code> structure.
<ul>
	<li>
		<pre>
template&lt;
    typename FirstPolicy,
    typename SecondPolicy
&gt;
class <b>pair_to_string</b>;
</pre>
	converts a <code>pair</code> to a string. This class has the following 
	constructor:
	<pre lang="c++">pair_to_string(
	first_policy_const_reference first_policy_,
	second_policy_const_reference second_policy_,
	string_param_type begin_ = "(",
	string_param_type delimiter_ = ":",
	string_param_type end_ = ")"
)
</pre>
	where <code>first/second_policy_</code> are the policies applied respectively 
	to the <code>first</code> and <code>second</code> members of <code>pair</code>, 
	and the other parameters are used to separated the data. In fact, the core of 
	the writer is:
	<pre lang="c++">output
  &lt;&lt;m_begin
  &lt;&lt;m_first_policy.encode(value.first)
  &lt;&lt;m_delimiter,
  &lt;&lt;m_second_policy.encode(value.second),
  &lt;&lt;m_end;
</pre>
	</li>
	<li>
		<pre>
template&lt;
	typename Pair,
    typename FirstPolicy,
    typename SecondPolicy
&gt;
class <b>pair_from_string</b>;
</pre>
	reads a <code>pair</code> from a string. This class has the following 
	constructor:
	<pre lang="c++">pair_from_string(
	first_policy_const_reference first_policy_,
	second_policy_const_reference second_policy_,
	string_param_type begin_ = "(",
	string_param_type delimiter_ = ":",
	string_param_type end_ = ")"
)
</pre>
	where <code>first/second_policy_</code> are the policies applied respectively 
	to the <code>first</code> and <code>second</code> members of <code>pair</code>, 
	and the other parameters are used to separated the data. In fact, the core of 
	the writer is:
	<pre lang="c++">pair.first=m_frist_policy.encode(first_string);
pair.second=m_second_policy.encode(second_string);	</pre>
	</li>	
</ul>

These policies support new operators that take care of policy, pair type change:
<ul>
<li><code>&lt;&lt;</code>, changes the first policy,</li>
<li><code>&gt;&gt;</code>, changes the second policy,</li>
<li><code>%</code>, changes the pair type (only for <code>pair_from_string</code>).</li>
</ul>

<p>Example:</p>
<pre lang"c++">
	pair<float,string> p_fs(1,"second");
	str=encode( p_fs, pair_to_string_p);
	cerr&lt;&lt;"\tpair (1,second): "&lt;&lt;str&lt;&lt;endl;
	cerr&lt;&lt;"\treseting pair"&lt;&lt;endl;
	p_fs.first=0;
	p_fs.second="";
	p_fs=encode(
		str, 
		pair_from_string_p
			% p_fs 
			&lt;&lt; from_string<float>()
			&gt;&gt; from_string<std::string>()
		);
	cerr<<"\tpair (from string):"&lt;&lt;encode( p_fs, pair_to_string_p)&lt;&lt;endl;
</pre>
<pre>
-- output
        pair (1,second): (1:second)
        reseting pair
        pair (from string):(1:second)
</pre>
<h3>Associative containers</h3>

Associative containers such as <code>map</code>,<code>set</code>, etc... are just a combination of a sequence container and a pair 
(speaking about serialization). Hence, using <code>sequence_to/from_string</code> and pair_to/from_string, you can easily build
serializers for them, witout redifining new classes (the compiler will build them for you):
<pre lang="c++">
map&lt;float,string&gt; m_fs;
const char* numbers[] = {"one", "two", "three", "four", "five"};

for (i=1;i<5;++i)
	m_fs[static_cast&lt;float&gt;(i)]=numbers[i];

// dumping to string
str=encode(
		m_fs.begin(), 
		m_fs.end(), 
		sequence_to_string_p &lt;&lt; pair_to_string_p
		);
cerr&lt;&lt;"\tm (to_string): "&lt;&lt;str&lt;&lt;endl;
cerr&lt;&lt;"\tm is cleared..."&lt;&lt;endl;

// reading back the data	
m_fs.clear();
m_fs=encode(
	str,
	sequence_from_string_p % m_fs
	&lt;&lt; (
			pair_from_string_p
			% pair&lt;float,std::string&gt;() 
			&lt;&lt; from_string&lt;float&gt;()
			&gt;&gt; from_string&lt;std::string&gt;() 
		)				
	);
cerr&lt;&lt;"\tm (from_string): "&lt;&lt;encode(
		m_fs.begin(), 
		m_fs.end(), 
		sequence_to_string_p &lt;&lt; pair_to_string_p
		)
	&lt;&lt;endl;
</pre>

<pre>
-- output
-- associative container:
    combination of sequence_to/from_string and pair
        m (to_string): (1:two),(2:three),(3:four),(4:five)
        m is cleared...
        m (from_string): (1:two),(2:three),(3:four),(4:five)
</pre>
<h3>Others...</h3>
There is room for a lot of other policies:
<ul>
	<li>
		encryption, decryption,</li>
	<li>
		zipping, unzipping,</li>
	<li>
		url encoding, decoding,</li>
	<li>
		...</li>
</ul>
<h2>History</h2>
	<tr>
		<td>05-04-2003</td>
		<td>Initial release.</td>
	</tr>
<h2>Reference</h2>
<table border="0">
	<tr>
		<td>[1]</td>
		<td><a href="http://www.boost.org">Boost</a></td>
	</tr>
</table>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Engineer
United States United States
Jonathan de Halleux is Civil Engineer in Applied Mathematics. He finished his PhD in 2004 in the rainy country of Belgium. After 2 years in the Common Language Runtime (i.e. .net), he is now working at Microsoft Research on Pex (http://research.microsoft.com/pex).

Comments and Discussions