4

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<>
Xeo
  • 123,374
  • 44
  • 277
  • 381
kfmfe04
  • 14,193
  • 11
  • 67
  • 132

2 Answers2

12

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));
}
Xeo
  • 123,374
  • 44
  • 277
  • 381
  • +1 v.nice - didn't know you could *break off* the ... like that, but when I stared at it for a while, the syntax actually makes perfect sense! – kfmfe04 Mar 15 '13 at 00:21
  • @kfmfe04: Pack expansion just says "here's this pattern, copy-and-replace for everything in the pack". – Xeo Mar 15 '13 at 00:23
  • I've seen the typical `Ts...` expansion - it was the `std::set...` that really surprised me! I'd bet it was non-trivial for the compiler developers to get that piece of code to work parse and run properly. But now that I look at Andy's example below, I'm not quite sure I understand his usage - is he doing a whole bunch of individual subclasses from std::set<>? That's neat you can do that. – kfmfe04 Mar 15 '13 at 00:30
  • @kfmfe04: Yes, that is allowed as well. I am basically making `my_sets` inherit from all the types obtained by the pack expansion – Andy Prowl Mar 15 '13 at 00:44
  • @kfmfe04 C++11 was designed with ease of parsing in mind, I'd guess that this turns out to be surprisingly simple to implement! – Yakk - Adam Nevraumont Mar 15 '13 at 01:35
2

@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);
}
Andy Prowl
  • 114,596
  • 21
  • 355
  • 432
  • I don't think inheriting from `std::set` does *any* good here, especially over just keeping a `std::tuple` of them around as member functions. – Xeo Mar 15 '13 at 00:22
  • If you're so set on inheriting from something, why not from `std::tuple` atleast? :/ That solves the multiple types problem without all the cruft (or, y'know, you could just have it as members). I *really really* don't see why you want it as base classes. – Xeo Mar 15 '13 at 01:43
  • @Xeo: No need to get upset ;) I just took this question (which I found already answered) as a chance for showing some technique/design pattern that might turn out to be interesting or useful to some (indices, inheritance from an expanded type list, multiple argument pack expansion, etc.). I acknowledge your solution is simpler and more idiomatic, and that's what I would have posted if I had found this question before you answered it. – Andy Prowl Mar 15 '13 at 09:20