Wednesday, February 17, 2010

Using mpl::for_each to fill a tuple

This article is actually a paste of something I wrote to comp.lang.c++ quite a while ago.


A while back I posted a question either here or to the boost user list about how to iterate through a vector of strings and perform a lexical cast on them into elements of a tuple. I was working with sqlite3 and found myself repeatedly writing code like so:

t.get<0>() = boost::lexical_cast<double>(vect[0]);
t.get<1>() = boost::lexical_cast<int>(vect[1]);
...

For each query I would invent. Certainly there seemed there should be a way to use metaprogramming to approach this problem. I could get here:

t.get<N>() = boost::lexical_cast< boost::tuples::element<N, TUP>::type >(vect[N]);

However, I couldn't figure how to insert that into a metaprogram. I figured it would have something to do with for_each but couldn't figure it out. When I approached whatever list I posted my question to I was sent to some library...I forget which one but it wasn't the answer (blog note: it was the phoenix library). Well, last night a light in my brain turned on. It's actually very simple once it dawns on one how to do it:

template < typename TUPLE >
struct tuple_assign
{
TUPLE & t;
std::vector< std::string > const& data;

tuple_assign(TUPLE & to, std::vector const& from) : t(to), data(from) {}

template < typename T>
void operator() (T) // T must be an mpl::int_
{
boost::tuples::get<T::value>(t) =
boost::lexical_cast< typename boost::tuples::element<T::value, TUPLE>::type >(data[T::value]);
}
};

Your calling code looks like so:

boost::tuple<double, int, std::string> t;
std::vector<std::string> d;
d += "5.2","42","HELLO!";

boost::mpl::for_each< boost::mpl::range<0,3> >(tuple_assign< boost::tuple<double,int,std::string> >(t,d));

The range can also be derived through the template system like so:

boost::mpl::range< 0, boost::tuples::length<boost::tuple<double,int,std::string> > >

Much safety can be placed on this system. I haven't done so here. This problem solved though, there's nothing stopping a generic query interface that could be used something like so:

tie(x, y, z) = query.run();

as well as an iterative interface that provides a similar tuple interface.

blog note: you'd need to override the = operator between tuples and some type returned by run() in order to get the necessary information to perform the above technique.

1 comment:

  1. Sounds similar to http://lists.boost.org/boost-users/2009/03/46356.php that uses boost::fusion::for_each instead.

    ReplyDelete