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

Creating a sub-tuple starting from a std::tuple<some_types...>


Let us suppose that a std::tuple<some_types...> is given. I would like to create a new std::tuple whose types are the ones indexed in [0, sizeof...(some_types) - 2]. For instance, let's suppose that the starting tuple is std::tuple<int, double, bool>. I would like to obtain a sub-tuple defined as std::tuple<int, double>.

I'm quite new to variadic templates. As a first step I tried to write a struct in charge of storing the different types of the original std::tuple with the aim of creating a new tuple of the same kind (as in std::tuple<decltype(old_tuple)> new_tuple).

template<typename... types>
struct type_list;

template<typename T, typename... types>
struct type_list<T, types...> : public type_list<types...>  {
    typedef T type;
};


template<typename T>
struct type_list<T> {
    typedef T type;
};

What I would like to do is something like:

std::tuple<type_list<bool, double, int>::type...> new_tuple // this won't work

And the next step would be of discarding the last element in the parameter pack. How can I access the several type's stored in type_list? and how to discard some of them?

Thanks.


Solution

  • This kind of manipulation is fairly easy with an index sequence technique: generate an index sequence with two fewer indices than your tuple, and use that sequence to select fields from the original. Using std::make_index_sequence and return type deduction from C++14:

    template <typename... T, std::size_t... I>
    auto subtuple_(const std::tuple<T...>& t, std::index_sequence<I...>) {
      return std::make_tuple(std::get<I>(t)...);
    }
    
    template <int Trim, typename... T>
    auto subtuple(const std::tuple<T...>& t) {
      return subtuple_(t, std::make_index_sequence<sizeof...(T) - Trim>());
    }
    

    In C++11:

    #include <cstddef>     // for std::size_t
    
    template<typename T, T... I>
    struct integer_sequence {
      using value_type = T;
    
      static constexpr std::size_t size() noexcept {
        return sizeof...(I);
      }
    };
    
    namespace integer_sequence_detail {
    template <typename, typename> struct concat;
    
    template <typename T, T... A, T... B>
    struct concat<integer_sequence<T, A...>, integer_sequence<T, B...>> {
      typedef integer_sequence<T, A..., B...> type;
    };
    
    template <typename T, int First, int Count>
    struct build_helper {
      using type = typename concat<
        typename build_helper<T, First,           Count/2>::type,
        typename build_helper<T, First + Count/2, Count - Count/2>::type
      >::type;
    };
    
    template <typename T, int First>
    struct build_helper<T, First, 1> {
      using type = integer_sequence<T, T(First)>;
    };
    
    template <typename T, int First>
    struct build_helper<T, First, 0> {
      using type = integer_sequence<T>;
    };
    
    template <typename T, T N>
    using builder = typename build_helper<T, 0, N>::type;
    } // namespace integer_sequence_detail
    
    template <typename T, T N>
    using make_integer_sequence = integer_sequence_detail::builder<T, N>;
    
    template <std::size_t... I>
    using index_sequence = integer_sequence<std::size_t, I...>;
    
    template<size_t N>
    using make_index_sequence = make_integer_sequence<size_t, N>;
    
    #include <tuple>
    
    template <typename... T, std::size_t... I>
    auto subtuple_(const std::tuple<T...>& t, index_sequence<I...>) 
      -> decltype(std::make_tuple(std::get<I>(t)...))
    {
      return std::make_tuple(std::get<I>(t)...);
    }
    
    template <int Trim, typename... T>
    auto subtuple(const std::tuple<T...>& t)
      -> decltype(subtuple_(t, make_index_sequence<sizeof...(T) - Trim>()))
    {
      return subtuple_(t, make_index_sequence<sizeof...(T) - Trim>());
    }
    

    Live at Coliru.