CS253: Software Development with C++

Fall 2022

Functors

Show Lecture.Functors as a slide show.

CS253 Functors

Please pronounce carefully.

A Method

once: 💉

twice: 💉💉 thrice: 💉💉💉

Consider this class:

class Foo {
  public:
    int thrice (int n) const { 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~ () const { 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) const { 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) const { 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) const { 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) const { return i+increment; }
};

Bigger b(4), c(12);
cout << b(17) << ' ' << b(100) << '\n'
     << b(1000) << ' ' << c(2000) << '\n';
21 104
1004 2012

ID numbers

The Local Static lecture generated ID numbers using a local static variable. Now, with a functor:

class ID {
    long id;
    const short inc;
  public:
    ID(long id=800'00'0000, short inc=13) : id(id), inc(inc) { }
    auto operator()() { return id += inc; } // NOT const
};

int main() {
    ID student_id;  // NOT student_id()
    ID employee_id(10000,1);
    cout << student_id()  << ' ' << student_id()  << '\n'
         << employee_id() << ' ' << employee_id() << '\n';
}
800000013 800000026
10001 10002

Another stateful functor

Another use of memory. You recall that NAN means Not a Number. No const, because operator() changes previous.

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

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

Comparison functor

Why can’t set 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 

Make sure that the comparison functor is const. Compilers are getting fussier about this for comparison functors used with sort() or STL containers.

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 cool technique: 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')  // 'a' <= c <= 'z' will NOT work
        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) const {
        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) const {
        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.