Вот быстрый у-комбинатор на основе рекурсивный двигатель:
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)};
}
теперь вы можете сделать:
auto f = recurse([](auto&& f, auto x){ return x ? f(x/2)+1 : 0; });
и вы получите рекурсивное лямбда, не имеют &
захват (что ограничивает его использование в текущей области видимости).
Захват std::function
по ссылке означает, что ваше время жизни лямбда - это текущая область действия, и каждый рекурсивный вызов требует перехода на стирание типа (блокирование любой возможной оптимизации, например хвостовой рекурсии, по рекурсивному вызову). То же самое относится к другим аналогичным решениям.
Необходимо использовать recursive_t
, а не использовать лямбда, поскольку лямбда не может называть себя самой.
Live example.
На основе лямбда-версии несколько проще в реализации. Обратите внимание, что вы должны были бы различными функции типа для изменяемой и неизменяемой лямбды:
template<class F>
auto recurse(F&& f) {
return [f=std::forward<F>(f)](auto&&...args){
return f(f, decltype(args)(args)...);
};
};
В recursive_t
работает как:
auto fib = recurse([](auto&& fib, int x){ if (x<2) return 1; return fib(x-1)+fib(x-2); });
версия лямбда работает как:
auto fib = recurse([](auto&& self, int x){ if (x<2) return 1; return self(self, x-1)+self(self,x-2); });
, который я , лично, найти более неудобно.
Это также сложнее описать тип recurse
. Для версии recursive_t
, recurse
имеет тип:
((A->B)->A->B)->(A->B)
что неудобно, но конечный тип.
Лямбда-версия сложнее. Тип функции аргумента recursive
имеет тип:
F:= F->A->B
который раздражающе бесконечным, а затем recurse
имеет тип
F->A->(A->B)
, который наследует бесконечность.
Во всяком случае, возвращаемое значение recurse
может быть сохранено в обыденном std::function
или не храниться в контейнере с любым типом.
Проблема здесь, я думаю, заключается в том, что функция lamba нуждается в определенном типе - 'auto' просто позволяет компилировать вычисление самостоятельно. Это не означает, что вы можете передать лямбда-функцию любому коду с любым типом - тип должен быть известен во время компиляции лямбда-функции. –
Как насчет 'auto & f = [& f] (auto x) {return x? f (x/2) +1: 0;}; '? Это работает? (Возможно, просто 'auto' вместо' auto & '.) У меня нет компилятора с поддержкой C++ 14, поэтому я не могу его легко проверить. – celticminstrel
'std :: function' использует стирание типа. Я совершенно уверен, что принципиально невозможно * создать объект-стираемый объект-объект, который принимает произвольные типы, поскольку каждый набор типов аргументов должен * создавать * новую функцию, которая может быть выполнена только в том случае, если компилятор может видеть завернутый * шаблон функции *. Методы обхода включают использование фиксированного набора наборов типов параметров (используйте существующие наборы перегрузок/precompute всех перегрузок до стирания типа) или тип-стирание аргументов. – dyp