CS253: Software Development with C++

Spring 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

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

The order of the three operator() calls is unspecified.

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

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.

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;       // Why?
    transform(name.begin(), name.end(),
              result.begin(), embiggen);
    cout << result << '\n';
}
BEVERLY HILLS CHIHUAHUA

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