2

Possible Duplicate:
Pretty-print std::tuple

In a database library (soci), there is a chunk of code below that works on std::tuple<>'s from one to ten parameters.

static class methods from_base() and to_base() are implemented for arguments of 1-tuple to 10-tuple.

The guts essentially stream every n-tuple element to and from the passed-in stream. Everything is hard-coded.

How can this code be translated to use C++11's variadic templates instead (no limit on parameters)? Actually using variadic templates or not is secondary. What we really want to do is replace the hard-coding with the general case of n-tuple argument.

Part of the problem is, there is, technically, only one argument, but that argument is an n-tuple, so I can't quite use what is described here in Wikipedia. What is the best approach?

#include "values.h"
#include "type-conversion-traits.h"
#include <tuple>

namespace soci
{

template <typename T0>
struct type_conversion<std::tuple<T0> >
{
    typedef values base_type;

    static void from_base(base_type const & in, indicator ind,
        std::tuple<T0> & out)
    {
        in
            >> std::get<0>(out);
    }

    static void to_base(std::tuple<T0> & in,
        base_type & out, indicator & ind)
    {
        out
            << std::get<0>(in);
    }
};

template <typename T0, typename T1>
struct type_conversion<std::tuple<T0, T1> >
{
    typedef values base_type;

    static void from_base(base_type const & in, indicator ind,
        std::tuple<T0, T1> & out)
    {
        in
            >> std::get<0>(out)
            >> std::get<1>(out);
    }

    static void to_base(std::tuple<T0, T1> & in,
        base_type & out, indicator & ind)
    {
        out
            << std::get<0>(in)
            << std::get<1>(in);
    }
};

// ... all the way up to 10 template parameters

}

RUNNABLE ANSWER (based on Grizzly's post below)

#include <iostream>
#include <tuple>

using namespace std;

// -----------------------------------------------------------------------------

template<unsigned N, unsigned End>
struct to_base_impl 
{
    template<typename Tuple>
    static void execute(Tuple& in, ostream& out) 
    {
      out << std::get<N>(in) << endl;
      to_base_impl<N+1, End>::execute(in, out);
    }
};

template<unsigned End>
struct to_base_impl<End, End>
{ 
    template<typename Tuple>
    static void execute(Tuple& in, ostream& out) 
    {
      out << "<GAME OVER>" << endl;
    }
};

// -----------------------------------------------------------------------------

template <typename Tuple>
struct type_conversion
{
    static void to_base(Tuple& in, ostream& out )
    {
        to_base_impl<0, std::tuple_size<Tuple>::value>::execute(in, out);
    }
};

template <typename... Args>
struct type_conversion<std::tuple<Args...>>
{
    static void to_base(std::tuple<Args...>& in, ostream& out )
    {
        to_base_impl<0, sizeof...(Args)>::execute(in, out);
    }
};

// -----------------------------------------------------------------------------

main()
{
    typedef tuple<double,int,string> my_tuple_type;
    my_tuple_type t { 2.5, 5, "foo" };

    type_conversion<my_tuple_type>::to_base( t, cerr );
}
Community
  • 1
  • 1
kfmfe04
  • 14,936
  • 14
  • 74
  • 140
  • See also [this](http://stackoverflow.com/a/7858971/726300) and [this](http://stackoverflow.com/q/687490/500104). – Xeo Nov 06 '12 at 18:43

2 Answers2

5

If I understand your question correctly you basically need to call the operator << or >> on in respectively out for every element of your tuple. In this case you can construct something similar to a for loop using partial specialization and recursion (sort of, since it actually calls a different function each time):

template<unsigned N, unsigned End>
struct to_base_impl {
    template<typename Tuple>
    void execute(Tuple& in, base_type& out) {
      out<<std::get<N>(in);
      to_base_impl<N+1, End>::execute(in, out);
    }
};

template<unsigned End>
struct to_base_impl<End, End> { //End of loop
    template<typename Tuple>
    void execute(Tuple& in, base_type& out) {}
};

template <typename Tuple>
struct type_conversion
{
    typedef values base_type;

    static void to_base(Tuple& in, base_type & out, indicator & ind){
        to_base_impl<0, std::tuple_size<Tuple>::value>::execute(in, out);
    }
};

This will iterate from zero to the size of the tuple and call out<<std::get<N>(in); each iteration. from_base would be implemented the same way only with in>>std::get<N>(out);. If you want to make sure your converter is only called with tuples you can use a variadic template:

template <typename... Args>
struct type_conversion<std::tuple<Args...>>
{
    typedef values base_type;

    static void to_base(std::tuple<Args...>& in, base_type & out, indicator & ind){
        to_base_impl<0, sizeof...(Args)>::execute(in, out);
    }
};

Of course you could write this more generically, but that will make the source much more complex and probably not give you much in return.

Grizzly
  • 19,595
  • 4
  • 60
  • 78
3

I like the indices trick:

template<unsigned...> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

template<class T> using Alias = T;

template<class... Ts, unsigned... Is>
void to_base(std::tuple<Ts...> const& t, base_type& out, seq<Is...>){
  Alias<char[]>{ (void(out << std::get<Is>(t)), '0')... };
}

template<class... Ts, unsigned... Is>
void to_base(std::tuple<Ts...> const& t, base_type& out){
  to_base(t, out, gen_seq<sizeof...(Ts)>{});
}

And the same for from_base. (Live example based on jrok's.)

The code "exploits" the variadic unpacking mechanism to call out << get<Is>(t) exactly sizeof...(Is) (which is the same as sizeof...(Ts)) times. gen_seq<N> generates a compile-time integer list from 0 to N-1 (seq<0, 1, 2, ..., N-1>). This list is saved to unsigned... Is, which are then unpacked in get<Is>(t) to get<0>(t), get<1>(t), all the way up to get<N-1>(t).
Alias<char[]>{ ... } (a temporary array) provides a context where pack expansion can be used. I specifically chose array initialization here, because left-to-right evaluation of the passed arguments is guaranteed. The (void(expression), '0') will evaluate expression, discard the value, and pass '0' as the initializer (void(...) is used to force the built-in comma-operator to be selected, and not any possible overloaded one).

Xeo
  • 129,499
  • 52
  • 291
  • 397