Show Lecture.LambdaFunctions as a slide show.
CS253 Lambda Functions
Lambda
λ
This is the Greek letter lambda.
In C++, it refers to an anonymous function, an unnamed function,
a function literal.
Functions
- Functions are not first-class objects in C++.
- You can’t have a variable of type “function”, or even of type
“function taking no arguments and returning int”.
- You can’t copy a function.
- However, you can have a pointer to a function.
A function
Here’s a boring ordinary function:
bool odd(int n) { return n & 0b1; }
int main() {
cout << odd(42) << ' ' << odd(43);
}
0 1
The 0b
prefix indicates a binary constant. Sure, 1
, 01
, or
0x01
would also work, but 0b1
stresses that it’s a bitmask.
Boolean values are displayed as 0
and 1
by default, unless you
use cout << boolalpha
.
Pointer to a function
bool odd(int n) { return n & 0b1; }
int main() {
bool (*p)(int) = odd;
cout << (*p)(42) << ' ' << p(43);
}
0 1
p
is a variable of type “pointer to function taking a int
and returning a bool”.
- Or,
p
is a pointer to a function taking a int and
returning a bool.
- So,
*p
is a function that takes a int and returns a bool.
- Some people (not me!) find such declarations
hard to understand.
- For convenience,
*
is optional when calling the function.
auto is your friend
bool odd(int n) { return n & 0b1; }
int main() {
auto p = odd;
cout << (*p)(42) << ' ' << p(43);
}
0 1
🧙
Even though C++ wizards understand bool (*p)(int) = odd;
just fine,
we realize that auto sure made that declaration easier!
There must be a better way.
- It seems a shame to have to name the function
odd
.
- Imagine if numbers worked that way, always having names:
cout << 355/113.0; // easy as
3.14159
int numerator=355;
float denominator=113.0;
cout << numerator/denominator; // tedious
3.14159
- It’s convenient to have unnamed numeric constants.
- If only we could have unnamed functions …
Trailing return type
There are two ways to declare a function return type:
- Standard declaration, with the return type first,
before the arguments are declared.
- Trailing return type, with an auto placeholder,
then the arguments, so we can use the arguments to
figure out the return type in a template.
bool odd_and(int n) { return n & 0b1; }
auto odd_mod(int n) -> bool { return n % 2 != 0; }
int main() {
cout << odd_and(12) << ' ' << odd_mod(13);
}
0 1
Patience—neither of these are lambda functions,
because they have names.
Deduced Return Type
If the trailing return type is not given, it is deduced:
bool odd_and(int n) { return n & 0b1; }
auto odd_mod(int n) { return n % 2 != 0; }
int main() {
cout << odd_and(12) << ' ' << odd_mod(13);
}
0 1
This use of auto does not indicate any sort of run-time
flexibility. Instead, it instructs the compiler to figure it out
at compile time, the same as auto answer=42;
figures out the
type of answer
from the type of 42
. In neither case is there
any run-time flexibility.
The return type of odd_mod
is not auto, it’s the actual
type that is returned.
Deduced Return Type
However, the return type must be unambiguous:
auto delta(bool flag) {
if (flag)
return 5;
return 6.7; // 🦡
}
int main() {
cout << delta(true);
}
c.cc:4: error: inconsistent deduction for auto return type: ‘int’ and then
‘double’
λ-expressions
auto p = [](int n) -> bool { return n & 0b1; };
cout << p(42) << ' ' << p(43);
0 1
[](int n) -> bool { return n & 0b1; }
is a λ-expression
[]
is the capture-specification
- Stuff can go inside
[]
, but we’ll ignore that.
- Think of it as “here comes a λ-function”.
(int n)
is the argument list
-> bool
specifies the trailing return type
- optional, if the compiler can figure it out
{ return n & 0b1; }
is the body
- I often forget the
;
at the end of the first line.
It’s an assignment statement, and so ends with a semicolon.
λ-expressions
The return-type may be deduced, if omitted:
auto p = [](int n) { return n & 0b1; };
cout << p(42) << ' ' << p(43);
0 1
What is the return-type of the lambda-expression pointed to by p
?
n
is an int, 0b1
is an int, so n & 0b1
is an int.
- Therefore, the return-type is int.
- An int works as well as a bool in this context.
- Before C had bool, it used int as a boolean type.
Generic λ-expressions
Even the arguments can be auto:
auto twice = [](auto v) { return v + v; };
cout << twice(3) << '\n'
<< twice(2.34) << '\n'
<< twice("Jack"s) << '\n'
<< twice('!') << '\n';
6
4.68
JackJack
66
- This is radically different than using auto as the return value.
- Instead, this is more like a templated function, in that it defines a
family of functions, taking different argument types.
+
works very differently for the different argument types: int,
double, and char do addition, whereas std::string concatenates
strings.
No arguments
If no arguments are needed, the ()
can be omitted:
auto unique_id = [] { return getpid(); };
cout << unique_id();
874422
Of course, this example could be accomplished with a simple pointer:
auto unique_id = getpid; // no parens
cout << unique_id();
874423
Use
- Well, this is all very impressive, but what good is it?
- Surely, it’s easier just to use regular functions.
- It’s more general than bind(), which is the older way of doing this.
- Functors and λ-expressions are useful with algorithms.