functor


Concept: Functora

In C++, a functor (also known as function object) is a class or struct that overloads the function call operator operator(). This allows objects of the class to be used with the same syntax as a function call. Functors are powerful because they can maintain state between calls and can be used in many contexts where functions are expected, such as in standard algorithms.

Example 1: Basic Functor

#include <iostream>

class Multiplier {
private:
    int factor;

public:
    Multiplier(int f) : factor(f) {}

    int operator()(int x) const {
        return x * factor;
    }
};

int main() {
    Multiplier times3(3);
    std::cout << "5 * 3 = " << times3(5) << std::endl;
    std::cout << "7 * 3 = " << times3(7) << std::endl;

    return 0;
}

Explanation

Example 2: Functor with State

#include <iostream>
#include <vector>
#include <algorithm>

class Accumulator {
private:
    int sum;

public:
    Accumulator() : sum(0) {}

    void operator()(int x) {
        sum += x;
    }

    int getSum() const {
        return sum;
    }
};

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    Accumulator acc = std::for_each(numbers.begin(), numbers.end(), Accumulator());

    std::cout << "Sum of numbers: " << acc.getSum() << std::endl;

    return 0;
}

Explanation

Example 3: Parameterized Functor

#include <iostream>
#include <vector>
#include <algorithm>

class DivisibleBy {
private:
    int divisor;

public:
    DivisibleBy(int d) : divisor(d) {}

    bool operator()(int x) const {
        return x % divisor == 0;
    }
};

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    int divisor = 3;
    DivisibleBy isDivisibleBy3(divisor);

    int count = std::count_if(numbers.begin(), numbers.end(), isDivisibleBy3);

    std::cout << "Numbers divisible by " << divisor << ": " << count << std::endl;

    return 0;
}

Explanation

Example 4: Functor in Custom Sorting

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

class LengthCompare {
public:
    bool operator()(const std::string& a, const std::string& b) const {
        return a.length() < b.length();
    }
};

int main() {
    std::vector<std::string> words = {"apple", "banana", "cherry", "date", "elderberry"};

    std::sort(words.begin(), words.end(), LengthCompare());

    std::cout << "Words sorted by length:" << std::endl;
    for (const auto& word : words) {
        std::cout << word << std::endl;
    }

    return 0;
}

Explanation

Why use Functors and not member functions?

A member function named count() could indeed track state similarly to a functor's operator(). We explore the key advantages of functors over simple member functions, focusing on capabilities that go beyond just maintaining state.

Introduction

While functors and member functions can both maintain state, functors offer distinct advantages in certain scenarios, particularly in the context of generic programming and when working with the C++ Standard Library algorithms.

Key Advantages of Functors

1. First-Class Functions

#include <iostream>
#include <vector>
#include <algorithm>

class Adder {
private:
    int value;
public:
    Adder(int v) : value(v) {}
    int operator()(int x) const { return x + value; }
};

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::transform(numbers.begin(), numbers.end(), numbers.begin(), Adder(10));

    for (int num : numbers) {
        std::cout << num << " ";
    }
    return 0;
}

Explanation

2. Type Erasure and Polymorphic Behavior

#include <iostream>
#include <functional>
#include <vector>

class Multiplier {
private:
    int factor;
public:
    Multiplier(int f) : factor(f) {}
    int operator()(int x) const { return x * factor; }
};

int main() {
    std::vector<std::function<int(int)>> operations;
    operations.push_back(Multiplier(2));
    operations.push_back([](int x) { return x + 5; });
    operations.push_back([](int x) { return x * x; });

    for (const auto& op : operations) {
        std::cout << op(10) << " ";
    }
    return 0;
}

Explanation

3. Compile-Time Polymorphism

#include <iostream>

template<typename Operation>
void performOperation(int x, Operation op) {
    std::cout << "Result: " << op(x) << std::endl;
}

class Square {
public:
    int operator()(int x) const { return x * x; }
};

class Cube {
public:
    int operator()(int x) const { return x * x * x; }
};

int main() {
    performOperation(5, Square());
    performOperation(5, Cube());
    return 0;
}

Explanation

4. Stateless Functors as Empty Base Optimization Candidates

#include <iostream>

class EmptyFunctor {
public:
    void operator()() const {}
};

template<typename Func>
class Container : private Func {
public:
    void doSomething() {
        Func::operator()();
    }
};

int main() {
    Container<EmptyFunctor> c;
    std::cout << "Size of Container<EmptyFunctor>: " << sizeof(c) << std::endl;
    return 0;
}

Explanation

5. Function Composition

#include <iostream>
#include <functional>

template<typename F, typename G>
class Compose {
private:
    F f;
    G g;
public:
    Compose(F f, G g) : f(f), g(g) {}

    template<typename T>
    auto operator()(T x) const -> decltype(f(g(x))) {
        return f(g(x));
    }
};

template<typename F, typename G>
Compose<F, G> compose(F f, G g) {
    return Compose<F, G>(f, g);
}

int main() {
    auto square = [](int x) { return x * x; };
    auto addOne = [](int x) { return x + 1; };

    auto squareThenAddOne = compose(addOne, square);
    std::cout << "Result: " << squareThenAddOne(5) << std::endl;

    return 0;
}

Explanation

Summary

While it's true that member functions can maintain state similarly to functors, functors offer several unique advantages:

  1. First-Class Function Behavior: Functors can be passed directly to algorithms and functions, behaving like first-class functions.

  2. Type Erasure: They work seamlessly with std::function, enabling flexible polymorphic behavior without inheritance.

  3. Compile-Time Polymorphism: Functors excel in template-based generic programming, allowing for efficient compile-time polymorphism.

  4. Empty Base Optimization: Stateless functors can be optimized away when used as base classes, which is not possible with member functions.

  5. Function Composition: Functors naturally support function composition, allowing for the creation of complex operations from simpler ones.

  6. STL Compatibility: They integrate more naturally with the C++ Standard Library algorithms and containers.

Functors in C++ are versatile and powerful constructs that combine the functionality of functions with the ability to maintain state. They are particularly useful in the following scenarios:

  1. When you need to customize behavior of standard algorithms.
  2. For operations that require maintaining state between calls.
  3. As a way to parameterize algorithms with behavior.
  4. To implement callback mechanisms.

The examples provided demonstrate various use cases of functors, from simple arithmetic operations to more complex scenarios like custom sorting and accumulation. Functors offer better performance compared to function pointers because they can be inlined by the compiler. They also provide a clean and object-oriented way to encapsulate both behavior and associated data, making them a valuable tool in modern C++ programming.

Related

Previous Page | Course Schedule | Course Content