Рекурсивные лямбда-функции в C++11
Я новичок в C++11. Я пишу следующую рекурсивную лямбда-функцию, но она не компилируется.
сумма.cpp
#include <iostream>
#include <functional>
auto term = [](int a)->int {
return a*a;
};
auto next = [](int a)->int {
return ++a;
};
auto sum = [term,next,&sum](int a, int b)mutable ->int {
if(a>b)
return 0;
else
return term(a) + sum(next(a),b);
};
int main(){
std::cout<<sum(1,10)<<std::endl;
return 0;
}
ошибка компиляции:
Вимал@Линукс-718q:~/исследование/09С++/с++0х/лямбда> г++ -с std=с++0х сумма.cpp
сумма.cpp: в лямбда-функции:
сумма.cpp: 18: 36: ошибка:'((<lambda(int, int)>*)this)-><lambda(int, int)>::sum ' не может использоваться как функция
версия gcc
gcc version 4.5.0 20091231 (experimental) (GCC)
но если я измените объявление sum(), как показано ниже, это работает:
std::function<int(int,int)> sum = [term,next,&sum](int a, int b)->int {
if(a>b)
return 0;
else
return term(a) + sum(next(a),b);
};
может кто-нибудь пролить свет на это?
13 ответов:
подумайте о разнице между авто версия и полностью указанная версия типа. Элемент авто ключевое слово выводит свой тип из того, с чем он инициализирован, но то, с чем вы его инициализируете, должно знать, что это за тип (в этом случае лямбда-закрытие должно знать типы, которые оно захватывает). Что-то вроде проблемы с курицей и яйцом.
С другой стороны, полностью определенный тип объекта функции не должен ничего " знать о том, что ему назначается, и поэтому закрытие лямбды также может быть полностью информировано о типах его захвата.
рассмотрим эту небольшую модификацию вашего кода, и это может иметь больше смысла:
std::function<int(int,int)> sum; sum = [term,next,&sum](int a, int b)->int { if(a>b) return 0; else return term(a) + sum(next(a),b); };очевидно, что это не будет работать с авто. Рекурсивные лямбда-функции работают отлично (по крайней мере, они работают в MSVC, где у меня есть опыт работы с ними), просто они не очень совместимы с выводом типа.
трюк состоит в том, чтобы кормить в реализации лямбда себе в качестве параметра, а не путем захвата.
const auto sum = [term,next](int a, int b) { auto sum_impl=[term,next](int a,int b,auto& sum_ref) mutable { if(a>b){ return 0; } return term(a) + sum_ref(next(a),b,sum_ref); }; return sum_impl(a,b,sum_impl); };все проблемы в информатике могут быть решены с помощью другого уровня косвенности. Я впервые нашел этот простой трюк в http://pedromelendez.com/recursive-lambdas-in-c14/
Это тут требуется C++14, в то время как вопрос находится на C++11, но, возможно, интересен для большинства.
через
std::functionтакже возможно, но можете результат в более медленный код. Но не всегда. Взгляните на ответы на С std::функция против шаблона
у меня есть другое решение, но работает только с лямбдами без гражданства:
void f() { static int (*self)(int) = [](int i)->int { return i>0 ? self(i-1)*i : 1; }; std::cout<<self(10); }трюк здесь заключается в том, что лямбды могут обращаться к статическим переменным, и вы можете конвертировать их в указатель функции.
Вы можете использовать его со стандартной лямбды:
void g() { int sum; auto rec = [&sum](int i) -> int { static int (*inner)(int&, int) = [](int& _sum, int i)->int { _sum += i; return i>0 ? inner(_sum, i-1)*i : 1; }; return inner(sum, i); }; }его работа в GCC 4.7
С C++14 теперь довольно легко сделать эффективную рекурсивную лямбду без необходимости нести дополнительные накладные расходы
std::function, всего в нескольких строках кода (с небольшим редактированием от оригинала, чтобы предотвратить пользователь от принятия случайной копии):template <class F> struct y_combinator { F f; // the lambda will be stored here // a forwarding operator(): template <class... Args> decltype(auto) operator()(Args&&... args) const { // we pass ourselves to f, then the arguments. // [edit: Barry] pass in std::ref(*this) instead of *this return f(std::ref(*this), std::forward<Args>(args)...); } }; // helper function that deduces the type of the lambda: template <class F> y_combinator<std::decay_t<F>> make_y_combinator(F&& f) { return {std::forward<F>(f)}; }С которым ваш оригинал
sumпопытка становится:auto sum = make_y_combinator([term,next](auto sum, int a, int b) { if (a>b) { return 0; } else { return term(a) + sum(next(a),b); } });
вы можете заставить лямбда-функцию вызывать себя рекурсивно. Единственное, что вам нужно сделать, это ссылаться на него через оболочку функции, чтобы компилятор знал, что это тип возврата и аргумента (вы не можете захватить переменную-саму лямбду-которая еще не определена).
function<int (int)> f; f = [&f](int x) { if (x == 0) return 0; return x + f(x-1); }; printf("%d\n", f(10));будьте очень осторожны, чтобы не выйти за пределы области действия обертки f.
сделать рекурсивный лямбда без использования внешних классов и функций (например,
std::functionили комбинатор с фиксированной точкой) можно использовать следующую конструкцию в C++14 (видео):#include <utility> #include <list> #include <memory> #include <iostream> int main() { struct tree { int payload; std::list< tree > children = {}; // std::list of incomplete type is allowed }; std::size_t indent = 0; // indication of result type here is essential const auto print = [&] (const auto & self, const tree & node) -> void { std::cout << std::string(indent, ' ') << node.payload << '\n'; ++indent; for (const tree & t : node.children) { self(self, t); } --indent; }; print(print, {1, {{2, {{8}}}, {3, {{5, {{7}}}, {6}}}, {4}}}); }принты:
1 2 8 3 5 7 6 4Примечание, тип результата лямбда должен быть указан явно.
Я запустил тест сравнения рекурсивной функции против рекурсивной лямбда-функции с помощью
std::function<>метод захвата. При полной оптимизации, включенной в clang версии 4.1, лямбда-версия работала значительно медленнее.#include <iostream> #include <functional> #include <chrono> uint64_t sum1(int n) { return (n <= 1) ? 1 : n + sum1(n - 1); } std::function<uint64_t(int)> sum2 = [&] (int n) { return (n <= 1) ? 1 : n + sum2(n - 1); }; auto const ITERATIONS = 10000; auto const DEPTH = 100000; template <class Func, class Input> void benchmark(Func&& func, Input&& input) { auto t1 = std::chrono::high_resolution_clock::now(); for (auto i = 0; i != ITERATIONS; ++i) { func(input); } auto t2 = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count(); std::cout << "Duration: " << duration << std::endl; } int main() { benchmark(sum1, DEPTH); benchmark(sum2, DEPTH); }Результаты поиска:
Duration: 0 // regular function Duration: 4027 // lambda function(Примечание: я также подтвердил версию, которая взяла входы из cin, чтобы исключить оценку времени компиляции)
Clang также создает компилятор внимание:
main.cc:10:29: warning: variable 'sum2' is uninitialized when used within its own initialization [-Wuninitialized]что ожидается, и безопасно, но следует отметить.
здорово иметь решение в наших поясах инструментов, но я думаю, что язык будет нуждаться в лучшем способе обработки этого случая, если производительность будет сопоставима с текущими методами.
Примечание:
как отметил комментатор, похоже, последняя версия VC++ нашла способ оптимизировать это до точки равной производительности. Может быть, нам не нужен лучший способ справьтесь с этим, в конце концов (за исключением синтаксического сахара).
кроме того, как некоторые другие сообщения SO наметили в последние недели, производительность
std::function<>сам по себе может быть причиной замедления против вызова функции напрямую, по крайней мере, когда захват лямбда слишком велик, чтобы вписаться в некоторое пространство, оптимизированное для библиотекиstd::functionиспользует для малых функторов (я думаю, что это похоже на различные короткие строковые оптимизации?).
Это немного более простая реализация оператора fixpoint, что делает его немного более очевидным, что именно происходит.
#include <iostream> #include <functional> using namespace std; template<typename T, typename... Args> struct fixpoint { typedef function<T(Args...)> effective_type; typedef function<T(const effective_type&, Args...)> function_type; function_type f_nonr; T operator()(Args... args) const { return f_nonr(*this, args...); } fixpoint(const function_type& p_f) : f_nonr(p_f) { } }; int main() { auto fib_nonr = [](const function<int(int)>& f, int n) -> int { return n < 2 ? n : f(n-1) + f(n-2); }; auto fib = fixpoint<int,int>(fib_nonr); for (int i = 0; i < 6; ++i) { cout << fib(i) << '\n'; } }
C++ 14: Вот рекурсивный анонимный без состояния / без захвата общий набор лямбд что выводит все числа от 1, 20
([](auto f, auto n, auto m) { f(f, n, m); })( [](auto f, auto n, auto m) -> void { cout << typeid(n).name() << el; cout << n << el; if (n<m) f(f, ++n, m); }, 1, 20);Если я правильно понимаю, это использует решение y-комбинатора
и вот сумма (n, m) версия
auto sum = [](auto n, auto m) { return ([](auto f, auto n, auto m) { int res = f(f, n, m); return res; })( [](auto f, auto n, auto m) -> int { if (n > m) return 0; else { int sum = n + f(f, n + 1, m); return sum; } }, n, m); }; auto result = sum(1, 10); //result == 55
вы пытаетесь захватить переменную (сумма) вы находитесь в середине определения. Это не может быть хорошо.
Я не думаю, что истинно саморекурсивные C++0x лямбды возможны. Вы должны быть в состоянии захватить другие лямбды, хотя.
вот окончательный ответ на ОП. В любом случае, в Visual Studio 2010 не поддерживает захват глобальных переменных. И вам не нужно их захватывать, потому что глобальная переменная доступна глобально по определению. В следующем ответе вместо этого используется локальная переменная.
#include <functional> #include <iostream> template<typename T> struct t2t { typedef T t; }; template<typename R, typename V1, typename V2> struct fixpoint { typedef std::function<R (V1, V2)> func_t; typedef std::function<func_t (func_t)> tfunc_t; typedef std::function<func_t (tfunc_t)> yfunc_t; class loopfunc_t { public: func_t operator()(loopfunc_t v)const { return func(v); } template<typename L> loopfunc_t(const L &l):func(l){} typedef V1 Parameter1_t; typedef V2 Parameter2_t; private: std::function<func_t (loopfunc_t)> func; }; static yfunc_t fix; }; template<typename R, typename V1, typename V2> typename fixpoint<R, V1, V2>::yfunc_t fixpoint<R, V1, V2>::fix = [](tfunc_t f) -> func_t { return [f](fixpoint<R, V1, V2>::loopfunc_t x){ return f(x(x)); } ([f](fixpoint<R, V1, V2>::loopfunc_t x) -> fixpoint<R, V1, V2>::func_t{ auto &ff = f; return [ff, x](t2t<decltype(x)>::t::Parameter1_t v1, t2t<decltype(x)>::t::Parameter1_t v2){ return ff(x(x))(v1, v2); }; }); }; int _tmain(int argc, _TCHAR* argv[]) { auto term = [](int a)->int { return a*a; }; auto next = [](int a)->int { return ++a; }; auto sum = fixpoint<int, int, int>::fix( [term,next](std::function<int (int, int)> sum1) -> std::function<int (int, int)>{ auto &term1 = term; auto &next1 = next; return [term1, next1, sum1](int a, int b)mutable ->int { if(a>b) return 0; else return term1(a) + sum1(next1(a),b); }; }); std::cout<<sum(1,10)<<std::endl; //385 return 0; }
вам нужен комбинатор с фиксированной точкой. Смотрите этой.
или посмотрите на следующий код:
//As decltype(variable)::member_name is invalid currently, //the following template is a workaround. //Usage: t2t<decltype(variable)>::t::member_name template<typename T> struct t2t { typedef T t; }; template<typename R, typename V> struct fixpoint { typedef std::function<R (V)> func_t; typedef std::function<func_t (func_t)> tfunc_t; typedef std::function<func_t (tfunc_t)> yfunc_t; class loopfunc_t { public: func_t operator()(loopfunc_t v)const { return func(v); } template<typename L> loopfunc_t(const L &l):func(l){} typedef V Parameter_t; private: std::function<func_t (loopfunc_t)> func; }; static yfunc_t fix; }; template<typename R, typename V> typename fixpoint<R, V>::yfunc_t fixpoint<R, V>::fix = [](fixpoint<R, V>::tfunc_t f) -> fixpoint<R, V>::func_t { fixpoint<R, V>::loopfunc_t l = [f](fixpoint<R, V>::loopfunc_t x) -> fixpoint<R, V>::func_t{ //f cannot be captured since it is not a local variable //of this scope. We need a new reference to it. auto &ff = f; //We need struct t2t because template parameter //V is not accessable in this level. return [ff, x](t2t<decltype(x)>::t::Parameter_t v){ return ff(x(x))(v); }; }; return l(l); }; int _tmain(int argc, _TCHAR* argv[]) { int v = 0; std::function<int (int)> fac = fixpoint<int, int>::fix([](std::function<int (int)> f) -> std::function<int (int)>{ return [f](int i) -> int{ if(i==0) return 1; else return i * f(i-1); }; }); int i = fac(10); std::cout << i; //3628800 return 0; }
этот ответ уступает Янкесу, но все же, вот он:
using dp_type = void (*)(); using fp_type = void (*)(dp_type, unsigned, unsigned); fp_type fp = [](dp_type dp, unsigned const a, unsigned const b) { ::std::cout << a << ::std::endl; return reinterpret_cast<fp_type>(dp)(dp, b, a + b); }; fp(reinterpret_cast<dp_type>(fp), 0, 1);
Comments