CS253: Software Development with C++

Spring 2021

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

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

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:

  1. Each lowercase letter fits in a char.
  2. Each uppercase letter is also one byte.
  3. Each lowercase letter has an uppercase equivalent.

However:

  1. Eszett, ß, U+00DF, an sz ligature, is two bytes in UTF-8.
  2. The capital letter, ẞ, U+1E9E, is three bytes in UTF-8 (because it came later?).
  3. Pre-2017, ẞ was unofficial in Germany, so “Straße” became “STRASSE” in uppercase. Post-2017, “STRAẞE” is also acceptable.