Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I generate recursive data structures on variadic templates?

I am trying to grasp the technique of recursive data-structure generation using TMP with this question.

Question

Suppose I have a variadic template template<typename... Ts> struct my_sets { };.

In my_sets I would like to generate a new type whose data members depends on Ts.

For instance, I want my_sets to have one std::set<T> data member for every element/type that is in Ts...

using x_t = my_sets<int,char,std::string>;
x_t x;

x.insert<0>(   5   );  // into a std::set<int> member in my_sets<>
x.insert<1>(  'z'  );  // into a std::set<char> member in my_sets<>
x.insert<2>( "foo" );  // into a std::set<std::string> member in my_sets<>

I think one way of accomplishing this may be through the use of subclassing and recursion, but I'm not sure.

fwiw, if it is more straight-forward to implement the mutator through a free function or plain function overloading, that's ok, too:

insert<0>( x,   5   );  // into a std::set<int> member in my_sets<>
insert<1>( x,  'z'  );  // into a std::set<char> member in my_sets<>
insert<2>( x, "foo" );  // into a std::set<std::string> member in my_sets<>
like image 630
kfmfe04 Avatar asked Dec 08 '22 17:12

kfmfe04


2 Answers

What's wrong with std::tuple here?

#include <tuple>
#include <set>

template<class... Ts>
using my_sets = std::tuple<std::set<Ts>...>;

// ...

auto x = my_sets<int, char, std::string>;

std::get<0>(x).insert(5);
std::get<1>(x).insert('z');
std::get<2>(x).insert("foo");

For looks, add a free insert function:

#include <utility>

template<std::size_t I, class SetTuple, class Arg>
auto insert(SetTuple& st, Arg&& arg)
  -> decltype(std::get<I>(st).insert(std::forward<Arg>(arg)))
{
  return std::get<I>(st).insert(std::forward<Arg>(arg));
}
like image 170
Xeo Avatar answered Dec 11 '22 10:12

Xeo


@Xeo has an elegant and simple solution. If you want to have insert<> as a member function, however, you can use the following approach:

#include <set>
#include <tuple>

template<typename... Ts>
struct my_sets : protected std::set<Ts>...
{
    using types = std::tuple<Ts...>;

    template<int I, typename T>
    typename std::pair<
        typename std::set<typename std::tuple_element<I, types>::type>::iterator,
        bool> insert(T&& t)
    {
        return std::set<typename std::tuple_element<I, types>::type>::insert(
               std::forward<T>(t)
               );
    }

    // ...

    // Function for retrieving each set...
    template<int I>
    typename std::set<typename std::tuple_element<I, types>::type>& get()
    {
        return *this;
    }
};

And this is how you would use it

#include <string>

int main()
{
    my_sets<int, double, std::string> s;
    s.insert<0>(42);
    s.insert<1>(3.14);
    s.insert<2>("Hello World!");

    s.get<0>().insert(42);
}

Notice, that the above solution doesn't allow for multiple occurrences of the same type in the type list (which may or may not be desired), although it can be quite easily extended to allow them:

#include <set>
#include <tuple>

namespace detail
{
    template<int... Is>
    struct indices
    {
        typedef indices<Is..., sizeof...(Is)> next;
    };

    template<int I>
    struct index_range
    {
        using type = typename index_range<I - 1>::type::next;
    };

    template<>
    struct index_range<0>
    {
        using type = indices<>;
    };

    template<int I, typename T>
    struct dummy : T { };

    template<typename, typename... Ts>
    struct my_sets { };

    template<int... Is, typename... Ts>
    struct my_sets<indices<Is...>, Ts...> : protected dummy<Is, std::set<Ts>>...
    {
        using types = std::tuple<Ts...>;

        template<int I, typename T>
        typename std::pair<
            typename std::set<typename std::tuple_element<I, types>::type>::iterator,
            bool
            > insert(T&& t)
        {
            return dummy<I, std::set<typename std::tuple_element<I, types>::type>>::
                insert(std::forward<T>(t));
        }

        template<int I>
        dummy<I, std::set<typename std::tuple_element<I, types>::type>>& get()
        {
            return *this;
        }
    };
}

template<typename... Ts>
using my_sets = detail::my_sets<
    typename detail::index_range<sizeof...(Ts)>::type,
    Ts...
    >;

And this is how you would use it:

#include <string>

int main()
{
    my_sets<int, double, int, std::string> s;

    s.insert<0>(42);
    s.insert<1>(3.14);
    s.insert<2>(1729);
    s.insert<3>("Hello World!");

    s.get<0>().insert(42);
}
like image 40
Andy Prowl Avatar answered Dec 11 '22 08:12

Andy Prowl