Сначала напишу плохой, если функциональный, Y-комбинатор.
using fib_f = std::function<int(int)>;
using protofib_f = std::function< int( fib_f, int ) >;
int protofib( std::function<int(int)> f, int n) {
if (n>1) return f(n-1)+f(n-1);
return n;
}
auto Y( protofib_f f )->fib_f {
return [=](int n) { return f( Y(f), n ); };
}
некрасиво, но работает.
Мы можем написать лучший комбинатор Y.
template<class R>
auto Y = [&](auto&& f){
return [=](auto&&...args)->R {
return f( Y<R>(f), decltype(args)(args)... );
};
};
Для простоты необходимо указать тип возвращаемого значения.
auto fib = Y<int>(protofib);
также откладывает стирание типа, что дает производительность.
Мы можем раздеть тип стирания формы протофиб:
auto protofib = [](auto&& f, int n)->int {
if (n>1) return f(n-1)+f(n-1);
return n;
};
, превратив его в лямбду.
Для улучшенного Y-комбинатора требуется немного больше шаблонов, потому что лямбды не могут получить к нему доступ:
template<class F>
struct y_combinate_t {
F f;
template<class...Args>
decltype(auto) operator()(Args&&...args)const {
return f(*this, std::forward<Args>(args)...);
}
};
template<class F>
y_combinate_t<std::decay_t<F>> y_combinate( F&& f ) {
return {std::forward<F>(f)};
};
опять же, накладные расходы на стирание нулевого типа, и этот не требует, чтобы возвращаемый тип передавался явно. (украдено у меня за здесь ).
В c ++ 17 хелпер y_combinate
может быть исключен:
template<class F>
struct y_combinate {
F f;
template<class...Args>
decltype(auto) operator()(Args&&...args)const {
return f(*this, std::forward<Args>(args)...);
}
};
template<class F>
y_combinate(F&& f)->y_combinate<std::decay_t<F>>;
с руководством по удержанию.
y_combinate{ protofib }
- это полная функция Фиббоначи.
Живой пример .
Если пойти еще дальше, вы можете добавить памятку к вашему комбинатору.