Oto szybki y-syntezator oparty rekurencyjne silnika:
template<class F>
struct recursive_t {
F f;
// note Self must be an lvalue reference. Things get
// strange if it is an rvalue:
// invoke makes recursive ADL work a touch better.
template<class Self, class...Args>
friend auto invoke(Self& self, Args&&...args)
-> decltype(self.f(self, std::declval<Args>()...))
{
return self.f(self, std::forward<Args>(args)...);
}
// calculate return type using `invoke` above:
template<class Self, class...Args>
using R = decltype(invoke(std::declval<Self>(), std::declval<Args>()...));
template<class...Args>
R<recursive_t&, Args...> operator()(Args&&...args)
{
return invoke(*this, std::forward<Args>(args)...);
}
template<class...Args>
R<recursive_t const&, Args...> operator()(Args&&...args)const
{
return invoke(*this, std::forward<Args>(args)...);
}
};
template<class F>
recursive_t< std::decay_t<F> > recurse(F&& f)
{
return {std::forward<F>(f)};
}
Teraz można zrobić:
auto f = recurse([](auto&& f, auto x){ return x ? f(x/2)+1 : 0; });
i masz rekurencyjne lambda, która nie ma przechwytywania &
(co ogranicza jego zastosowanie do bieżącego zakresu).
Przechwytywanie std::function
przez odniesienie oznacza, że czas życia lambda jest bieżącym zakresem, a każde wywołanie rekurencyjne wymaga przejścia przez usuwanie typu (blokowanie dowolnej możliwej optymalizacji, takiej jak rekursja ogona, przez rekursywne wywołanie). To samo dotyczy innych podobnych rozwiązań.
Używanie recursive_t
jest wymagane, zamiast używać lambda, ponieważ lambda nie może sama się w sobie nazwać.
Live example.
Wersja oparta na lambdzie jest nieco prostsza w implementacji. Zauważ, że należałoby inną funkcję typu dla modyfikowalnych i niezmiennych lambdas:
template<class F>
auto recurse(F&& f) {
return [f=std::forward<F>(f)](auto&&...args){
return f(f, decltype(args)(args)...);
};
};
W recursive_t
działa jak:
auto fib = recurse([](auto&& fib, int x){ if (x<2) return 1; return fib(x-1)+fib(x-2); });
wersja lambda działa jak:
auto fib = recurse([](auto&& self, int x){ if (x<2) return 1; return self(self, x-1)+self(self,x-2); });
które , osobiście, znajdź coś bardziej niezręcznego.
Trudniej jest również opisać typ recurse
. Dla wersji recursive_t
, recurse
jest typu:
((A->B)->A->B)->(A->B)
co jest niewygodne, ale to typ skończony.
Wersja lambda jest trudniejsza. Typ argumentu funkcyjnym recursive
jest typu:
F:= F->A->B
który nieznośno nieskończona, a następnie recurse
jest typu
F->A->(A->B)
która dziedziczy się w nieskończoność.
W każdym razie wartość zwracana recurse
może być przechowywana w przyziemnym std::function
lub nie jest przechowywana w żadnym wymazanym typie pojemniku.
Problem tutaj, jak sądzę, polega na tym, że funkcja lamba wymaga określonego typu - "auto" po prostu pozwala na samodzielną kompilację. Nie oznacza to, że można przekazać funkcję lambda do dowolnego kodu, który ma dowolny typ - typ musi być znany podczas kompilacji funkcji lambda. –
Co z 'auto & f = [& f] (auto x) {return x? f (x/2) +1: 0;}; '? Czy to działa? (Prawdopodobnie po prostu 'auto' zamiast' auto & '.) Nie mam przy sobie kompilatora obsługującego C++ 14, więc nie mogę go łatwo przetestować. – celticminstrel
'std :: function' używa typu wymazania. Jestem dość pewny, że * zasadniczo niemożliwe * jest napisanie owijanego obiektu funkcji typu, który akceptuje dowolne typy, ponieważ każdy zestaw typów argumentów musi * stworzyć * nową funkcję, która może być wykonana tylko wtedy, gdy kompilator widzi owinięty * szablon funkcji *. Obejścia obejmują użycie stałego zestawu zestawów typów parametrów (użyj istniejących zestawów przeciążeń/wykonaj wstępne obliczenia wszystkich przeciążeń przed usunięciem typu) lub usuń argumenty typu. – dyp