Thursday, April 1, 2010

Converting an MPL sequence to a variadic class template

Just as a function can be overloaded on the number of arguments, in C++, it would be nice to do something similar with a class template, i.e. specialize on the number of template arguments. Would be? That's what variadic class templates (VCT) do. However, one may need instead a class template with exactly one template parameter that is supposed to be instantiated with an MPL sequence (containing the arguments). Let's denote it SCT.

VCT and SCT can be bridged using preprocessing macros, but this post looks at how to avoid this as much as as possible. To make an SCT from VCT, see this blog, which got me started on this. For the other way around, a solution is proposed below, but it only works for "true" variadic templates i.e. those that are supported in C++x0:
template<typename ...Args>
struct
my_class{};
as opposed to the old way, for example, with arity equals two:
struct use_default{};
template
<typename T0 = use_default,
typename T1 = use_default>
struct
my_class{};
Realizing that it's easy to get mixed up between class template and template class, template parameter and template arguments etc., to absolve myself from any mistake I defer to this very reliable source on all matters pertaining to terminology about templates.

Let's invent some specializations:
template<> struct my_class<> {};
template
<typename T> struct my_class<T>{};
template
<typename T,typename U> struct my_class<T,U>{};
What are the requirements of our algorithm? As an input we have an MPL sequence, equivalently a pair of MPL iterators (the first and the last). The returned value will have to be my_class<> instantiated with all the elements from the sequence.

How do we implement it? It will involve a recursion incrementing the first iterator until it reaches the last. At each iteration, the first of the remaining elements from the sequence is appended to the arguments already collected. Wait! You can only prepend Args... (by the way, notice that the ellipsis is on the rhs of Args because here it's an argument, not a parameter), therefore we will need reversed iterators. This translates into:
namespace impl{

template
<typename F,typename L>
struct
exit : boost::mpl::equal_to<
typename
boost::mpl::distance<F,L>::type,
boost::mpl::int_<0>
>{};


template
<typename F,typename L, bool exit,
typename ...Args>
struct
to_variadic{
typedef typename
boost::mpl::deref<F>::type front_;
typedef typename
boost::mpl::next<F>::type next_;
typedef typename
impl::exit<next_,L>::type exit_;
typedef typename
to_variadic<next_,L,
exit_::value,front_,Args...>::type type;
};


template
<typename F,typename L,typename ...Args>
struct
to_variadic<F,L,true,Args...>{
typedef
my_class<Args...> type;
};


template
<typename Seq>
struct
seq_traits{
typedef typename
boost::mpl::begin<Seq>::type first_;
typedef typename
boost::mpl::end<Seq>::type last_;
typedef typename
impl::exit<first_,last_>::type exit_;
};

}
//impl

template
<typename Seq>
struct
to_variadic{
typedef typename
boost::mpl::reverse<
Seq>::type reversed_;
typedef typename
impl::to_variadic<
typename
impl::seq_traits<reversed_>::first_,
typename
impl::seq_traits<reversed_>::last_,
impl::seq_traits<Seq>::exit_::value
>::
type type;
};
Let's test it:
int main (int argc, char * const argv[]) {
typedef
boost::mpl::int_<0> _0;
typedef
boost::mpl::int_<1> _1;

{

typedef
to_variadic<
boost::mpl::vector<> >::type found_;
typedef
my_class<> wanted_;
BOOST_MPL_ASSERT((boost::is_same<found_,wanted_>));
}
{

typedef
to_variadic<
boost::mpl::vector<_0> >::type found_;
typedef
my_class<_0> wanted_;
BOOST_MPL_ASSERT((boost::is_same<found_,wanted_>));
}
{

typedef
to_variadic<
boost::mpl::vector<_1,_0> >::type found_;
typedef
my_class<_1,_0> wanted_;
BOOST_MPL_ASSERT((boost::is_same<found_,wanted_>));
}

return
0;
}
Only a subset of the possible combinations are shown. That's not a trivial comment : my first test was partial and I had missed the reversed iterators part. In fact, I'm not claiming any more than the test shows as this stuff is still fresh.

I used Ubuntu 9.10/GCC 4.4 with the additional gcc command -std=c++0x. For completeness, here are the headers that have to be included:
#include <boost/type_traits/is_same.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/equal_to.hpp>
#include <boost/mpl/distance.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/deref.hpp>
#include <boost/mpl/begin.hpp>
#include <boost/mpl/end.hpp>
#include <boost/mpl/reverse.hpp>
Thanks for reading till the bitter end and see you, hopefully soon.