Lambda expressions
==================
since C++11
extended in C++14
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
v.push_back(i);
for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
cout << endl;
return 0;
}
$ g++ l.cpp
$ ./a.out
0 1 2 3 4 5 6 7 8 9
$
[] lambda-introducer
(int n) lambda parameter declaration
{ ... } compound statement
Lambda expressons define classes and construct objects.
The program above is equivalent to:
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
struct LambdaFunctor
{
void operator() (int n) const { cout << n << " "; }
};
int main()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
v.push_back(i);
for_each(v.begin(), v.end(), LambdaFunctor());
cout << endl;
return 0;
}
Definitions from Meyers' Effective Modern C++:
Lambda expression: [](int n) { cout << n << " "; }
Closure: runtime object created from lambda. May hold captured variables.
Closure class: the type of the closure object.
The expression can contain multiply statements
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
v.push_back(i);
for_each(v.begin(), v.end(), [](int n) {
cout << n ;
if ( n % 2 )
cout << ":odd ";
else
cout << ":even ";
});
cout << endl;
return 0;
}
0:even 1:odd 2:even 3:odd 4:even 5:odd 6:even 7:odd 8:even 9:odd
If the lambda expession has return value, than the return type
is automatically deduced from that expression:
int main()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
v.push_back(i);
deque<int> d;
transform(v.begin(), v.end(), front_inserter(d), [](int n) {return n*n;} );
for_each(d.begin(), d.end(), [](int n) { cout << n << " "; });
cout << endl;
return 0;
}
81 64 49 36 25 16 9 4 1 0
Sometimes it is not easy to deduce the return type. We can explicitly
specify the return type.
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
#include <deque>
using namespace std;
int main()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
v.push_back(i);
deque<double> d;
transform(v.begin(), v.end(), front_inserter(d),
[](int n) -> double { return n / 2.0; } );
for_each(d.begin(), d.end(), [](double n) { cout << n << " "; });
cout << endl;
return 0;
}
4.5 4 3.5 3 2.5 2 1.5 1 0.5 0
-> double is the lambda return type clause
(it is on the right, because [] should be on left for parsing)
[] means, that the lambda is "stateless", but we can "capture" local variables
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
v.push_back(i);
int x = 0;
int y = 0;
cin >> x >> y;
v.erase( remove_if(v.begin(),
v.end(),
[x,y](int n) { return x < n && n < y; }
),
v.end()
);
for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
cout << endl;
return 0;
}
3 6
0 1 2 3 6 7 8 9
We passed the x and y parameters by value. Basically it is equivalent:
struct LambdaFunctor
{
public:
LambdaFunctor(int a, int b) : m_a(a), m_b(b) { }
bool operator()(int n) const { return m_a < n && n < m_b; }
private:
int m_a;
int m_b;
};
v.erase( remove_if(v.begin(),v.end(),LambdaFunctor(x,y)), v.end());
The x and y parameters are copied and being stored in the function object.
We cannot modify the captured values because the operator() in functor is
const. It is a "real" copy, therefore the modification of x and y is not
reflected inside the lambda.
[=] default-capture lambda introducer. Captures all locals by value.
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
v.push_back(i);
int x = 0;
int y = 0;
cin >> x >> y;
v.erase( remove_if(v.begin(),
v.end(),
[=](int n) { return x < n && n < y; }
),
v.end()
);
for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
cout << endl;
return 0;
}
3 6
0 1 2 3 6 7 8 9
Variables captured by value by lambda are really stored in the object:
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
v.push_back(i);
int x = 0;
int y = 0;
auto f = [=](int n) { auto sz = v.size(); return x < n && n < y; };
v.erase( remove_if(v.begin(),
v.end(),
f
),
v.end()
);
for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
cout << endl;
cout << "size of f = " << sizeof(f) << endl;
return 0;
}
$ ./a.out
0 1 2 3 4 5 6 7 8 9
size of f = 32
Lambda expressions are const by default but we can manage to modify x and y.
Note, that modification of prev does not update local prev variable.
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
v.push_back(i);
int prev = 0;
for_each(v.begin(), v.end(), [=](int& r) mutable {
const int oldr = r;
r *= prev;
prev = oldr;
});
for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
cout << "prev = " << prev << endl;
return 0;
}
0 0 2 6 12 20 30 42 56 72 prev = 0
Capture by reference
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
v.push_back(i);
int prev = 0;
for_each(v.begin(), v.end(), [&prev](int& r) mutable {
const int oldr = r;
r *= prev;
prev = oldr;
});
for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
cout << "prev = " << prev << endl;
return 0;
}
0 0 2 6 12 20 30 42 56 72 prev = 9
[&] Capture everything by reference
[=, &x, &y] Capture everything by value but x and y which capured by reference
[&, x, y] Capture everything by reference but x and y which capured by value
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
struct X
{
int s;
vector<int> v;
void print() const
{
for_each(v.begin(), v.end(), [](int n) { cout << n*s << " "; });
}
};
int main()
{
X x;
x.s = 2;
for(int i = 0; i < 10; ++i)
x.v.push_back(i);
x.print();
return 0;
}
$ g++ l10.cpp
l10.cpp: In lambda function:
l10.cpp:15:60: error: ‘this’ was not captured for this lambda function
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
struct X
{
int s;
vector<int> v;
void print() const
{
for_each(v.begin(), v.end(), [this](int n) { cout << n*s << " "; });
}
};
int main()
{
X x;
x.s = 2;
for(int i = 0; i < 10; ++i)
x.v.push_back(i);
x.print();
return 0;
}
- this always captured by value
- you can implicitly capture this with [=]
- capturing this can be dangerous:
(not smart) pointer, pointed object lifetime may already finished...
global variables, static data members could be used
but they are not captured!
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
int ii = 5;
int main()
{
vector<int> v;
for(int i = 0; i < 10; ++i)
v.push_back(i);
int x = 0;
int y = 10;
auto f = [=](int n) { if (x < n-ii && n-ii < y)
cout << n << " ";
};
for_each(v.begin(), v.end(), f);
cout << endl;
ii = 1;
for_each(v.begin(), v.end(), f);
cout << endl;
return 0;
}
$ ./a.out
6 7 8 9
2 3 4 5 6 7 8 9
We can define "nullary" lambdas
#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
int i = 0;
generate_n(back_inserter(v), 10, [&] { return i++; } );
for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
return 0;
}
0 1 2 3 4 5 6 7 8 9
Or you can write [&]() { return i++; }
Lambdas can be stored in std::functional
#include <algorithm>
#include <functional>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
void doit(const vector<int>& v, const function<void (int)>& f)
{
for_each(v.begin(), v.end(), f);
cout << endl;
}
int main()
{
vector<int> v;
int i = 0;
generate_n(back_inserter(v), 10, [&] { return i++; } );
doit(v, [](int n) { cout << n << " "; });
const function<void (int)>& ff = [](int n) { cout << n << " "; };
doit(v, ff);
return 0;
}
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
int i = some_default_value;
if(someConditionIstrue)
{
i =
}
int x = i;
i = 10;
const int i = [&]{
int i = some_default_value;
if(someConditionIstrue)
{
i =
}
return i;
} ();
Generalized lambdas in C++14:
=============================
auto L = [](const auto& x, auto& y){ return x + y; };
means:
struct
{
template <typename T, typename U>
auto operator()(const T& x, U& y) const
{
return x + y;
}
} L;
Init capture or Generalized lambda capture, since C++14
=======================================================
auto up = std::make_unique<X>();
auto func = [up = std::move(up)] { return up->f(); }
^ ^ ^
| | |
this is called this is here we
inside the lambda captured use inside
or:
auto func = [up = std::make_unique<X>()] { return up->f(); }
auto f = [](auto x) { return func(func2(x)); }
class UnnamedFunctor
{
public:
template <typename T>
auto operator()(T x) cont
{
return func(func2(x));
}
};
Perfect forwarding:
class UnnamedFunctor
{
public:
template <typename T>
auto operator()(T&& x) cont
{
return func(func2(std::forward<T&&>x));
}
};
auto f = [](auto&& x) { return func(func2(std::forward<???>(x))); }
auto f = [](auto&& x) { return func(func2(std::forward<decltype(x)>(x))); }
auto f = [](auto&&... params)
{
return func(func2(std::forward<decltype(params)>(params)... ));
}
Be care with capturing this
===========================
#include <algorithm>
#include <functional>
#include <memory>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;
std::function<void (int)> f;
struct X
{
X(int i) : ii(i) {}
int ii;
void addLambda()
{
f = [=](int n) { if (n == ii) cout << n;
else cout << ii;
};
}
};
int main()
{
{
std::unique_ptr<X> up = std::make_unique<X>(4);
up->addLambda();
f(4);
}
f(4);
return 0;
}