In C++ we can have a recursive variadic template function. For example:
template<typename ...H>
void g(){}
template<typename H, typename ...T>
void g(H h,T ...t){
std::cout << h << "\t";
g<T...>(t...);
}
However this does not seem possible to be done using lambdas. My two main main concerns are:
I know I can have recursive lambdas but can't see a way to make it variadic. Is this type of feature only available in more high-level kind of languages such as Javascript?
Edit: So far, this is what I have come up with:
template<typename C,typename H, typename ...T>
std::function<void(C,H,T...)> f=[](auto&& self,H h,T ...t){
std::cout << h << "\t";
if(sizeof...(t)>0)
self(self,t...);
};
Here the first argument would be the lambda itself. However, the main issue is that in order to call this method I would to have define the type C, which I'm unsure on how to do (or even if it's possible).
Edit: A more simplistic approach would be:
auto f = [] (auto&& self, auto&& h,auto&&... t) {
std::cout << sizeof...(t) << "\n";
if( sizeof...(t)>0 ){
self(self,1);
}
};
int main()
{
f(f,1,2,3,4,5,6);
return 0;
}
However gives the following error:
main.cpp:55:13: error: use of ' [with auto:1 = &; auto:2 = int; auto:3 = {}]' before deduction of 'auto'
self(self,1);
^
C++17 solves this problem with Constexpr if:
#include <iostream>
auto f = [](auto&&... t){
auto f_impl = [](auto& self, auto&& h, auto&&... t) {
std::cout << sizeof...(t) << "\n";
if constexpr ( sizeof...(t)>0 ){
self(self,t...);
}
};
return f_impl(f_impl, t...);
};
int main() {
f(1,2,3,4,5,6);
}
I also took the liberty of wrapping the first parameter(self
) inside a "parent" lambda.
The problem with using if(sizeof...(t))
as a guard is that, even if you will not call the lambda with improper arguments at runtime, the compiler still needs to compile the expression self(self, t...)
with sizeof...(t)==0
, which fails.
constexpr if
solves this by making this check at compile-time, and not even compiling the block when the check yields false
. Before C++17, conditional compilation semantics(excluding macros) could only be achieved using SFINAE or template specialization, both of which can't be done using only lambdas.
I hate helpers, i think using helpers is a misleading design. That said, i couldn't find a solution to this without using a small helper.
The obstacles encountered here are the following:
You cannot refer to a variable declared with auto
within it's own initialisation expression
This is solved by passing the function as a parameter to itself using an inner lambda to provide a clean public interface
You cannot use if( sizeof... )
as a guard (or any other "runtime" mechanism) for the "base-case" because you would still have a call to a function that cannot accept that number of parameters within that if
block, which doesn't compile
This is solved by "overloading the lambda" and actually defining all the needed function overloads (i.e the "base-case" also)
You cannot "simply" overload a lambda once it is defined
This is solved by using a "small" helper code
All in all, one solution could be this:
#include <iostream>
/////////// Helper
template<class F, class... Fs>
struct overloaded : F, overloaded<Fs...>
{
using F::operator();
using overloaded<Fs...>::operator();
overloaded(F&& f, Fs&&... fs)
: F(std::move(f))
, overloaded<Fs...>(std::move(fs)...)
{}
};
template<class F>
struct overloaded<F> : F
{
using F::operator();
overloaded(F&& f)
: F(std::move(f))
{}
};
template<class... Ts>
overloaded<Ts...> overload(Ts&&...lambdas)
{ return overloaded<Ts...>{std::move(lambdas)...}; }
///////// Recursive Variadic Lambda
int main(void)
{
auto lambda = [](auto... args)
{
auto lambda_impl = overload(
[](auto self)
{
},
[] (auto self, auto first, auto...rest)
{
std::cout << first << std::endl;
self(self, rest...);
});
lambda_impl(lambda_impl, args...);
};
lambda(4, "lol", 8.3);
}
If 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