Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Lambda Expressions & C++


Lambda Expressions & C++

Lambda Expressions & C++

By Bjorn Karlsson

Lambda expressions are a common feature in functional programming languages. A lambda expression is a highly compact expression that does not require a separate class/function definition. The variables, values, and functions are defined together in one statement. A lambda expression is sometimes called an "unnamed" expression because it does not require a name that must then be associated with a description elsewhere in the program. Instead, the function is defined and used in one callout. That definition can include references to other functions, but the lambda expression still has the effect of eliminating one or more layers of code that separate the data from the operation.

There is no facility such as this in C++, although there is a definite need for it — especially with STL and other class libraries employing the concept of function objects. A lot of small classes are written to accommodate the requirements for C++ Standard Library algorithms; simple dispatchers needed to tweak argument ordering, arity, or combinations and delays of function calls. You can address the problem of this proliferation of small classes with lambda expressions, now available to C++ users through the Boost Lambda library (http://www.boost.org/libs/lamba/doc/).

The format of a lambda expression is lambda p1...pn.e, where p1...pn is a list of parameters used in the expression and e is the expression.

When you invoke a lambda expression, you also call out the arguments that are substituted into the expression. For instance, (lambda A B.A*B) 3 4 evaluates to 3*4=12. The expression (lambda A B C.do_something(A+B,B*2,C) 1 2 "str" calls the function do_something() with integer arguments 3, 4, and the string literal str.

Lambda expressions are important because they allow for functional composition and local functions. A good example is applying operations to elements of containers. Assume that for all elements, you must call a member function func() and pass the result of that function call to another function func2(); this is typically implemented using a free function or a function object with the sole purpose of calling these two functions. However, this type of simple task is better expressed directly at the call site by composing the functional elements into a lambda expression. It makes the code easier to write, easier to read, and easier to maintain.

You can use lambda expressions to dramatically reduce the size of your code. In this article, I provide an overview of the Boost Lambda library (BLL) and give examples of real-world uses for lambda expressions with the Standard Library. I also describe how BLL addresses topics such as control constructs, binders, and even exception handling.

Hello, Lambda

A good "Hello World" example for using lambda expressions in C++ is the problem of printing the elements of a std::vector containing strings to std::cout. Listing 1 is the current way of achieving this (without copying to an ostream_iterator), and the utility is used thus:

std::for_each( vec.begin(), vec.end(), print_to_stream<std::string>(std::cout));

There is nothing inherently wrong with this approach, but it would be convenient if you could define the function object directly, rather than having to create it in a separate block of code. With the BLL, all of this becomes:

std::for_each(vec.begin(),vec.end(),std::cout << _1);

This is just about as short and concise as it gets. The function object is created for us by the Lambda library, with _1 serving as a placeholder for an argument to be passed to the function call operator. Listing 2 is the Hello Lambda program in full.

The crux of this example is the result of the expression std::cout << _1. Through overloading, the placeholder _1 provides the context required for the Lambda library to do its job, which, in this case, is to create a function object that calls operator<< for any object passed to it.

The Hello Lambda example may still seem like some sort of black magic, so it is a worthwhile digression to take a quick look at how this works. What does that placeholder really do? The key to lambda expressions is the fact that almost all C++ operators can be overloaded, and overloading provides you with any context you would like. In our example, this involves creating a function object that can be invoked at any given time.

For consistency with the example, Listing 3 is a simple device capable of the same cool brevity as the previous example. The ingredients you need are simple: a tag class to use for overloading purposes, an instance of this class for passing to the operator, and an overloaded operator.

Now, with the help of the overloaded operator<<, an expression containing _1 (which is a variable of the placeholder type) returns a function object of type simple_binder_lshift<T>, which accepts a single argument to the function call operator. So, the black magic is really just operator overloading, creativity, and hard work. Since the example is limited in functionality and has some severe restrictions that make it unsuitable for a full-scale solution, you can imagine what it takes to go all the way with every operator and full functionality. For the expression std::for_each(vec.begin(),vec.end(),std::cout << _1), the third argument is of a type, T, that is applied to the result of dereferencing the iterators (in the range denoted by the first two parameters). The type T must thus be either a free function or a function object, accepting an argument of type std::string (or compatible cv-qualifiers). The expression std::cout << _1 is exactly that, because the overload resolution kicks in and selects the correct function:

  • The function template <typename T> sample::simple_binder_lshift<T> operator<<(T& t,sample::placeholder_1 ignore) is invoked.

  • The result of calling the function is an instance of template <typename T> struct simple_binder_lshift, with T being std::ostream. The function object stores the stream.

  • For each element in the range, the algorithm invokes the function call operator on the function object (template <typename U> T& operator()(U u)), which simply forwards the call to the stream operator<<, passing the element u (here, an object of type std::string).

Control Structures

While the placeholders like _1, _2, and _3 (to be used for additional arguments) enable wonderful things, they are not enough. Control structures are fundamental parts of any nontrivial programming task, and BLL supports constructs for if, if-then-else, while, do-while, for, and switch. Listing 4 demonstrates the use of the ubiquitous if_then and its cousin if_ (yes, the underscore must be there), and avoids a subtle pitfall while doing so.

The first use of a control structure in the example is:

std::for_each( 
  vec.begin(),
  vec.end(),
  if_then(_1%2,std::cout 
          << constant(" ") << _1));

if_then creates a lambda function object returning void (all BLL control structures do). If the Boolean condition (_1%2) returns True, the then part (std::cout << constant(" ") << _1) of the expression is executed. But what about constant? Consider what would happen if you created this lambda expression if_then(_1%2,std::cout << " " << _1). The operands of std::cout << " " are not lambda expressions, and they will thus be evaluated at once (and only once, for that matter). You need a lambda expression to delay the evaluation, and constant does exactly this. There are two more such utilities, namely constant_ref and var, used together with variables. The names convey their meanings — constant_ref creates a lambda function object for a reference to const, and var creates one for mutable references.

Next, an alternative syntax is used to create an if-then-else construct:

std::for_each(
  vec.begin(),
  vec.end(),
  if_(_1%2==0)[_1*=2].else_[std::cout 
              << constant(" ") <<  _1]);

The reason for having two sets of semantically equivalent control structures is that only experience will tell if syntactic resemblance with existing C++ constructs is preferable over the function-call style. The latter approach is influenced by another C++ lambda effort, Phoenix (part of Spirit Parser Library, http://spirit.sourceforge.net/).

There's just one more thing going on in the example that deserves mention — the naming of a delayed constant. It can quickly become tedious to write constant in the lambda expressions, and more importantly, it is easy to forget. A delayed constant alleviates this problem by naming a constant that does the same thing — creates a lambda expression:

constant_type<char>::type space(constant(' '));

For completeness, here are the remaining control structures available in the BLL:

if_then(condition, then_part)
if_then_else(condition, then_part, else_part)
if_then_else_return(condition, then_part,  else_part)
while_loop(condition, body)
while_loop(condition) 
do_while_loop(condition, body)
do_while_loop(condition) 
for_loop(init, condition, increment, body)
for_loop(init, condition, increment) 
switch_statement(...)

Did You Really Say Exception Handling?

Consider an std::vector containing missile objects for a war game. When someone pushes that red button, all of the missiles should be fired. Right, but what if a missile is broken and throws an exception? Should the remaining missiles be left as is until the problem is fixed? I think not; see Listing 5.

Bind expressions are also part of this example (see the Boost Bind library at http://www.boost.org/libs/bind/bind.html). In this context, bind is used for two purposes — to tie the member function missile::fire to the invocations, and to delay the function call.

The exception-handling mechanism is straightforward and takes the form:

try_catch(
  lambda-expression,
  catch_exception<exception-type>(catch-handler),
  catch_all(catch-handler)
  )

To rethrow the exception, call rethrow. Throw a new exception with throw_exception:

for_each(vec.begin(),vec.end(),
  try_catch(
    bind(&missile::fire,_1),
        catch_exception<missile_error>(
    throw_exception
      (std::logic_error("All is lost")))));

If, however, you need to throw an exception and use information from one of the placeholders (or perhaps the existing exception), you need to work a little harder. The problem you're facing is that it's not possible to take the address of a constructor (which in turn makes it impossible to create a constructor lambda expression). BLL solves this problem by adding a layer of indirection — a special function object (constructor) is used to wrap the constructor call, and you are thus able to use bind:

for_each(vec.begin(),vec.end(),
  try_catch(
    bind(&missile::fire,_1),
        catch_exception<missile_error>(
      throw_exception(bind(constructor
                  <std::logic_error>(),
        bind(&missile_error::
                        what, _e))))));

There is a special placeholder, _e (used in the preceding code), which designates the exception that was caught by the handler. By applying another bind to e_, you call the what member function: bind(&std::exception::what, _e). Of course, _e is not available in the catch_all handler.

Yet Another Example

Typically, lambda expressions are used when small and not overly complex functions are needed at the call site. If the function were nontrivial, you wouldn't want a lambda expression but a normal function or a function object. Consider a container with variant types, where you need to perform an action on certain types of (contained) elements. For the variant class, use boost::any (http://www.boost.org/libs/any/index.html), which serves as an opaque placeholder for any (nope, no pun) type of object. You retrieve the values from an instance of any by calling any_cast<T>(a), with T being the type of the value and a is the any. Now, if any is empty or contains a type other than T, an exception (bad_any_cast) is thrown. Thus, you again have a case where an otherwise simple function, suitable for a lambda expression, needs to perform exception handling. While it is possible (and even idiomatic) to use any_cast with a pointer argument for a pointer return without exceptions being thrown, this approach does not make for a readable lambda expression. The lambda expression (when avoiding exception handling) would look something along the lines of this (with ps being a pointer to std::string):

std::for_each(stuff.begin(),stuff.end(),
  if_then(((var(ps)=bind<std::string*>(
    &boost::any_cast<std::string>,&_1),
    var(ps)!=static_cast<std::string*>(0))),
    std::cout <<  *var(ps) << ' '));

A lambda expression like this kind of defeats the purpose, huh? See Listing 6 for a more readable version of the code.

I think you'll agree that lambda expressions can make the code readable. The ability to add functions at the call site is one step closer to literate programming. The meat of the example is in the call to for_each, where the action to be performed looks like this:

std::cout << 
  bind<std::string>(
    &boost::any_cast<std::string>,_1) 
          << ' ', noop

First, you bind the function any_cast. Because the return type of any_cast cannot be deduced, it is explicitly stated (bind<std::string>). Setting the return type explicitly relieves bind from trying to infer the return type. Then you (surprisingly) tack on another statement — noop, which is a void return lambda expression. Why? Because in exception-handling code, the return types of the functions must be identical. As you intend to eat the exception, you must make sure that the return types are the same, and that's why noop occurs once again, in the body for the catch_exception handler.

This type of code would become a lot simpler if these (template) functions were also available as function objects, with appropriate typedefs available for automatic deduction of return types.

Conclusion

Lambda expressions are powerful tools for creating functions at the call site. This makes for clear and concise code, and can substantially reduce the complexity of the code when employed correctly. In C++, we have yet to learn the idiomatic ways of using lambda expressions, simply because they haven't been available prior to the Boost Lambda Library. Thanks to Jaako Järvi and Gary Powell, the authors of BLL, for this great library.


Bjorn can be reached at [email protected].



Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.