我是C ++ 11的新手。我正在编写以下递归lambda函数,但它不能编译。
#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:〜/书房/ 09C ++ / C ++ 0X /λ&GT; g ++ -std = c ++ 0x sum.cpp
sum.cpp:在lambda函数中:
sum.cpp:18:36:错误:'((<lambda(int, int)>*)this)-><lambda(int, int)>::sum
'不能用作函数
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);
};
有人可以点亮这个吗?
答案 0 :(得分:147)
考虑自动版本与完全指定类型版本之间的区别。 auto 关键字从它初始化的任何内容中推断出它的类型,但是你初始化它需要知道它的类型是什么(在这种情况下,lambda闭包需要知道它捕获的类型) 。鸡蛋和鸡蛋问题。
另一方面,完全指定的函数对象的类型不需要“知道”有关分配给它的内容的任何内容,因此lambda的闭包同样可以完全了解它捕获的类型。
考虑对代码的这种轻微修改,它可能更有意义:
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);
};
显然,这不适用于自动。递归lambda函数运行得非常好(至少它们在MSVC中,我对它们有经验),只是它们与类型推断并不真正兼容。
答案 1 :(得分:46)
诀窍是将lambda实现作为参数提供给自身,而不是通过捕获。
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
答案 2 :(得分:25)
使用C ++ 14,现在很容易制作一个有效的递归lambda而不必承担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);
}
});
答案 3 :(得分:21)
我有另一种解决方案,但只能使用无状态lambda:
void f()
{
static int (*self)(int) = [](int i)->int { return i>0 ? self(i-1)*i : 1; };
std::cout<<self(10);
}
这里的诀窍是lambdas可以访问静态变量,你可以将无状态变量转换为函数指针。
您可以将它与标准lambdas一起使用:
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中的工作
答案 4 :(得分:10)
可以以递归方式调用lambda函数。你唯一需要做的就是通过函数包装器引用它,以便编译器知道它的返回和参数类型(你不能捕获一个变量 - lambda本身 - 尚未定义)
function<int (int)> f;
f = [&f](int x) {
if (x == 0) return 0;
return x + f(x-1);
};
printf("%d\n", f(10));
请注意不要超出包装f的范围。
答案 5 :(得分:8)
要在不使用外部类和函数(如vowels := map[rune]rune{
'a': '3',
'e': '2',
}
s = strings.Map(func(r rune) rune {
if u, ok := vowels[r]; ok {
return u
}
return r
}, s)
或定点组合器)的情况下使lambda递归,可以在C ++ 14中使用以下构造(live example):
std::function
打印:
#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}}});
}
注意,应明确指定lambda的结果类型。
答案 6 :(得分:6)
我使用std::function<>
捕获方法运行了比较递归函数和递归lambda函数的基准。在clang 4.1版上启用完全优化后,lambda版本的运行速度明显变慢。
#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<>
本身的性能可能是直接调用减速与直接调用函数的原因,至少当lambda捕获太大而无法放入某些库时 - 优化空间std::function
用于小型仿函数(我想有点像各种短字符串优化?)。
答案 7 :(得分:1)
这是一个稍微简单的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';
}
}
答案 8 :(得分:0)
C ++ 14: 这是一个递归的匿名无状态/无捕获通用lambda集 输出1,20的所有数字
string image = Constants.ImageName;
if (image != "NONE")
{
Directory.CreateDirectory(Path.GetDirectoryName(DestinationDir + "\\" + image));
File.Copy(SourceDir + "\\" + image, DestinationDir + "\\" + image, true);
}
如果我理解正确,这是使用Y-combinator解决方案
这是sum(n,m)版本
([](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);
答案 9 :(得分:0)
这是基于@Barry提出的Y组合器解决方案的精炼版本。
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.
});
它与OCaml中的let rec
关键字相似,尽管不同。
答案 10 :(得分:-1)
以下是OP的最终答案。无论如何,Visual Studio 2010不支持捕获全局变量。而且您不需要捕获它们,因为全局变量可以通过define全局访问。以下答案改为使用局部变量。
#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;
}
答案 11 :(得分:-2)
您正在尝试捕获您正在定义的变量(总和)。这不可能是好事。
我不认为真正的自递归C ++ 0x lambdas是可能的。但是你应该能够捕获其他的lambda。
答案 12 :(得分:-2)
这个答案不如Yankes的那个,但是,它仍然在这里:
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);
答案 13 :(得分:-3)
你需要一个定点组合器。请参阅this。
或查看以下代码:
//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;
}