CS253: Software Development with C++

Fall 2020

Functors

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

An operator function

Let’s use the unary ~ operator. It’s defined as bit-wise complement for integers.

class Foo {
  public:
    const char *operator~() { return "hello"; }
};

Foo f;
cout << ~f << '\n';
hello

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

Sounds useless

😒

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

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

Another stateful functor

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

Another stateful functor

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

The code above is questionable, because the order of the calls to Queue::operator() are not defined. It could call q(42) first, last, or in between.

Even Queueier!

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

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 << " 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

Sorting with Functors

The Idiot Savant

Comparison functor

Why can’t you use a comparison function, as opposed to a comparison functor?

Because the set template takes types, not objects.

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 > > folderol?

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, like last name/first name.

struct compare {
    bool operator()(int a, int b) const {
        int da = abs(a-45), db = abs(b-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);
        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 

Curious criteria

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, 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; 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

Not all

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