Я новичок в C ++ 11. Я пишу следующую рекурсивную лямбда-функцию, но она не компилируется.
sum.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;
}
ошибка компиляции:
vimal @ linux-718q: ~ / Study / 09C ++ / c ++ 0x / lambda> g ++ -std = c ++ 0x sum.cpp
sum.cpp: В лямбда-функции: sum.cpp: 18: 36: error: ' ((<lambda(int, int)>*)this)-><lambda(int, int)>::sum
' не может использоваться как функция
версия gcc
gcc версия 4.5.0 20091231 (экспериментальная) (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);
};
Может кто-нибудь пролить свет на это?
mutable
делает ключевое слово?std::function<int(int,int)> sum = [&](int a, int b) {
Ответы:
Подумайте о разнице между автоматической версией и полностью указанной типовой версией. В авто выводит ключевое слово его тип от того, что он инициализированный, но что вы инициализирующего его потребности , чтобы знать , что его типа (в данном случае потребность закрытия лямбды знать типы это захватив). Что-то вроде проблемы с курицей и яйцом.
С другой стороны, полностью заданному типу функционального объекта не нужно «знать» что-либо о том, что ему присваивается, и поэтому лямбда-замыкание может точно так же быть полностью информировано о типах, которые он захватывает.
Рассмотрите эту небольшую модификацию вашего кода, и это может иметь больше смысла:
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, где у меня есть опыт работы с ними), просто они не совсем совместимы с выводом типов.
источник
auto
переменную в ее инициализаторе. тип автоматической переменной еще не известен, когда инициализатор обрабатывается.sum
другогоstd::function<int(int, int)>
, или спецификация C ++ просто не позаботилась об этом?Хитрость заключается в том, чтобы передать лямбда-реализацию себе в качестве параметра , а не путем захвата.
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/blog/2015/07/16/recursive-lambdas-in-c14/
Для этого действительно требуется C ++ 14, в то время как вопрос касается C ++ 11, но, возможно, интересен большинству.
std::function
Также возможно прохождение через , но это может привести к замедлению кода. Но не всегда. Посмотрите ответы на std :: function vs templateЭто не просто особенность C ++, это напрямую связано с математикой лямбда-исчисления. Из Википедии :
Lambda calculus cannot express this as directly as some other notations: all functions are anonymous in lambda calculus, so we can't refer to a value which is yet to be defined, inside the lambda term defining that same value. However, recursion can still be achieved by arranging for a lambda expression to receive itself as its argument value
источник
function<>
. Я не понимаю, почему кто-то предпочел бы это. Изменить: это, по-видимому, быстрее.error: use of ‘[...]’ before deduction of ‘auto’
- необходимо явно указать тип возвращаемого значения (с другой стороны, не нужно изменять).С C ++ 14 теперь довольно легко создать эффективную рекурсивную лямбду без дополнительных накладных расходов
std::function
, всего в нескольких строках кода (с небольшим изменением оригинала, чтобы предотвратить случайное копирование пользователем. ):с которой ваша первоначальная
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); } });
В C ++ 17 с CTAD мы можем добавить руководство по дедукции:
template <class F> y_combinator(F) -> y_combinator<F>;
Что устраняет необходимость во вспомогательной функции. Мы можем просто написать
y_combinator{[](auto self, ...){...}}
прямо.В C ++ 20 с CTAD для агрегатов руководство по дедукции не требуется.
источник
std::forward<decltype(sum)>(sum)
вместоsum
последней строчки.operator()
так что от пересылки ничего не выиграетsum
const
в случае, если предоставленный объект-функция не имеетconst
оператора вызова. И используйте SFINAE и вычислитеnoexcept
для обоих. Кроме того, в C ++ 17 больше нет необходимости в функции производителя.auto sum
копирует ... но копируетreference_wrapper
, что то же самое, что брать ссылку. Выполнение этого один раз в реализации означает, что ни одно из применений никогда не будет случайно скопировано.У меня есть другое решение, но работаю только с лямбдами без состояния:
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
источник
Вы можете вызвать саму лямбда-функцию рекурсивно. Единственное, что вам нужно сделать, это указать на нее через оболочку функции, чтобы компилятор знал ее возвращаемый тип и тип аргумента (вы не можете захватить переменную - саму лямбду - которая еще не определена) .
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
для библиотеки пространства, используемые для малых функторов. (Я думаю, вроде как различные оптимизации коротких строк?).источник
Вот усовершенствованная версия решения Y-комбинатора, основанная на предложении @Barry.
template <class F> struct recursive { F f; template <class... Ts> decltype(auto) operator()(Ts&&... ts) const { return f(std::ref(*this), std::forward<Ts>(ts)...); } template <class... Ts> decltype(auto) operator()(Ts&&... ts) { return f(std::ref(*this), std::forward<Ts>(ts)...); } }; template <class F> recursive(F) -> recursive<F>; auto const rec = [](auto f){ return recursive{std::move(f)}; };
Чтобы использовать это, можно сделать следующее
auto fib = rec([&](auto&& fib, int i) { // implementation detail omitted. });
Он похож на
let rec
ключевое слово в OCaml, но не то же самое.источник
Это немного более простая реализация оператора фиксированной точки, которая делает более очевидным, что именно происходит.
#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'; } }
источник
std::function
указателем на функцию (из ядер он будет работать только с нормальной функцией и лямбдами без состояния). Кстати,fib_nonr
следует принятьfixpoint<int,int>
, если вы используетеstd::function
его, требуя создания новой копии из*this
.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
источник
Вот окончательный ответ на OP. В любом случае 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; }
источник
Вы пытаетесь зафиксировать переменную (сумму), которую вы определяете. Это не может быть хорошо.
Я не думаю, что по-настоящему саморекурсивные лямбды C ++ 0x возможны. Однако у вас должна быть возможность захватывать другие лямбды.
источник
Этот ответ уступает ответу Янкса, но все же вот оно:
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);
источник
reinterpret_cast
. Вероятно, лучший способ в вашем случае - создать структуру, которая заменяетdp_type
. Он должен иметь полеfp_type
, может быть построен изfp_type
оператора()
с такими аргументами, какfp_type
. Это будет близко кstd::function
аргументу с самооценкой, но позволит.struct
также добавит дополнительный уровень косвенности. Пример работает, и приведение соответствует стандарту, я не знаю, для чего он-1
был.-1
я не знал, кто дал это вам, но я думаю, что это потому, чтоreinterpret_cast
следует использовать в крайнем случае.cast
Якобы гарантированно работать на C ++ 11 стандарта. Наstruct
мой взгляд, использование a может помешать использованию лямбда-объекта. В конце концов,struct
вы предлагаете функтор, использующий лямбда-объект.std::function
и у вас будет что-то близкое к тому, что я имел в виду. Вероятно, это будет иметь производительность, аналогичную вашему решению.Вам нужен комбинатор с фиксированной точкой. Смотрите это .
или посмотрите следующий код:
//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; }
источник