how to capture a parameter pack by forward or move?

One of the few remaining useful things that can be done with std::bind. The capturing is performed by bind and the captured values are passed as arguments to a capture-less generic lambda:

template <typename... A>
auto test(A&&... a)
{
    auto f = [](auto&&... a)
    {
        // use a...
    };
    return std::bind(f, std::forward<A>(a)...);
}

Live demo

The above works with Clang, but this GCC seems to have an issue with a spurious volatile qualifier.

We can do it without bind by capturing a tuple in a second lambda that calls std::apply (C++17) to unpack the tuple into the first lambda's parameter list:

template <typename... A>
auto test(A&&... a)
{
    auto f = [](auto&&... a)
    {
        // use a...
    };
    return [f, tup = std::make_tuple(std::forward<A>(a)...)]() mutable { std::apply(f, tup); };
}

Live demo

Works with Clang and GCC; apply is implemented with the indices trick that you wanted to avoid, but you are not exposed to it. The mutable means the second lambda's call operator is non-const, so the tuple elements don't end up gaining a const qualification.


C++20

C++20 has proper support for capturing by perfect forwarding:

template <typename... A>
auto test(A&&... a)
{
    return [...a = std::forward<A>(a)]()
    {
        // use a...
    };
}

One way would be to write a functor in the Haskell sense. Well a variardic one, which isn't very Haskell.

Write a function of signature (Ts...)->( ((Ts...)->X) -> X ). Ie a function that takes a pack, and returns a function. The returned function can take a function taking that pack and evaluate it.

template<class...Ts>
auto make_functor(Ts&&...ts); // TODO

Once we have that we can solve your problem easily.

template<class ...A>
auto test(A&& ...a) {
  return [unpack_a=make_functor(std::forward<A>(a)...)]() mutable
  {
    return unpack_a([&](auto&&...a){
      // here you have access to a...
      return sizeof...(a);
    });
  };
}

test takes a pack, and returns a function that returns the size of that pack (well, does anything with the pack).

make_functor is not easy: basically, we write a manual lambda, storing the args in a tuple, and unpacking the musing the indexes trick in an operator ().

In effect, we do the pack storing and unpacking once in a manual pseudo-lambda class, then get to reuse it later.

On second thought, it may be better to write a delayed apply that takes a tuple, stores it, then uses std::apply later.

template<class...Ts>
auto delayed_apply(std::tuple<Ts...> tup){
  return [tup=std::move(tup)](auto&&f)->decltype(auto) mutable{
    return std::experimental::apply(decltype(f)(f), std::move(tup));
  };
}

which lets the value/refness of parameters be not lost!

template<class ...A>
auto test(A&& ...a) {
  return [unpack_a=delayed_apply(std::forward_as_tuple(std::forward<A>(a)...))]() mutable
  {
    return unpack_a([&](auto&&...a){
      // here you have access to a...
      return sizeof...(a);
    });
  };
}

this does require std::experimental::apply.

If you want to store rvalues and leave lvalues as references:

unpack_a=delayed_apply(std::tuple<A...>(std::forward<A>(a)...))

If you want to store both l and r values:

unpack_a=delayed_apply(std::make_tuple(std::forward<A>(a)...))

as you can see, this approach gives lots of control.

If you need a std::experimental::apply, there are reference implementations: better those than anything I write on a smartphone.

Note that make_functor can be written in terms of delayed_apply, but the opposite is ... not as true.

In case you are confused, unpack_a takes a lambda and unpacks the tuple used to create unpack_a into it. Basically we store one object that is the entire pack, then unpack it when we need it inside the body of the lambda.

A longer delayed_apply that handles both const and non-const and maybe even rvalue overloads may be required if you want the unpacking to work "more than once" sometimss and "only once" other times. It will have to return a class, not a lambda. Annoying. Made the example code work, I think, still not compiling.

Fortunetally this kind of thing is write once, use many.

Tags:

C++

C++17