Search code examples
c++tuplestemplate-meta-programmingparameter-pack

Template Meta Programming: How to combine parameter packs to new parameter pack


I'm trying to implement a way of removing some types from a tuple; for instance I want to be able to e.g. only take a tuple of the first 2 template arguments for a tuple depending on a condition:

  1. Is it possible to 'pack' the types of which a tuple consists back into a parameter pack? (tuple -> typename... contained_types)
  2. Is it possible to combine a param. pack with a typename (e.g. use "Pack1..., Pack2..." specifying a single parameter pack for a struct?
#include <cstdint>
#include <tuple>

template <typename... tpl> struct Helper {
  template <std::size_t rem, typename curr, typename... rest> struct take {
    using type = Helper<(tpl..., curr)>::take<rem-1, rest...>::type; // here, I'm trying (2.)
  };

  template <typename curr, typename... rest> struct take<0, curr, rest...> {
    using type = std::tuple<tpl...>;
  };
};

template <std::size_t s, typename... tpl> using take_t = Helper<>::take<s, tpl...>;

int main() {
  take_t<2, int, int, int> k = std::make_tuple(1, 2);
}

edit The line Helper fails with the following message:

/home/juli/test.cc:6:18: error: need ‘typename’ before ‘Helper<tpl ..., curr>::take’ because ‘Helper<tpl ..., curr>’ is a dependent scope
    6 |     using type = Helper<tpl..., curr>::take<rem-1, rest...>::type;

and when I provide typename

/home/juli/test.cc:6:53: error: expected ‘;’ before ‘<’ token
    6 |     using type = typename Helper<tpl..., curr>::take<rem-1, rest...>::type;

edit2 I achieved this via [helper functions](https://gist.github.com/juliusHuelsmann/669f537aeb5e7105386d510d186b24e1 ), but those fail with non primitive types when the constructor is not constexpr so I cannot use it in my use case and am curious to know how to achieve this and why my approach failed.


Solution

  • The answer to Question 2) Yes it is possible, as @user253751 suggested, my original approach (Helper<tpl..., curr>) is correct, I made a different error though, which caused the aforementioned error in that line by omitting 'template' after the used type. The code below shows the fix and works fine:

    #include <cstdint>
    #include <tuple>
    
    template <typename... tpl> struct Helper {
      template <std::size_t rem, typename curr, typename... rest> struct take {
        using tp = Helper<tpl..., curr>;
        using type = tp::template take<rem-1, rest...>::type;
      };
    
      template <typename curr, typename... rest> struct take<0, curr, rest...> {
        using type = std::tuple<tpl...>;
      };
    };
    
    template <std::size_t s, typename... tpl> using take_t = Helper<>::take<s, tpl...>;
    
    int main() {
      take_t<2, int, int, int>::type k = std::make_tuple(1, 2);
    }
    

    The answer to Question 1 is given by the trick performed here, see a full example here. It is possible e.g. by the following construct (adapted from the example on-the-fly and untested):

    template <class tuple> struct foo;
    
    template <class ... args> struct foo<std::tuple<args...> {
        // implementation here!
    };
    

    which is very neat and can be used for integer_sequence aswell (which I tried and did not succeed to do until now).

    An entire implementation of what I wanted to achieve that is much clearer than my initial approach above can be found here:

    template <size_t s, typename is, typename ...args> struct thlp;
    
    template <size_t s, size_t... i, class... args> struct thlp<s, std::index_sequence<i...>, args...> {
      static_assert(s <= sizeof...(args), "Requested new tuple size exceeds old one.");
      using type = std::tuple<std::tuple_element_t<i, std::tuple<args...>>...>;
    };
    
    template <size_t s, typename... args>
    using conditional_tuple_t = thlp<s, decltype(std::make_index_sequence<s>()), args...>::type;