The Why of Lambdas

You may be wondering what need, other than the flowering of geekly expressionism, the lambda serves. Let’s examine this question in terms of four qualities: proximity, brevity, efficiency, and capability.

Many programmers feel that it is useful to locate definitions close to where they are used. That way, you don’t have to scan through pages of source code to find, say, what the third argument to a count_if() function call accomplishes. Also if you need to modify the code, all the components are close at hand. And if you cut and paste the code for use elsewhere, again all the components are at hand. From this standpoint, lambdas are ideal because the definition is at the point of usage. Functions are worst because functions cannot be defined inside other functions, so the definition will be located possibly quite far from the point of usage. Functors can be pretty good because a class, including a functor class, can be defined inside a function, so the definition can be located close to the point of use.

In terms of brevity, the functor code is more verbose than the equivalent function or lambda code. Functions and lambdas are approximately equally brief. One apparent exception would be if you had to use a lambda twice:

count1 = std::count_if(n1.begin(), n1.end(),
         [](int x){return x % 3 == 0;});
count2 = std::count_if(n2.begin(), n2.end(),
         [](int x){return x % 3 == 0;});

But you don’t actually have to write out the lambda twice. Essentially, you can create a name for the anonymous lambda and then use the name twice:

auto mod3 = [](int x){return x % 3 == 0;}  // mod3 a name for the lambda
count1 = std::count_if(n1.begin(), n1.end(), mod3);
count2 = std::count_if(n2.begin(), n2.end(), mod3);

You even can use this no-longer-anonymous lambda as an ordinary function:

bool result = mod3(z); // result is true if z % 3 == 0

Unlike an ordinary function, however, a named lambda can be defined inside a function. The actual type for mod3 will be some implementation-dependent type that the compiler uses to keep track of lambdas.

The relative efficiencies of the three approaches boils down to what the compiler chooses to inline. Here, the function pointer approach is handicapped by the fact that compilers traditionally don’t inline a function that has its address taken because the concept of a function address implies a non-inline function. With functors and lambdas, there is no apparent contradiction with inlining.

Finally, lambdas have some additional capabilities. In particular, a lambda can access by name any automatic variable in scope. Variables to be used are captured by having their names listed within the brackets. If just the name is used, as in [z], the variable is accessed by value. If the name is preceded by an &, as in [&count], the variable is accessed by reference. Using [&] provides access to all the automatic variables by reference, and [=] provides access to all the automatic variables by value. You also can mix and match. For instance, [ted, &ed] would provide access to ted by value and ed by reference, [&, ted] would provide access to ted by value and to all other automatic variables by reference, and [=, &ed] would provide access by reference to ed and by value to the remaining automatic variables. In Listing 18.4, you can replace

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

with this:

int count13 = 0;
std::for_each(numbers.begin(), numbers.end(),
     [&count13](int x){count13 += x % 13 == 0;});

The [&count13] allows the lambda to use count13 in its code. Because count13 is captured by reference, any changes to count13 in the lambda are changes to the original count13. The expression x % 13 == 0 evaluates to true if x is divisible by 13, and true converts to 1 when added to count13. Similarly, false converts to 0. Thus, after for_each() applies the lambda expression to each element of numbers, count13 counts the number of elements divisible by 13.

You can use this technique to count elements divisible by 3 and elements divisible by 13 using a single lambda expression:

int count3 = 0;
int count13 = 0;
std::for_each(numbers.begin(), numbers.end(),
     [&](int x){count3 += x % 3 == 0; count13 += x % 13 == 0;});

This time, [&] makes all the automatic variables, including count3 and count13, available to the lambda expression.

Listing 18.5 puts these techniques to use.

Listing 18.5. lambda1.cpp


// lambda1.cpp -- use captured variables
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <ctime>
const long Size = 390000L;

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

    std::srand(std::time(0));
    std::generate(numbers.begin(), numbers.end(), std::rand);
    cout << "Sample size = " << Size << ' ';
// using lambdas
    int count3 = std::count_if(numbers.begin(), numbers.end(),
          [](int x){return x % 3 == 0;});
    cout << "Count of numbers divisible by 3: " << count3 << ' ';
    int count13 = 0;
    std::for_each(numbers.begin(), numbers.end(),
         [&count13](int x){count13 += x % 13 == 0;});
    cout << "Count of numbers divisible by 13: " << count13 << ' ';
// using a single lambda
    count3 = count13 = 0;
    std::for_each(numbers.begin(), numbers.end(),
         [&](int x){count3 += x % 3 == 0; count13 += x % 13 == 0;});
    cout << "Count of numbers divisible by 3: " << count3 << ' ';
    cout << "Count of numbers divisible by 13: " << count13 << ' ';
    return 0;
}


Here is a sample output:

Sample size = 390000
Count of numbers divisible by 3: 130274
Count of numbers divisible by 13: 30009
Count of numbers divisible by 3: 130274
Count of numbers divisible by 13: 30009

It’s reassuring that both approaches (two separate lambdas and a single lambda) in this program led to the same answers.

The main motivation for adding lambdas to C++ was to enable using a function-like expression as an argument to a function expecting a function pointer or functor as an argument. So the typical lambda would be a test expression or comparison expression that could be written as a single return statement. That keeps the lambda short and easy to understand and enables automatic deduction of the return value. However, it is likely that a subset of the ingenious C++ programming community will develop other uses.

Wrappers

C++ provides several wrappers or adapters. These are objects used to provide a more uniform or more appropriate interface for other programming elements. For example, Chapter 16 described bind1st and bind2nd, which adapt functions with two parameters to match up with STL algorithms that expect functions with one parameter to be supplied as an argument. C++11 provides additional wrappers. They include the bind template, which provides a more flexible alternative to bind1st and bind2nd, the mem_fn template, which allows a member function to pass as a regular function, the reference_wrapper template allows you to create an object that acts like reference but which can be copied, and the function wrapper, which provides a way to handle several function-like forms uniformly.

Let’s look more closely at one example of wrapper, the function wrapper, and at the problem it addresses.

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

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