devxlogo

Creating Unnamed Functions with the Lambda Library

Creating Unnamed Functions with the Lambda Library

A lambda expression enables you to define an unnamed function?directly on the call site of an algorithm. Such unnamed functions are chiefly useful in functional programming, generic libraries, and scientific applications. However, even mainstream C++ applications can benefit from the higher-order programming concepts of lambda abstractions. The following sections show how TR1’s lambda library can simplify the usage of STL algorithms.


How can you create small, unnamed functions directly on the call site of an STL algorithm?


Use the lambda library for implementing lambda functors.

Presenting the Problem
The term a “lambda expression” originates of the mathematical field of lambda calculus. To see what kind of problems a lambda expression can solve, consider the following invocation of the transform() algorithm originally presented in a previous 10-Minute Solution:

string s="hello";transform(s.begin(), s.end(), s.begin(), toupper);

transform‘s fourth argument can be any callable entity, say a functor, a bound member function or a freestanding function. Now, suppose you want to apply a different type of transformation to the string?namely mask every character with an asterisk. You want to be able to write something like this:

transform(s.begin(),           s.end(),           s.begin(),           ='*');

Of course, this code won’t compile. Instead, you have to write a full-blown function first:

int toasterisk(int original_letter){ return '*';}And call transform() like this:transform(s.begin(), s.end(), s.begin(), toasterisk);

Using for_each() instead of transform() for such a task seems more natural:

for_each(s.begin(), s.end(), toasterisk);

But still, the third argument must be the name of a function defined elsewhere in the program. This coding style, though inevitable, is artificial and unwieldy. In some cases, it also compromises encapsulation. Take a look at how to define an unnamed function on the call site using the lambda library. Creating a Lambda Expression
The lambda library enables you to rewrite the previous for_each() call like this:

for_each(s.begin(),          s.end(),         _1 = '*'); 

The interesting part is the third argument:

_1 = '*'

This expression creates a lambda functor which writes * to every character in s. The syntax seems unusual, so let’s see how it works under the hood.

The identifier _1 (an underscore followed by the number one, not a lowercase “L”) is called a placeholder. It’s the crux of the lambda functor. Within each iteration of for_each(), the functor is called with a different element of s as its actual argument. This actual argument is substituted for the placeholder _1, and the body of the lambda functor is evaluated. Thus, within the first iteration _1 is translated to s[0], then s[1] and so on.

Delaying the Evaluation of Constants and Variables
The following line outputs the elements of a vector. Each element is followed by a space:

vector  vi;vi.push_back(10);vi.push_back(20);vi.push_back(30);for_each(vi.begin(),          vi.end(),          cout << _1 << ' ');

Remember to understand what a lambda functor such as:

std::cout << _1 << ' '

does on each iteration, simply replace the placeholder with an actual argument e.g., vi[0]. As expected, this for_each() call outputs:

10 20 30

Now suppose you want to insert a space before each element. You modify the for_each() call slightly:

for_each(vi.begin(),          vi.end(),          cout << ' ' <<_1); 

This however doesn't work as expected. It outputs a single space followed by the elements of viwith no separators. The problem is that none of the operands of:

cout << ' '

is a lambda expression. Therefore, this expression is evaluated immediately, causing a single space to be displayed. In such cases, you need to use the constant() function for delaying the evaluation of constants:

for_each(vi.begin(),          vi.end(),          cout << constant(' ') << _1);

This time, you get the desired output:

10 20 30

The expression constant(' ') produces a nullary lambda functor that stores the character constant ' ' and returns a reference to it when called.

A similar problem arises when you use variables in a lambda expression:

int idx = 0; for_each(vi.begin(),          vi.end(),          cout << ++idx << ':' << _1 << '
');

Here, idx is incremented and displayed only once while the rest of the elements are displayed sequentially. To get the desired results, wrap idx with the function var():

 for_each(vi.begin(),          vi.end(),          cout << ++var(index) << ':' << _1 << '
');

Now you get the desired output:

1:102:203:30

Lambda by Numbers
A unary expression uses one placeholder?the predefined variable _1. The lambda library defines two more placeholders: _2 and _3 for binary and ternary functors, respectively. These placeholders are useful for creating an unnamed function taking two and three operands, respectively. Let's look at an example.

Suppose you have a container of pointers that needs to be sorted. The default sort() implementation will sort the bare pointers, which isn't what you usually want. To override this behavior, provide a lambda functor as the third argument:

struct Foo{//..};vector  vp;//..populate the container sort(vp.begin(), vp.end(), *_1 < *_2);//ascending

Let's see how it works. vp[n] is a pointer. Hence, the placeholders _1 and _2 represent pointers. The complete lambda expression dereferences these pointers and returns a Boolean value indicating whether *_1 is less than *_2. This binary lambda functor ensures that the sorting operation produces the desired results.

Lambda Unlimited
The lambda library has many other constructs for defining if-then-else statements, exception handling, switch statements and the sizeof and typeid operators. Although you've only seen the basic features of the lambda library here, it's clear that it's a very powerful addition to the C++ Standard Library.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist