Show Lecture.Functors as a slide show.
CS253 Functors
Please pronounce carefully.
A Method
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)
- return type: int
- name of the method:
thrice
- argument list:
(int n)
An operator function
Let’s use the unary ~
operator, defined as bit-wise complement
for integers.
class Foo {
public:
const char * operator~ () { return "hello"; }
};
Foo f;
cout << ~f << '\n';
hello
const char * operator~ ()
- return type: const char *
- name of the method:
operator~
- argument list:
()
- Not to be confused with the destructor, which is named
~Foo
.
Another operator function
Let’s use the unary postfix ()
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)
- return type: double
- name of the method:
operator()
- argument list:
(int i, float f)
f
is not a function! f
is a Foo
object!
It sure behaves as a function.
Sounds useless
😒
- Hooray!
- We’ve created an alternate way to write a function.
- It has the advantage of being more confusing.
- However, it can also have state.
Another operator function
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)
- return type: int
- name of the method:
operator()
- argument list:
(int i)
Another operator function
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.
A stateful functor
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 the ctor,
b(4)
, are very different
than the parentheses in expression b(17)
.
b(4)
constructs an object of type Bigger
, named b
.
b(17)
is b.operator()(17)
.
Another stateful functor
Another use of memory:
class Queue {
double previous = NAN;
public:
double operator()(double d) {
const auto save = previous;
previous = d;
return save;
}
};
Queue q;
cout << q(1.2) << '\n';
cout << q(9.9) << '\n';
cout << q(3.4) << '\n';
nan
1.2
9.9
Another stateful functor
class Queue {
double previous = NAN;
public:
double operator()(double d) {
const auto save = previous;
previous = d;
return save;
}
};
Queue q;
cout << q(1.2) << '\n'
<< q(9.9) << '\n'
<< q(3.4) << '\n';
nan
1.2
9.9
The code above is questionable; the order of the Queue::operator()
calls are not defined. It could call q(9.9)
first, last, or in
between.
Even Queueier!
Multi-element queue:
class Queue {
deque<double> store; // Holy smokes--a deque!
public:
Queue(size_t init_size) : store(init_size, NAN) { }
double operator()(double d) {
store.push_back(d);
const auto f = store.front();
store.pop_front();
return f;
}
};
Queue q(3);
for (auto val=10.0; val<20.0; val+=1.1)
cout << q(val) << '\n';
nan
nan
nan
10
11.1
12.2
13.3
14.4
15.5
16.6
Or …
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 << " sum " << t(i) << '\n';
Add 0 sum 0
Add 1 sum 1
Add 2 sum 3
Add 3 sum 6
Add 4 sum 10
Add 5 sum 15
Add 6 sum 21
Add 7 sum 28
Add 8 sum 36
Add 9 sum 45
Add 10 sum 55
Sorting with Functors
- How does a container like set know the sorting order?
- Sure, it’s easy to answer for int and string, but what
about
class Student
?
- Sort by major?
- Sort by eId?
- Sort by last name, then first name, then CSUID?
- Besides, even for int, you might want to sort in a different order.
- You might even want multi-key sorting, like a phone book:
- primary key: last name
- secondary key: first name
- tertiary key: middle name
- quaternary key: street name
- quinary key: house number
- Imagine that you had a semi-brilliant assistant who was very good
at sorting.
- However, your assistant doesn’t know alphabetical order.
- He can do everything that’s needed for sorting except actually
determine if, say, “Jay” comes before “David”.
- Therefore, he frequently comes to you,
asking if this word comes before this other word.
- set is like that. It knows the mechanics of sorting,
but it doesn’t know what order to use. You need to help it.
Comparison functor
- You provide set with a comparison functor.
- The set code makes comparisons using the functor.
- The functor only compares two items at a time.
- I say again, two items at a time. Only two.
The functor compares—it doesn’t sort.
- The functor returns true iff the first item should come
before the second item.
Why can’t you use a comparison function, as opposed to a comparison functor ?
Because the set template takes types, not objects or
values.
Implicit/explicit
Implicit sorting criterion:
set<int> s = {31, 49, 41, 26, 53, 58, 97, 93, 23, 84};
for (auto v : s)
cout << v << ' ';
23 26 31 41 49 53 58 84 93 97
Explicit sorting criterion:
set<int, less<int> > s = {31, 49, 41, 26, 53, 58, 97, 93, 23, 84};
for (auto v : s)
cout << v << ' ';
23 26 31 41 49 53 58 84 93 97
What’s this > >
business?
An old problem with the >>
token. Compilers used to have
problems regarding >>
as the shift-right operator, as opposed to
two closing angle brackets. It’s no longer needed, but you still
see it in old code, or new code written by old coders.
Reversed
Use greater<int>
to sort from biggest to smallest:
set<int, greater<int>> s = {31, 49, 41, 26, 53, 58, 97, 93, 23, 84};
for (auto v : s)
cout << v << ' ';
97 93 84 58 53 49 41 31 26 23
Explicitly explicit
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, 49, 41, 26, 53, 58, 97, 93, 23, 84};
for (auto v : s)
cout << v << ' ';
23 26 31 41 49 53 58 84 93 97
Two-level
Use a comparison functor that does a two-level comparison;
primarily by proximity to 45, secondarily by value.
struct compare {
bool operator()(int a, int b) const {
int da = abs(a-45), db = abs(b-45); // distance from 45
if (da != db)
return da < db; // Primary sort: distance from 45
return a < b; // Secondary sort: value of number
}
};
set<int, compare> s = {31, 49, 41, 26, 53, 58, 97, 93, 23, 84};
for (auto v : s)
cout << v << ' ';
41 49 53 58 31 26 23 84 93 97
Ternary operator
Another way to write a two-level comparison functor:
struct compare {
bool operator()(int a, int b) const {
int da = abs(a-45), db = abs(b-45); // distance from 45
return (da != db) ? da < db : a < b;
}
};
set<int, compare> s = {31, 49, 41, 26, 53, 58, 97, 93, 23, 84};
for (auto v : s)
cout << v << ' ';
41 49 53 58 31 26 23 84 93 97
Let pair::operator<
do the work.
A technique that I like: offload the multi-level comparison to pair,
which naturally compares that way:
struct compare {
bool operator()(int a, int b) const {
pair pa = {abs(a-45), a}, // Love that CTAD!
pb = {abs(b-45), b};
return pa < pb;
}
};
set<int, compare> s = {31, 49, 41, 26, 53, 58, 97, 93, 23, 84};
for (auto v : s)
cout << v << ' ';
41 49 53 58 31 26 23 84 93 97
“But that’s less efficient.”
“Did you measure it, or are you guessing?”
Let pair::operator<
do the work.
Or, eliminating the pair variables that are only used once:
struct compare {
bool operator()(int a, int b) const {
return pair{abs(a-45), a} < pair{abs(b-45), b};
}
};
set<int, compare> s = {31, 49, 41, 26, 53, 58, 97, 93, 23, 84};
for (auto v : s)
cout << v << ' ';
41 49 53 58 31 26 23 84 93 97
Curious criteria
This sorts first by final digit, then by value:
struct compare {
bool operator()(int a, int b) const {
int last_a = a%10, last_b = b%10; // Final digits
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, 49, 41, 26, 53, 58, 97, 93, 23, 84};
for (auto v : s)
cout << v << ' ';
31 41 23 53 93 84 26 97 58 49
Multi-level
struct student {
int id; float gpa; string name;
};
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, 3.9, "Jack Applin"},
{82828282, 0.5, "Craig Partridge"},
{84444444, 2.2, "Homecoming Queen"},
{83333333, 2.2, "Joe Kolledge"},
};
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
Not all
- Of course, not all collections require a comparison functor.
- 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.
Example #1
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
Example #2
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
Example #3
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;
transform(name.begin(), name.end(),
result.begin(), embiggen);
cout << result << '\n';
}
BEVERLY HILLS CHIHUAHUA
Why assign name
to result
?
Can’t write to a string with no space allocated.
Example #4
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
Example #5
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
Example #6
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
Example #7
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
Example #8
Don’t re-invent the wheel:
string name = "Beverly Hills Chihuahua";
for (char &c : name)
c = toupper(c);
cout << name << '\n';
BEVERLY HILLS CHIHUAHUA
Sprechen Sie Deutsch?
Warning notice on the traffic light system of the Dresden Roads and Civil Engineering Office
The previous eight examples assume:
- Each lowercase letter fits in a char.
- Each uppercase letter is also one byte.
- Each lowercase letter has an uppercase equivalent.
However:
- Eszett, ß, U+00DF, an sz
ligature, is two bytes in UTF-8.
- The capital letter, ẞ, U+1E9E, is three bytes in UTF-8
(because it came later?).
- Pre-2017, ẞ was unofficial in Germany, so
“Straße”
became “STRASSE” in uppercase. Post-2017, “STRAẞE”
is also acceptable.