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?
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.
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 has proper support for capturing by perfect forwarding:
template <typename... A>
auto test(A&&... a)
{
return [...a = std::forward<A>(a)]()
{
// use a...
};
}
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.
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?
std::apply
and use the tuple as arguments for anotehr functionIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With