15.
C++ advanced (III): Functors and
Lambda
Generic Programming: higher order functions
414
Functors: Motivation
A simple output filter
template <typename T, typename Function>
void filter(const T& collection, Function f){
for (const auto& x: collection)
if (f(x)) std::cout << x << " ";
std::cout << "\n";
}
Question: when does a call of the filter template function work?
415
Functors: Motivation
A simple output filter
template <typename T, typename Function>
void filter(const T& collection, Function f){
for (const auto& x: collection)
if (f(x)) std::cout << x << " ";
std::cout << "\n";
}
Question: when does a call of the filter template function work?
Answer: works if the first argument offers an iterator and if the second argu-
ment can be applied to elements of the iterator with a return value that can
be converted to bool.
415
Functors: Motivation
template <typename T, typename Function>
void filter(const T& collection, Function f);
template <typename T>
bool even(T x){
return x % 2 == 0;
}
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
filter(a,even<int>); // output: 2,4,6,16
416
Context and Overview
Recent C++-lecture: make code parametric on the data it operates
Pair<T> for element types T
print<C> for iterable containers C
Now: make code parameteric on a (part of) the algorithm
filter(container, predicate)
apply(signal, transformation/filter)
We learn about
Higher-order functions: fucntions that take functions as arguments
Functors: objects with overloaded function operator ().
Lambda-Expressions: anonymous functors (syntactic sugar)
Closures: lambdas that capture their environment
417
Functors: Motivation
template <typename T, typename Function>
void filter(const T& collection, Function f){
for (const auto& x: collection)
if (f(x)) std::cout << x << " ";
std::cout << "\n";
}
Requirements on f: must be callable/applicable (...)
f must be a kind of function ⇒ filter is a function that takes a
function as argument
A function taking (or returning) a function is called a higher order
function
Higher order functions are parameteric in their functionality (or they
generate functions)
418
What if...
the filter should be more flexible:
template <typename T, typename function>
void filter(const T& collection, function f);
template <typename T>
bool largerThan(T x, T y){
return x > y;
}
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
int val = 8;
filter(a,largerThan<int>( ? ,val));
419
What if...
the filter should be more flexible:
template <typename T, typename function>
void filter(const T& collection, function f);
template <typename T>
bool largerThan(T x, T y){
return x > y;
}
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
int val = 8;
filter(a,largerThan<int>( ? ,val)); (No, this does not exist)
419
Functor: Object with Overloaded Operator ()
class GreaterThan{
int value; // state
public:
GreaterThan(int x):value{x}{} A Functor is a callable ob-
ject. Can be understood as
bool operator() (int par) const { a stateful function.
return par > value;
}
};
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
int value=8;
filter(a,GreaterThan(value)); // 9,11,16,19
420
Functor: object with overloaded operator ()
template <typename T>
class GreaterThan{
T value;
public:
GreaterThan(T x):value{x}{} (this also works with a tem-
plate, of course)
bool operator() (T par) const{
return par > value;
}
};
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
int value=8;
filter(a,GreaterThan<int>(value)); // 9,11,16,19
421
Observations
Need to give the predicate a name
template <typename T> Often unnecessary, many are
class GreaterThan{ unsed only once
T value;
public: Descriptive names not
GreaterThan(T x):value{x}{} always possible
Distance (in the code)
bool operator() (T par) const{ between declaration and use
return par > value; Overhead: stateful predicates as
}
functors
};
cumbersome for what is
ultimately only par > value
422
The same with a Lambda-Expression
Anonymous functions with lambda-expressions:
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
int value=8;
filter(a, [value](int x) {return x > value;} );
That is just syntactic sugar, from which the compiler generates a suitable
functor.
423
Interlude: Sorting with Custom Comparator
std::sort is generic
in the iterator-type
in the values iterated over
in the used comparator
std::sort (v.begin(), v.end(), std::less());
The comparator returns true if the elements are ordered as wished.
424
Sorting by Different Order
// pre: i >= 0
// post: returns sum of digits of i
int q(int i){
int res =0;
for(;i>0;i/=10)
res += i % 10;
return res;
}
std::vector<int> v {10,12,9,7,28,22,14};
std::sort (v.begin(), v.end(),
[] (int i, int j) { return q(i) < q(j);}
);
425
Sorting by Different Order
// pre: i >= 0
// post: returns sum of digits of i
int q(int i){
int res =0;
for(;i>0;i/=10)
res += i % 10;
return res;
}
std::vector<int> v {10,12,9,7,28,22,14};
std::sort (v.begin(), v.end(),
[] (int i, int j) { return q(i) < q(j);}
);
Now v =
425
Sorting by Different Order
// pre: i >= 0
// post: returns sum of digits of i
int q(int i){
int res =0;
for(;i>0;i/=10)
res += i % 10;
return res;
}
std::vector<int> v {10,12,9,7,28,22,14};
std::sort (v.begin(), v.end(),
[] (int i, int j) { return q(i) < q(j);}
);
Now v =10, 12, 22, 14, 7, 9, 28 (sorted by sum of digits)
425
Lambda-Expressions in Detail
[value] (int x) ->bool {return x > value;}
capture parameters return statement
type
426
Closure
[value] (int x) ->bool {return x > value;}
Lambda expressions evaluate to a temporary object – a closure
The closure retains the execution context of the function - the captured
objects.
Lambda expressions can be implemented as functors.
427
Simple Lambda Expression
[]()->void {std::cout << "Hello World";}
428
Simple Lambda Expression
[]()->void {std::cout << "Hello World";}
call:
[]()->void {std::cout << "Hello World";}();
428
Simple Lambda Expression
[]()->void {std::cout << "Hello World";}
call:
[]()->void {std::cout << "Hello World";}();
assignment:
auto f = []()->void {std::cout << "Hello World";};
428
Minimal Lambda Expression
[]{}
Return type can be inferred if no or only one return statement is
present.18
[]() {std::cout << "Hello World";}
If no parameters and no explicit return type, then () can be omitted.
[]{std::cout << "Hello World";}
[...] can never be omitted.
18
Since C++14 also several returns possible, provided that the same return type is
deduced
429
Examples
[](int x, int y) {std::cout << x * y;} (4,5);
Output:
430
Examples
[](int x, int y) {std::cout << x * y;} (4,5);
Output: 20
430
Examples
int k = 8;
auto f = [](int& v) {v += v;};
f(k);
std::cout << k;
Output:
431
Examples
int k = 8;
auto f = [](int& v) {v += v;};
f(k);
std::cout << k;
Output: 16
431
Examples
int k = 8;
auto f = [](int v) {v += v;};
f(k);
std::cout << k;
Output:
432
Examples
int k = 8;
auto f = [](int v) {v += v;};
f(k);
std::cout << k;
Output: 8
432
Commonly Used: std::foreach
Common task: iterate over a container and do something with each
element
for (auto& name: names) {
std::cout << name << ’ ’;
}
This pattern is typically implemented as higher-order function for_each
for_each(names, [](auto name) {std::cout << name << " ";});
433
Sum of Elements – Old School
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
int sum = 0;
for (auto x: a)
sum += x;
std::cout << sum << std::endl; // 83
434
Sum of Elements – Old School
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
int sum = 0;
....
std::cout << sum << std::endl; // 83
Task: increase sum for each call in for_each
Problem: for_each requires accesst to the context (here sum)
Other example filter: store each element of a vector into a different
vector according to some condition.
435
Sum of Elements – with Functor
template <typename T>
struct Sum{
T value = 0;
void operator() (T par){ value += par; }
};
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
Sum<int> sum;
// for_each copies sum: we need to copy the result back
sum = std::for_each(a.begin(), a.end(), sum);
std::cout << sum.value << std::endl; // 83
436
Sum of Elements – with Functor
template <typename T>
struct Sum{
T value = 0;
void operator() (T par){ value += par; }
};
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
Sum<int> sum;
// for_each copies sum: we need to copy the result back
sum = std::for_each(a.begin(), a.end(), sum);
std::cout << sum.value << std::endl; // 83
Ok: solves the problem but does not operate on the sum variable
436
Sum of Elements – with References
template <typename T>
struct SumR{
T& value;
SumR (T& v):value{v} {}
void operator() (T par){ value += par; }
};
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
int s=0;
SumR<int> sum{s};
// cannot (and do not need to) assign to sum here
std::for_each(a.begin(), a.end(), sum);
std::cout << s << std::endl; // 83
437
Of course this works, very similarly, using pointers
Sum of Elements – with Λ
std::vector<int> a {1,2,3,4,5,6,7,9,11,16,19};
int s=0;
std::for_each(a.begin(), a.end(), [&s] (int x) {s += x;} );
std::cout << s << std::endl;
438
Capture – Lambdas
For Lambda-expressions the capture list determines the context accessible
Syntax:
[x]: Access a copy of x (read-only)
[&x]: Capture x by reference
[&x,y]: Capture x by reference and y by value
[&]: Default capture all objects by reference in the scope of the lambda
expression
[=]: Default capture all objects by value in the context of the
Lambda-Expression
439
Capture – Lambdas
std::vector<int> v = {1,2,4,8,16};
int elements=0;
int sum=0;
std::for_each(v.begin(), v.end(),
[&] (int k) {sum += k; elements++;} // capture all by reference
)
std::cout << "sum=" << sum << " elements=" << elements << std::endl;
Output:
440
Capture – Lambdas
std::vector<int> v = {1,2,4,8,16};
int elements=0;
int sum=0;
std::for_each(v.begin(), v.end(),
[&] (int k) {sum += k; elements++;} // capture all by reference
)
std::cout << "sum=" << sum << " elements=" << elements << std::endl;
Output: sum=31 elements=5
440
Capture – Lambdas
template <typename T>
void sequence(vector<int> & v, T done){
int i=0;
while (!done()) v.push_back(i++);
}
vector<int> s;
sequence(s, [&] {return s.size() >= 5;} )
now v =
441
Capture – Lambdas
template <typename T>
void sequence(vector<int> & v, T done){
int i=0;
while (!done()) v.push_back(i++);
}
vector<int> s;
sequence(s, [&] {return s.size() >= 5;} )
now v = 0 1 2 3 4
441
Capture – Lambdas
template <typename T>
void sequence(vector<int> & v, T done){
int i=0;
while (!done()) v.push_back(i++);
}
vector<int> s;
sequence(s, [&] {return s.size() >= 5;} )
now v = 0 1 2 3 4
The capture list refers to the context of the lambda expression.
441
Capture – Lambdas
When is the value captured?
int v = 42;
auto func = [=] {std::cout << v << "\n"};
v = 7;
func();
Output:
442
Capture – Lambdas
When is the value captured?
int v = 42;
auto func = [=] {std::cout << v << "\n"};
v = 7;
func();
Output: 42
Values are assigned when the lambda-expression is created.
442
Capture – Lambdas
(Why) does this work?
class Limited{
int limit = 10;
public:
// count entries smaller than limit
int count(const std::vector<int>& a){
int c = 0;
std::for_each(a.begin(), a.end(),
[=,&c] (int x) {if (x < limit) c++;}
);
return c;
}
};
443
Capture – Lambdas
(Why) does this work?
class Limited{
int limit = 10;
public:
// count entries smaller than limit
int count(const std::vector<int>& a){
int c = 0;
std::for_each(a.begin(), a.end(),
[=,&c] (int x) {if (x < limit) c++;}
);
return c;
}
};
The this pointer is implicitly copied by value
443
Capture – Lambdas
struct mutant{
int i = 0;
void action(){ [=] {i=42;}();}
};
mutant m;
m.action();
std::cout << m.i;
Output:
444
Capture – Lambdas
struct mutant{
int i = 0;
void action(){ [=] {i=42;}();}
};
mutant m;
m.action();
std::cout << m.i;
Output: 42
The this pointer is implicitly copied by value
444
Lambda Expressions are Functors
[x, &y] () {y = x;}
can be implemented as
unnamed {x,y};
with
class unnamed {
int x; int& y;
unnamed (int x_, int& y_) : x (x_), y (y_) {}
void operator () () {y = x;}
};
445
Lambda Expressions are Functors
[=] () {return x + y;}
can be implemented as
unnamed {x,y};
with
class unnamed {
int x; int y;
unnamed (int x_, int y_) : x (x_), y (y_) {}
int operator () () const {return x + y;}
};
446
Polymorphic Function Wrapper std::function
#include <functional>
int k= 8;
std::function<int(int)> f;
f = [k](int i){ return i+k; };
std::cout << f(8); // 16
can be used in order to store lambda expressions.
Other Examples
std::function<int(int,int)>; std::function<void(double)> ...
http://en.cppreference.com/w/cpp/utility/functional/function
447
Example
template <typename T>
auto toFunction(std::vector<T> v){
return [v] (T x) -> double {
int index = (int)(x+0.5);
if (index < 0) index = 0;
if (index >= v.size()) index = v.size()-1;
return v[index];
};
}
448
Example
auto Gaussian(double mu, double sigma){
return [mu,sigma](double x) {
const double a = ( x - mu ) / sigma;
return std::exp( -0.5 * a * a );
};
}
template <typename F, typename Kernel>
auto smooth(F f, Kernel kernel){
return [kernel,f] (auto x) {
// compute convolution ...
// and return result
};
}
449
Example
std::vector<double> v {1,2,5,3};
auto f = toFunction(v);
auto k = Gaussian(0,0.1);
auto g = smooth(f,k);
450
Conclusion
Higher-order functions are parametric in their functionality: more
flexible → more widely applicable → more code reuse
Being able to pass around functions means being able to pass around
entire computations.
Lambda expressions facilitate the use of “one-off” functions, which are
often used in combination with higher-order functions.
Returning lambdas enables implementing function generators, that can
generate whole families of functions.
In C++, lambda expressions desugare into function objects (functors).
Higher-order functions and lambdas are an important, and nowadays
mainstream, building block of the functional programming paradigm.
451