- Overview
-
Table of Contents
- Special Member Functions: Constructors, Destructors, and the Assignment Operator
- Operator Overloading
- Memory Management
- Templates
- Namespaces
- Time and Date Library
- Streams
- Object-Oriented Programming and Design Principles
- The Standard Template Library (STL) and Generic Programming
- Exception Handling
- Runtime Type Information (RTTI)
- Signal Processing
- Creating Persistent Objects
- Bit Fields
- New Cast Operators
- Environment Variables
- Variadic Functions
- Pointers to Functions
- Function Objects
- Pointers to Members
- Lock Files
- Design Patterns
- Dynamic Linking
- Tips and Techniques
- Five Things You Need to Know About C++11 Unions
- A Tour of C99
- A Tour of C1X
-
C++0X: The New Face of Standard C++
- Reference Wrapper
- The Performance Technical Report
- auto for the People
- Ironing Templates' Syntactic Wrinkles
- Visual C++ Becomes ISO Compliant
- A Garbage Collector for C++
- C99 Core Features in C++0X
- The <code>shared_ptr</code> Class
- The shared_ptr Class, II
- Lambda Expressions and Closures, Part I
- Lambda Expressions and Closures, Part II
- Lambda Expressions and Closures, Part III
- The Type Traits Library, Part I
- The Type Traits Library, Part II
- The Type Traits Library, Part III
- finally Revisited
- The Any Library
- The nullptr Keyword Proposal
- Delegating Constructors
- The Explicit Conversion Operators Proposal
- Conditionally-Supported Behavior
- The weak ptr Class Template, Part I
- The weak ptr Class Template, Part II
- POD Types Revisited
- The rvalue Reference Proposal, Part I
- The rvalue Reference Proposal, Part II
- Proposal for New String Algorithms
- Concepts, Part I
- Concepts, Part II
- constexpr: Generalized Constant Expressions
- The <u>constexpr</u> Proposal: Constructors
- Strongly-Typed enum Types
- C++09: The Road Ahead
- C++09: Proposals by Statuses
- Changing Undefined Behavior to Diagnosable Errors
- New Character Types
- The __func__ Predeclared Identifier is Coming to C++
- Static Assertions
- The extern template Proposal
- Variadic Templates, Part I
- Variadic Templates, Part II
- Variadic Templates, Part III -- Critique
- Using unique_ptr, Part I
- Using unique_ptr, Part II
- Unrestricted Unions, Part I
- Unrestricted Unions, Part II
- Unrestricted Unions, Part III
- Types With No Linkage as Template Arguments
- New Initialization Syntax
- Initializer Lists and Sequence Constructors
- New Standard Library Algorithms
- Class Member Initializers
- Inheriting Constructors
- Introducing Attributes
- The Removal of Concepts From C++0x
- The Future of C++0x, Part I
- The Future of C++0X, Part II
- The Debate About Attributes, Part I
- The Debate About Attributes, Part II
- The Debate About Attributes, Part III
- The Debate About Attributes, Part IV
- Forward Declarations of Enum Types
- The SCARY Iterators Proposal, Part I
- The SCARY Iterators Proposal, Part II
- Heading for Deprecation: <tt>export</tt>, Exception Specification and <tt>register</tt>
- The Rejection of the Unified Function Syntax Proposal
- Rvalue References as Object Members
- FCD Approved
- The Debate on noexcept, Part I
- The Debate on noexcept, Part II
- The Debate on noexcept, Part III
- About-face -- [[Attributes]] to Be Replaced with Keywords
- Will Delegating Constructors Be Removed From C++0x?
- Rvalue References: Past, Present and Future, Part I
- Rvalue References: Past, Present and Future, Part II
- Rvalue References: Past, Present and Future, Part III
- A Move in the Right Direction, Part I
- A Move in the Right Direction, Part II
- New Keywords for Inheritance Control, Part I
- New Keywords for Inheritance Control, Part II
- FDIS Approved
- C++0x Concurrency
- The Reflecting Circle
- We Have Mail
- The Soapbox
- Numeric Types and Arithmetic
- Careers
- Locales and Internationalization
Lambda Expressions and Closures, Part I
Last updated Jan 1, 2003.
The C++ standards committee approved the lambda expressions proposal at the Bellevue meeting of February 2008. The latest version of the proposal is very different from the initial draft that I presented here in 2005. The terminology has changed, as have the syntax, semantics, usage and underlying implementation of lambda expressions. In the first part of this series I introduce the fundamental concepts of the latest lambda proposal.
Why Use Lambda Expressions?
A lambda expression (also known as a lambda function) is nameless function defined at the place of call. As such, it's similar to a function object. Indeed, lambda expressions are automatically transformed into function objects. So why not use function objects in the first place? As useful it may be, creating a function object is a laborious task: you have to define a class with data members, an overloaded function call operator and a constructor. You then have to instantiate an object of that type at the place of all. This is very verbose and ånot well-suited for creating a one-time function object that is used "on the fly".
To demonstrate the usefulness of lambda expressions, suppose you need to find the first employee whose salary is within a given range. Using traditional and verbose function objects, you could first write a function class called withinrange:
class withinrange {
double low, high;
public:
withinrange(double l, double h) : low(l), high(h) { }
bool operator()(const employee& emp) {
return emp.salary() >= low && emp.salary() < high;
}
};
Next, you use the find_if algorithm to locate the first employee whose salary is within the specified range from a sequence of employees:
double minimum_salary=1000; std::find if(employees.begin(), employees.end(), withinrange(minimum_salary, 1.25* minimum_salary));
The third argument of the find_if call is a function object that in other programming languages is called a closure. A closure is an unnamed function object that stores the said function's environment. The environments consists of local variables that the function accesses. In this case, the data members low and high are the environment stored in the closure. In simpler words, a closure is the hypothetical function object that the compiler will generate for a given lambda expression.
Lambda Expression Usage
Using the new lambda proposal, the above find_if call can be rewritten as:
double minimum_salary = 1000; double upper_limit = 1.25 * minimum_salary; std::find if(employees.begin(), employees.end(), [&](const employee& emp) (emp.salary() >= minimum_salary && emp.salary() < upper_limit));
First, notice that the lambda expression is self-contained within a single line of code. You don't need a separate function class anymore.
A lambda expression begins with the lambda introducer [] (I will discuss the meaning of the & between the brackets in a different part of this series). The lambda expression's parameter list appears after the lambda introducer. In this example, the parameter list consists of the sole parameter const employee&. This entire lambda expression is said to be monomorphic because the types of its parameters are explicitly specified. Here, the type of the sole parameter emp is const employee&. A polymorphic version of the same expression would be:
[&](emp) (emp.salary() >= minimum_salary && emp.salary() < upper_limit)
The latter form requires that the parameter types shall be deduced from the context (the place of call). The current proposal focuses only on monomorphic lambda expressions so I will not discuss polymorphic lambdas in this series.
Implicit and Explicit Return Types
The last part of the previous lambda expression:
(emp.salary() >= minimum_salary && emp.salary() < upper_limit)
is the lambda expression's body. A lambda expression's body can consist of a single parenthesized expression. In that case, the return type of the lambda function is implicitly deduced from the expression itself. For example, the following expression yields a bool result:
(emp.salary() >= minimum_salary && emp.salary() < upper_limit)
Therefore, the return type of the above lambda expression is bool.
Technically speaking, if the return type isn't explicitly specified in a lambda expression, it's defined as decltype(e) where e is the body of the lambda expression.
You may specify the return type of a lambda expression explicitly, though. Using the new function declaration syntax, here's how you do it:
[&](emp) ->bool (emp.salary() >= minimum_salary && emp.salary() < upper_limit)
Lambda Expression Body
A lambda expression's body may contain more than one statement. In such cases, the entire body is enclosed within a pair of braces and must have an explicit return statement. The following lambda expression takes two parameters of type int and has a return type int. Its body consists of three statements enclosed in a {} block:
[](int x, int y) -> int { int z; z = x + y; return z; }
The return statement in this example is mandatory because the lambda expression's body consists of multiple statements. Likewise, the explicit return type after the parameter list is mandatory as well.
External References
Lambda expressions are divided into two major categories: lambda expressions with no external references and those with external references. The latter access variables that are defined outside the lambda expression's parameter list, as opposed to lambda expressions that do not access variables defined outside the lambda expression's parameter list. Here's a lambda expression with no external references:
[](int x, int y) -> int { return x + y; }
Here's one with external references:
int z;
myfunc([](int x, int y) -> int { return x + y + z; } );//pseudo code
References to local variables declared outside of the lambda function bodies have been debated for a long time. The problem is that any local variable referenced in a lambda function body, e.g., z in the previous example, must somehow be stored in the resulting closure. How these variables are exactly stored in the closure is the disputed issue. Some proposed that copies of the external variables shall be stored in the closure. Copying however can be inefficient in some cases and might also lead to slicing and iterator invalidation. The other solution was to store references to the external variables in the closure. This approach can also be problematic as it could lead to dangling references. In the second part of this series I will show how the latest proposal solved the external references problem and how external references are represented in a closure.
