Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to capture a parameter pack by forward or move?

Tags:

c++

c++17

Say I have this function:

template <typename ...A>
void test(A&& ...a)
{
  [=]()
  {
  };
}

is the parameter pack going to be forwarded into the lambda or just copied by value? I'm concerned, since I'd have to make an explicit move() or forward() usually, as a... are lvalues. Is a tuple intermediary necessary to forward/move them? If so, does there exist an easy way to unpack the tuple into a parameter pack, without using the indices trick?

like image 368
user1095108 Avatar asked May 29 '16 14:05

user1095108


4 Answers

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.

like image 57
Yakk - Adam Nevraumont Avatar answered Nov 05 '22 13:11

Yakk - Adam Nevraumont


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...
    };
}
like image 42
Oktalist Avatar answered Nov 05 '22 13:11

Oktalist


First capture the arguments in a tuple with perfect forwarding:

template <typename ...A>
void test(A&& ...a)
{
  [tup= std::tuple<A...>(std::forward<A>(a)...)]()
  {
       //tup should contain the forwarded elements
  };
}

Then use this answer: https://stackoverflow.com/a/7858971/835629 to unpack the tuple in your later function calls.

//utils
template<int ...>
struct seq { };

template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };

template<int ...S>
struct gens<0, S...> {
  typedef seq<S...> type;
};


template<typename F, typename T, int ...S>
void unpackTupleToFunction_utils(F func, const T &tup, seq<S...>) {
 func(std::get<S>(tup) ...);
}

template<typename F, typename ...Args, int ...S>
void unpackTupleToFunction(F func, const std::tuple<Args...> &tup) {
 unpackTupleToFunction_utils(func, tup, typename gens<sizeof...(Args)>::type());
}

And finally to unpack the tuple inside the lambda in order to call a function with it:

template <typename ...Args>
void test(Args&& ...a) {
  auto lambda = [tup= std::tuple<Args...>(std::forward<Args>(a)...)]()
  {
    unpackTupleToFunction(f, tup);
  };

  lambda();
  lambda();
  lambda();
}

PS: It's a pity that something like [a = (std::forward<Args>(a)...)](){}; doesn't compile.

like image 3
coyotte508 Avatar answered Nov 05 '22 13:11

coyotte508


why wouldn't it passed by value? the forwarding is to the top function only.

let's say you pass a int , std::string& and float&& for example, so your function will look like

void test(int,string&,float&&)
{
  [=]()
  {
  };
}

from there, the anonymous lambda will copy the int,string& and float&& by value. a copy of a reference is still a copy. you can use a tuple to pack the arguments again and unpack them inside the lambda.

how to use a tuple inside the lamda?

  1. use a recursion like we usually do with variadic template
  2. find some non standard implementation of std::apply and use the tuple as arguments for anotehr function
like image 2
David Haim Avatar answered Nov 05 '22 11:11

David Haim