Search code examples
c++variadic-templatestemplate-meta-programming

Unpacking two parameter packs by threading their elements to pairs of types


Given say N user-specified types, I'd like a function to return a std::tuple of length N where each element is constructed via a function call to some function (exemplified by func below):

#include <tuple>
#include <utility>

template <typename T> 
T func(int x)
{
    return T();
}

template<typename... T, std::size_t... I> 
std::tuple<T...> build_tuple()
{
    // How do I write this in a generic way?
    return std::make_tuple(
        func<std::string>(0),
        func<int>(1),
        func<int>(2)
        );

    // This won't work, obviously
    // return std::make_tuple(func<T...>(I...));
}

int main() 
{
    build_tuple<std::string, int, int>();
}

Essentially, my question is how do I unpack arguments to get something along the lines of "type0, 0", "type1, 1" etc., instead of "type0, type1, ..., 0, 1, ...", if that makes sense.

This feels like a common problem, so is there an idiomatic solution?


Solution

  • If size_t arguments are 0, 1, ..., you can simply use additional level of indirection:

    template<class Tuple, std::size_t... I> 
    Tuple build_tuple_impl(std::index_sequence<I...>)
    {
        return std::make_tuple(
            func<std::tuple_element_t<I, Tuple>>(I)...);
    }
    
    template<typename... Ts> 
    auto build_tuple()
    {
        using Tuple = std::tuple<Ts...>;
        return build_tuple_impl<Tuple>(std::make_index_sequence<sizeof...(Ts)>{});
    }
    
    // Usage:
    auto t = build_tuple<std::string, int, int>();
    

    More general case:

    template<class Tuple, std::size_t... Is, std::size_t... I> 
    Tuple build_tuple_impl(std::index_sequence<Is...>, std::index_sequence<I...>)
    {
        constexpr std::size_t is[] = {Is...};
        return std::make_tuple(
            func<std::tuple_element_t<I, Tuple>>(is[I])...);
    }
    
    template<typename... Ts, std::size_t... Is>
    auto build_tuple(std::index_sequence<Is...> is)
    {
        static_assert(sizeof...(Ts) == sizeof...(Is));
    
        using Tuple = std::tuple<Ts...>;
        return build_tuple_impl<Tuple>(is, std::make_index_sequence<sizeof...(Ts)>{});
    }
    
    // Usage:
    auto t = build_tuple<std::string, int, int>(std::index_sequence<3, 4, 5>{});
    

    Not that you can't write build_tuple<std::string, int, int, 3, 4, 5>(). One of the sequences should be packed into a single type.