Search code examples
c++templatesc++11variadic-templatespack

How to return a template pack nested in other pack?


The following code works:

#include <iostream>
#include <list>

struct Base {};
struct A : Base {};  struct B : Base {};  struct C : Base {};
struct D : Base {};  struct E : Base {};  struct F : Base {};

template <int KEY, typename... RANGE> struct Map {};  // one-to-many map (mapping KEY to RANGE...)

template <typename...> struct Data {};

using Database = Data<  Map<0, A,B,C>, Map<1, D,E,F>  >;

template <int N, typename FIRST, typename... REST>  // N has meaning in my program, but not shown here.
void insertInMenu (std::list<Base*>& menu) {
    menu.push_back(new FIRST);
    insertInMenu<N, REST...> (menu);
}

template <int N>
void insertInMenu (std::list<Base*>&) {}  // End of recursion.

template <int N>
std::list<Base*> menu() {
    std::list<Base*> m;
    insertInMenu<0, A,B,C>(m);  // A,B,C should be obtained using N and Database.
    return m;
}

int main() {
    std::list<Base*> m = menu<0>();
    std::cout << "m.size() = " << m.size() << std::endl;  // 3
}

But as indicated in my comment above, I want to use Database and the value N to obtain the range A,B,C (or D,E,F) or whatever. But I don't know how to do that? Can anyone help? The line

insertInMenu<0, A,B,C>(m);

needs to be replaced with something like

obtainRange<Database, N>()

since those compile-time known values should be enough information to obtain the range I want.

obtainRange<Database, 0>()

should return A,B,C and

obtainRange<Database, 1>()

should return D,E,F in this case.


Solution

  • template <typename D, int N>
    struct obtainRange;
    
    template <int N, typename... Ts, typename... Maps>
    struct obtainRange<Data<Map<N, Ts...>, Maps...>, N>
    {
        using type = std::tuple<Ts...>;
    };
    
    template <int N, int M, typename... Ts, typename... Maps>
    struct obtainRange<Data<Map<M, Ts...>, Maps...>, N>
        : obtainRange<Data<Maps...>, N> {};
    
    template <int N, typename Tuple, std::size_t... Is>
    std::list<Base*> menu(std::index_sequence<Is...>)
    {
        std::list<Base*> m;
        insertInMenu<0, typename std::tuple_element<Is, Tuple>::type...>(m);
        return m;
    }
    
    template <int N>
    std::list<Base*> menu()
    {    
        using Tuple = typename obtainRange<Database, N>::type;
        return menu<N, Tuple>(std::make_index_sequence<std::tuple_size<Tuple>::value>{});
    }
    

    DEMO


    If you can't use std::index_sequence, then below is an alternative compatible implementation:

    template <std::size_t... Is>
    struct index_sequence {};
    
    template <std::size_t N, std::size_t... Is>
    struct make_index_sequence_h : make_index_sequence_h<N - 1, N - 1, Is...> {};
    
    template <std::size_t... Is>
    struct make_index_sequence_h<0, Is...>
    {
        using type = index_sequence<Is...>;
    };
    
    template <std::size_t N>
    using make_index_sequence = typename make_index_sequence_h<N>::type;
    

    You can go further, and make it working with arbitrary templates similar to Data and Map, e.g. a std::tuple (instead of Data) of Maps, using template template-parameters:

    template <typename D, int N>
    struct obtainRange;
    
    template <template <typename...> class DB
            , template <int, typename...> class MP
            , typename... Ts
            , typename... Maps
            , int N>
    struct obtainRange<DB<MP<N, Ts...>, Maps...>, N>
    {
        using type = std::tuple<Ts...>;
    };
    
    template <template <typename...> class DB
            , template <int, typename...> class MP
            , typename... Ts
            , typename... Maps
            , int M
            , int N>
    struct obtainRange<DB<MP<M, Ts...>, Maps...>, N> : obtainRange<DB<Maps...>, N> {};
    

    DEMO 2