The How of Function Pointers, Functors, and Lambdas

Let’s look at an example using three approaches for passing information to an STL algorithm: function pointers, functors, and lambdas. (For convenience, we’ll refer to these three forms as function objects so that we won’t have to keep repeating “function pointer or functor or lambda.”) Suppose you wish to generate a list of random integers and determine how many of them are divisible by 3 and how many are divisible by 13. If necessary, imagine that this is a quest you find absolutely fascinating.

Generating the list is pretty straightforward. One option is to use a vector<int> array to hold the numbers and use the STL generate() algorithm to stock the array with random numbers:

#include <vector>
#include <algorithm>
#include <cmath>
...
std::vector<int> numbers(1000);
std::generate(vector.begin(), vector.end(), std::rand);

The generate() function takes a range, specified by the first two arguments, and sets each element to the value returned by the third argument, which is a function object that takes no arguments. In this case, the function object is a pointer to the standard rand() function.

With the help of the count_if() algorithm, it’s easy to count the number of elements divisible by 3. The first two arguments should specify the range, just as for generate(). The third argument should be a function object that returns true or false. The count_if() function then counts all the elements for which the function object returns true. To find elements divisible by 3, you can use this function definition:

bool f3(int x) {return x % 3 == 0;}

Similarly, you can use the following function definition for finding elements divisible by 13:

bool f13(int x) {return x % 13 == 0;}

With these definitions in place, you can count elements as follows:

int count3 = std::count_if(numbers.begin(), numbers.end(), f3);
cout << "Count of numbers divisible by 3: " << count3 << ' ';
int count13 = std::count_if(numbers.begin(), numbers.end(), f13);
cout << "Count of numbers divisible by 13: " << count13 << " ";

Next, let’s review how to accomplish the same task using a functor. A functor, as you’ll recall from Chapter 16, is a class object than can be used as if it were a function name, thanks to the class defining operator()() as a class method. One advantage of the functor in our example is that you can use the same functor for both counting tasks. Here’s one possible definition:

class f_mod
{
private:
    int dv;
public:
    f_mod(int d = 1) : dv(d) {}
    bool operator()(int x) {return x % dv == 0;}
};

Recall how this works. You can use the constructor to create an f_mod object storing a particular integer value:

f_mod obj(3);  // f_mod.dv set to 3

This object can use the operator() method to return a bool value:

bool is_div_by_3 = obj(7); // same as obj.operator()(7)

The constructor itself can be used as an argument to functions such as count_if():

count3 = std::count_if(numbers.begin(), numbers.end(), f_mod(3));

The argument f_mod(3) creates an object storing the value 3, and count_if() uses the created object to call the operator()() method, setting the parameter x equal to an element of numbers. To count how many numbers are divisible by 13 instead of 3, use f_mod(13) as the third argument.

Finally, let’s examine the lambda approach. The name comes from lambda calculus, a mathematical system for defining and applying functions. The system enables one to use anonymous functions—that is, it allows one to dispense with function names. In the C++11 context, you can use an anonymous function definition (a lambda) as an argument to functions expecting a function pointer or functor. The lambda corresponding to the f3() function is this:

[](int x) {return x % 3 == 0;}

It looks much like the definition of f3():

bool f3(int x) {return x % 3 == 0;}

The two differences are that the function name is replaced with [] (how anonymous is that!) and that there is no declared return type. Instead, the return type is the type that decltype would deduce from the return value, which would be bool in this case. If the lambda doesn’t have a return statement, the type is deduced to be void. In our example, you would use this lambda as follows:

count3 = std::count_if(numbers.begin(), numbers.end(),
         [](int x){return x % 3 == 0;});

That is, you use the entire lambda expression as you would use a pointer or a functor constructor.

The automatic type deduction for lambdas works only if the body consists of a single return statement. Otherwise, you need to use the new trailing-return-value syntax:

[](double x)->double{int y = x; return x – y;}  // return type is double

Listing 18.4 illustrates the points just discussed.

Listing 18.4. lambda0.cpp


// lambda0.cpp -- using lambda expressions
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <ctime>
const long Size1 = 39L;
const long Size2 = 100*Size1;
const long Size3 = 100*Size2;

bool f3(int x) {return x % 3 == 0;}
bool f13(int x) {return x % 13 == 0;}

int main()
{
    using std::cout;
    std::vector<int> numbers(Size1);

    std::srand(std::time(0));
    std::generate(numbers.begin(), numbers.end(), std::rand);

// using function pointers
    cout << "Sample size = " << Size1 << ' ';

    int count3 = std::count_if(numbers.begin(), numbers.end(), f3);
    cout << "Count of numbers divisible by 3: " << count3 << ' ';
    int count13 = std::count_if(numbers.begin(), numbers.end(), f13);
    cout << "Count of numbers divisible by 13: " << count13 << " ";

// increase number of numbers
    numbers.resize(Size2);
    std::generate(numbers.begin(), numbers.end(), std::rand);
    cout << "Sample size = " << Size2 << ' ';
// using a functor
    class f_mod
    {
    private:
        int dv;
    public:
        f_mod(int d = 1) : dv(d) {}
        bool operator()(int x) {return x % dv == 0;}
    };

    count3 = std::count_if(numbers.begin(), numbers.end(), f_mod(3));
    cout << "Count of numbers divisible by 3: " << count3 << ' ';
    count13 = std::count_if(numbers.begin(), numbers.end(), f_mod(13));
    cout << "Count of numbers divisible by 13: " << count13 << " ";

// increase number of numbers again
    numbers.resize(Size3);
    std::generate(numbers.begin(), numbers.end(), std::rand);
    cout << "Sample size = " << Size3 << ' ';
// using lambdas
    count3 = std::count_if(numbers.begin(), numbers.end(),
             [](int x){return x % 3 == 0;});
    cout << "Count of numbers divisible by 3: " << count3 << ' ';
    count13 = std::count_if(numbers.begin(), numbers.end(),
              [](int x){return x % 13 == 0;});
    cout << "Count of numbers divisible by 13: " << count13 << ' ';

    return 0;
}


Here is a sample output:

Sample size = 39
Count of numbers divisible by 3: 15
Count of numbers divisible by 13: 6

Sample size = 3900
Count of numbers divisible by 3: 1305
Count of numbers divisible by 13: 302

Sample size = 390000
Count of numbers divisible by 3: 130241

Count of numbers divisible by 13: 29860

The output illustrates that one should not rely on statistics based on small samples.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.131.255