Well, your code doesn't work. But this does:
template<class F>
struct ycombinator {
F f;
template<class...Args>
auto operator()(Args&&...args){
return f(f, std::forward<Args>(args)...);
}
};
template<class F>
ycombinator(F) -> ycombinator<F>;
Test code:
ycombinator bob = {[x=0](auto&& self)mutable{
std::cout << ++x << "\n";
ycombinator ret = {self};
return ret;
}};
bob()()(); // prints 1 2 3
Your code is both UB and ill-formed no diagnostic required. Which is funny; but both can be fixed independently.
First, the UB:
auto it = [&](auto self) { // outer
return [&](auto b) { // inner
std::cout << (a + b) << std::endl;
return self(self);
};
};
it(it)(4)(5)(6);
this is UB because outer takes self
by value, then inner captures self
by reference, then proceeds to return it after outer
finishes running. So segfaulting is definitely ok.
The fix:
[&](auto self) {
return [self,&a](auto b) {
std::cout << (a + b) << std::endl;
return self(self);
};
};
The code remains is ill-formed. To see this we can expand the lambdas:
struct __outer_lambda__ {
template<class T>
auto operator()(T self) const {
struct __inner_lambda__ {
template<class B>
auto operator()(B b) const {
std::cout << (a + b) << std::endl;
return self(self);
}
int& a;
T self;
};
return __inner_lambda__{a, self};
}
int& a;
};
__outer_lambda__ it{a};
it(it);
this instantiates __outer_lambda__::operator()<__outer_lambda__>
:
template<>
auto __outer_lambda__::operator()(__outer_lambda__ self) const {
struct __inner_lambda__ {
template<class B>
auto operator()(B b) const {
std::cout << (a + b) << std::endl;
return self(self);
}
int& a;
__outer_lambda__ self;
};
return __inner_lambda__{a, self};
}
int& a;
};
So we next have to determine the return type of __outer_lambda__::operator()
.
We go through it line by line. First we create __inner_lambda__
type:
struct __inner_lambda__ {
template<class B>
auto operator()(B b) const {
std::cout << (a + b) << std::endl;
return self(self);
}
int& a;
__outer_lambda__ self;
};
Now, look there -- its return type is self(self)
, or __outer_lambda__(__outer_lambda__ const&)
. But we are in the middle of trying to deduce the return type of __outer_lambda__::operator()(__outer_lambda__)
.
You aren't allowed to do that.
While in fact the return type of __outer_lambda__::operator()(__outer_lambda__)
is not actually dependent on the return type of __inner_lambda__::operator()(int)
, C++ doesn't care when deducing return types; it simply checks the code line by line.
And self(self)
is used before we deduced it. Ill formed program.
We can patch this by hiding self(self)
until later:
template<class A, class B>
struct second_type_helper { using result=B; };
template<class A, class B>
using second_type = typename second_type_helper<A,B>::result;
int main(int argc, char* argv[]) {
int a = 5;
auto it = [&](auto self) {
return [self,&a](auto b) {
std::cout << (a + b) << std::endl;
return self(second_type<decltype(b), decltype(self)&>(self) );
};
};
it(it)(4)(6)(42)(77)(999);
}
and now the code is correct and compiles. But I think this is a bit of hack; just use the ycombinator.