4

I have a function foo that calls a function bar with a subset of types passed into foo's variadic template. For example:

template <typename... T>
void foo() {
  // ...
  template <size_t start_idx, typename... T>
  using param_pack = /*Parameter pack with T[start_idx]...T[N]*/
  auto b = bar<param_pack<2, T...>>();
  // ...
}

Is there a way to extract a "sub-parameter pack". In the above case if T = [int float char double] then param_pack<2, T...> = [char double]

[EDIT]

My goal is to be able to use something like this to match event handlers. For example

struct ev {};

template <typename... T>
struct event : ev {
  std::tuple<T...> data_;

  event(T&&... d) : data_(std::make_tuple(std::forward<T>(d)...)) {}
};

template <typename... Functor>
struct handler {
  std::tuple<Functor...> funcs_;

  handler(Functor&&... f) : funcs_(std::make_tuple(std::forward<Functor>(f)...)) {}

  void handle_message(ev* e) {
    auto ptrs = std::make_tuple(
      dynamic_cast<event<param_pack<1, typename function_traits<F>::args>>*>(e)...
    ); 

    match(ptrs);
  }
};

Here function_traits::args get a parameter pack for the function arguments and match iterates over the the tuple funcs_ checking if the dynamic_cast was successful and executing the first successful function. I already have these implemented.

The handlers are something like

[] (handler* self, <ARGS>) -> void {
  // ...
}

I am essentially trying to get rid of the self argument.

ssb
  • 7,422
  • 10
  • 36
  • 61

3 Answers3

4

Set aside the fact that it lacks a check on the index N for simplicity, here is a possible solution based on a function declaration (no definition required) and an using declaration:

template<std::size_t N, typename... T, std::size_t... I>
std::tuple<std::tuple_element_t<N+I, std::tuple<T...>>...>
sub(std::index_sequence<I...>);

template<std::size_t N, typename... T>
using subpack = decltype(sub<N, T...>(std::make_index_sequence<sizeof...(T) - N>{}));

The good part of this approach is that you have not to introduce a new type designed around a tuple, then specialize it somehow iteratively.


It follows a minimal, working example that uses the code above:

#include<functional>
#include<tuple>
#include<cstddef>
#include<type_traits>

template<std::size_t N, typename... T, std::size_t... I>
std::tuple<std::tuple_element_t<N+I, std::tuple<T...>>...>
sub(std::index_sequence<I...>);

template<std::size_t N, typename... T>
using subpack = decltype(sub<N, T...>(std::make_index_sequence<sizeof...(T) - N>{}));

int main() {
    static_assert(std::is_same<subpack<2, int, float, char, double>, std::tuple<char, double>>::value, "!");
}

See a full example up and running on wandbox.


The extended version that includes a check on the index N would look like this:

template<std::size_t N, typename... T, std::size_t... I>
std::enable_if_t<(N < sizeof...(T)), std::tuple<std::tuple_element_t<N+I, std::tuple<T...>>...>>
sub(std::index_sequence<I...>);

That is the type you can see in the first example once wrapped in a std::enable_if_t, nothing more. Again, declaration is enough, no definition required.


EDIT

If you want to use your own class template instead of an std::tuple, you can easily modify the code to do that:

#include<functional>
#include<tuple>
#include<cstddef>
#include<type_traits>

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

template<template<typename...> class C, std::size_t N, typename... T, std::size_t... I>
std::enable_if_t<(N < sizeof...(T)), C<std::tuple_element_t<N+I, std::tuple<T...>>...>>
sub(std::index_sequence<I...>);

template<template<typename...> class C, std::size_t N, typename... T>
using subpack = decltype(sub<C, N, T...>(std::make_index_sequence<sizeof...(T) - N>{}));

int main() {
    static_assert(std::is_same<subpack<bar, 2, int, float, char, double>, bar<char, double>>::value, "!");
}

EDIT

According to the code added to the question, the solution above is still valid. You should just define your event class as it follows:

struct ev {};

template <typename>
struct event;

template <typename... T>
struct event<std::tuple<T...>>: ev {
    // ...
};

This way, when you do this:

event<param_pack<1, typename function_traits<F>::args>>

You still get a tuple out of param_pack (that is the subpack using declaration in my example), but it matches the template partial specialization of event and the parameter pack is at your disposal as T....

This is the best you can do, for you cannot put a parameter pack in an using declaration. Anyway it just works, so probably it can solve your issue.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • The type is a `std::tuple` is there a way to get the pack directly i.e., `T...` – ssb Jul 18 '17 at 19:32
  • @subzero You cannot put a pack in an using declaration. All what you can is passing the type you want to fill along with the other data and get it back instead of a tuple. Could it work for you? How do you want to use that pack? – skypjack Jul 18 '17 at 19:35
  • @subzero I added another example to show how you can use your own class template. Let me know if it works for you. – skypjack Jul 18 '17 at 20:01
  • I have added an example use to the question – ssb Jul 18 '17 at 20:04
  • @subzero `function_traits::args` is the list of types you want to store aside? – skypjack Jul 18 '17 at 20:12
  • I dont want to store them... I want to pass them to the stored functor – ssb Jul 18 '17 at 21:30
  • @subzero You can't put them in a using declaration. That's how C++ works, I'm sorry. But if you store them in a tuple you can then unpack it by means of `std::tuple_element` as I did in the example code. That's the best you can do. – skypjack Jul 18 '17 at 21:32
  • @subzero Answer updated. See the last section. I added a way you can follow to solve your issue. This is the best you can do, really. – skypjack Jul 18 '17 at 21:48
3

You may do something like:

template <std::size_t N, typename ... Ts> struct drop;

template <typename ... Ts>
struct drop<0, Ts...>
{
    using type = std::tuple<Ts...>;
};

template <std::size_t N, typename T, typename ... Ts>
struct drop<N, T, Ts...>
{
    using type = typename drop<N - 1, Ts...>;
};

// Specialization to avoid the ambiguity
template <typename T, typename... Ts>
struct drop<0, T, Ts...>
{
    using type = std::tuple<T, Ts...>;
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I've tried to implement the similar idea but stucked with the fact that the compiler cannot choose the specialization for `N = 0`. Could you please take a look: https://wandbox.org/permlink/u3QOECdxokexLgyG – Edgar Rokjān Jul 18 '17 at 16:46
  • @EdgarRokyan: Indeed, an extra specialization is required. [Fixed version](https://wandbox.org/permlink/K0Bb3MwjwTtJFU8x). – Jarod42 Jul 18 '17 at 16:54
  • Perfect, thanks! Though it looks a bit awkward now :( – Edgar Rokjān Jul 18 '17 at 16:59
  • The type is a `std::tuple` is there a way to get the pack directly i.e., `T...` – ssb Jul 18 '17 at 19:30
  • Not with a `using`. But you can still change `std::tuple` to `bar` easily afterward. – Jarod42 Jul 18 '17 at 19:48
0

Here is a quick but not particularly reusable solution.

template <typename Pack, std::size_t N, std::size_t... Is>
void invoke_bar_impl(std::index_sequence<Is...>) {
    bar<std::tuple_element_t<N + Is, Pack>...>();
}

template <std::size_t N, typename... Ts>
void invoke_bar() {
    auto indices = std::make_index_sequence<sizeof...(Ts) - N>();
    invoke_bar_impl<std::tuple<Ts...>, N>(indices);
}
Joseph Thomson
  • 9,888
  • 1
  • 34
  • 38