Show Lecture.Functors as a slide show.
Please pronounce carefully.
Consider this class:
class Foo { public: int thrice(int n) { return n*3; } }; Foo f; cout << f.thrice(42) << '\n';
126
int thrice(int n)
int
thrice
(int n)
Let’s use the ~
operator, which is defined as bit-wise complement
for integral types:
class Foo { public: const char *operator~() { return "hello"; } }; Foo f; cout << ~f << '\n';
hello
const char *operator~()
const char *
operator~
()
~Foo
.
Let’s use the ()
operator, alias the “function call” operator:
class Foo { public: double operator()(int i, float f) { return i+f; } }; Foo f; cout << f(3, 1.2) << '\n';
4.2
double operator()(int i, float f)
double
operator()
(int i, float f)
😒
Let’s write a functor that returns its argument+1:
class BiggerBy1 { public: int operator()(int i) { return i+1; } }; BiggerBy1 b; cout << b(17) << '\n';
18
int operator()(int i)
int
operator()
(int i)
It’s easy to change that to increment by two, instead:
class BiggerBy2 { public: int operator()(int i) { return i+2; } }; BiggerBy2 b; cout << b(17) << '\n';
19
However, it would be tedious to have to write many such functors for all the different increment values we might want to use.
Let’s give the functor a ctor argument:
class Bigger { const int increment; public: Bigger(int inc) : increment(inc) { } int operator()(int i) { return i+increment; } }; Bigger b(4), c(12); cout << b(17) << ' ' << b(100) << '\n' << b(1000) << ' ' << c(2000) << '\n';
21 104 1004 2012
The parentheses in b(4)
are very different than the
parentheses in b(17)
.
Another use of memory:
class Queue { int previous=0; public: int operator()(int n) { const auto save=previous; previous=n; return save; } }; Queue q; cout << q(12) << '\n'; cout << q(100) << '\n'; cout << q(42) << '\n';
0 12 100
Why does this fail?
class Queue { int previous=0; public: int operator()(int n) { const auto save=previous; previous=n; return save; } }; Queue q; cout << q(12) << '\n' << q(100) << '\n' << q(42) << '\n';
0 12 100
Multi-element queue:
class Queue { deque<int> store; public: Queue(int init_size) : store(init_size) { } int operator()(int n) { store.push_back(n); const auto f = store.front(); store.pop_front(); return f; } }; Queue q(3); for (int i=10; i<20; i++) cout << q(i) << '\n';
0 0 0 10 11 12 13 14 15 16
class Total { int sum=0; public: int operator()(int n) { return sum += n; } }; Total t; for (int i=0; i<=10; i++) cout << "Add " << i << " yielding " << t(i) << '\n';
Add 0 yielding 0 Add 1 yielding 1 Add 2 yielding 3 Add 3 yielding 6 Add 4 yielding 10 Add 5 yielding 15 Add 6 yielding 21 Add 7 yielding 28 Add 8 yielding 36 Add 9 yielding 45 Add 10 yielding 55
set
know the sorting order?
int
and string
, but what
about class Student
?
int
, you might want to sort in a different order.
set
is like that. It knows the mechanics of sorting,
but it doesn’t know what order to use. You need to help it.
set
with a comparison functor.
set
code makes comparisons using the functor.
true
iff the first item should come
before the second item.
Implicit sorting criterion:
set<int> s = {31, 41, 59, 26, 53, 58, 97, 93, 23, 84}; for (auto v : s) cout << v << ' ';
23 26 31 41 53 58 59 84 93 97
Explicit sorting criterion:
set<int, less<int> > s = {31, 41, 59, 26, 53, 58, 97, 93, 23, 84}; for (auto v : s) cout << v << ' ';
23 26 31 41 53 58 59 84 93 97
What’s this > >
folderol?
Use greater<int>
to sort from biggest to smallest:
set<int, greater<int>> s = {31, 41, 59, 26, 53, 58, 97, 93, 23, 84}; for (auto v : s) cout << v << ' ';
97 93 84 59 58 53 41 31 26 23
Use a comparison functor that behaves the same as less<int>
:
struct compare { // 😱😱struct‽😱😱 bool operator()(int a, int b) const { return a < b; } }; set<int, compare> s = {31, 41, 59, 26, 53, 58, 97, 93, 23, 84}; for (auto v : s) cout << v << ' ';
23 26 31 41 53 58 59 84 93 97
Use a comparison functor that does a two-level comparison, like last name/first name.
struct compare { bool operator()(int a, int b) const { int da = abs(a-50), db = abs(b-50); if (da != db) return da < db; // Primary sort: distance from 50 return a < b; // Secondary sort: value of number } }; set<int, compare> s = {31, 41, 59, 26, 53, 58, 97, 93, 23, 84}; for (auto v : s) cout << v << ' ';
53 58 41 59 31 26 23 84 93 97
Another way to write a two-level comparison functor:
struct compare { bool operator()(int a, int b) const { int da = abs(a-50), db = abs(b-50); return (da != db) ? da < db : a < b; } }; set<int, compare> s = {31, 41, 59, 26, 53, 58, 97, 93, 23, 84}; for (auto v : s) cout << v << ' ';
53 58 41 59 31 26 23 84 93 97
This sorts by different criteria:
struct compare { bool operator()(int a, int b) const { int last_a = a%10, last_b = b%10; if (last_a != last_b) return last_a < last_b; // Primary sort: by last digit return a < b; // Secondary sort: value of number } }; set<int, compare> s = {31, 41, 59, 26, 53, 58, 97, 93, 23, 84}; for (auto v : s) cout << v << ' ';
31 41 23 53 93 84 26 97 58 59
struct student { int id; string name; float gpa; }; struct compare { bool operator()(const student &a, const student &b) const { return (a.gpa != b.gpa) ? a.gpa > b.gpa : a.id < b.id; } }; set<student,compare> s = { {81234567, "Jack Applin", 3.9}, {82828282, "Craig Partridge", 0.5}, {84444444, "Homecoming Queen", 2.2}, {83333333, "Joe Kolledge", 2.2}, }; for (const auto &v : s) cout << v.id << ' ' << v.gpa << ' ' << v.name << '\n';
81234567 3.9 Jack Applin 83333333 2.2 Joe Kolledge 84444444 2.2 Homecoming Queen 82828282 0.5 Craig Partridge
vector
, array
, and list
just go in the order you specify,
and so have no comparison functor.
unordered_set
needs only an equality comparison (and a hash
functor), so its comparison functor defaults to equal_to
,
not less
.
We’re going to build up to using functors and λ-expressions.
char embiggen(char c) { if ('a' <= c && c <= 'z') return c - 'a' + 'A'; else return c; } int main() { string name = "Beverly Hills Chihuahua"; for (char &c : name) // & for reference c = embiggen(c); cout << name << '\n'; }
BEVERLY HILLS CHIHUAHUA
Use a ternary expression, instead:
char embiggen(char c) { return ('a'<=c && c<='z') ? c-'a'+'A' : c; } int main() { string name = "Beverly Hills Chihuahua"; for (char &c : name) c = embiggen(c); cout << name << '\n'; }
BEVERLY HILLS CHIHUAHUA
Use the transform
algorithm, rather than an explicit loop:
char embiggen(char c) { return ('a'<=c && c<='z') ? c-'a'+'A' : c; } int main() { string name = "Beverly Hills Chihuahua"; string result = name; // Why? transform(name.begin(), name.end(), result.begin(), embiggen); cout << result << '\n'; }
BEVERLY HILLS CHIHUAHUA
This example uses the same buffer for input & output of transform
.
char embiggen(char c) { return ('a'<=c && c<='z') ? c-'a'+'A' : c; } int main() { string name = "Beverly Hills Chihuahua"; transform(name.begin(), name.end(), name.begin(), embiggen); cout << name << '\n'; }
BEVERLY HILLS CHIHUAHUA
This code uses an actual functor, as opposed to a function.
class embiggen { public: char operator()(char c) { return ('a'<=c && c<='z') ? c-'a'+'A' : c; } }; string name = "Beverly Hills Chihuahua"; embiggen biggifier; transform(name.begin(), name.end(), name.begin(), biggifier); cout << name << '\n';
BEVERLY HILLS CHIHUAHUA
Create a temporary functor object:
class embiggen { public: char operator()(char c) { return ('a'<=c && c<='z') ? c-'a'+'A' : c; } }; string name = "Beverly Hills Chihuahua"; transform(name.begin(), name.end(), name.begin(), embiggen()); cout << name << '\n';
BEVERLY HILLS CHIHUAHUA
Instead of a functor, a lambda-expression can also be used.
string name = "Beverly Hills Chihuahua"; transform(name.begin(), name.end(), name.begin(), [](char c){ return 'a'<=c && c<='z' ? c-'a'+'A' : c; } ); cout << name << '\n';
BEVERLY HILLS CHIHUAHUA
Don’t re-invent the wheel:
string name = "Beverly Hills Chihuahua"; for (char &c : name) c = toupper(c); cout << name << '\n';
BEVERLY HILLS CHIHUAHUA