Nun, Ihr Code funktioniert nicht. Aber das tut:
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>;
Testcode:
ycombinator bob = {[x=0](auto&& self)mutable{
std::cout << ++x << "\n";
ycombinator ret = {self};
return ret;
}};
bob()()(); // prints 1 2 3
Ihr Code ist sowohl UB als auch schlecht geformt, keine Diagnose erforderlich. Welches ist lustig; aber beide können unabhängig voneinander behoben werden.
Erstens, die UB:
auto it = [&](auto self) { // outer
return [&](auto b) { // inner
std::cout << (a + b) << std::endl;
return self(self);
};
};
it(it)(4)(5)(6);
Dies ist UB, da der äußere self
Wert nach Wert nimmt, der innere Wert self
nach Referenz erfasst und nach outer
Abschluss des Laufvorgangs zurückgegeben wird. Segfaulting ist also definitiv in Ordnung.
Die Reparatur:
[&](auto self) {
return [self,&a](auto b) {
std::cout << (a + b) << std::endl;
return self(self);
};
};
Der Code bleibt schlecht geformt. Um dies zu sehen, können wir die Lambdas erweitern:
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);
dies instanziiert __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;
};
Also müssen wir als nächstes den Rückgabetyp von bestimmen __outer_lambda__::operator()
.
Wir gehen es Zeile für Zeile durch. Zuerst erstellen wir __inner_lambda__
Typ:
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;
};
Schauen Sie dort nach - der Rückgabetyp ist self(self)
oder __outer_lambda__(__outer_lambda__ const&)
. Aber wir sind gerade dabei, den Rückgabetyp von abzuleiten __outer_lambda__::operator()(__outer_lambda__)
.
Das darfst du nicht.
Während der Rückgabetyp von __outer_lambda__::operator()(__outer_lambda__)
tatsächlich nicht vom Rückgabetyp von abhängt __inner_lambda__::operator()(int)
, ist es C ++ egal, wie Rückgabetypen abgeleitet werden. es überprüft einfach den Code Zeile für Zeile.
Und self(self)
wird verwendet, bevor wir es abgeleitet haben. Schlecht geformtes Programm.
Wir können dies korrigieren, indem wir uns self(self)
bis später verstecken :
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);
}
und jetzt ist der Code korrekt und kompiliert. Aber ich denke, das ist ein bisschen Hack; benutze einfach den ykombinator.