In C++, the result of a lambda-expression
is called a closure object
. This article is not intended to introduce the usage of C++ lambdas (this is covered in detail in “TC++PL” and “C++ Primer,” or you can refer to my previous summary C++11 Syntactic Sugar #lambda Expressions), but rather to analyze how lambda-expression
is implemented in Clang from the perspective of LLVM-IR.
The C++ standard describes lambda as follows:
[ISO/IEC 14882:2014 §5.1.2.2] The evaluation of a lambda-expression results in a prvalue temporary (12.2). This temporary is called the closure object.
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type — called the closure type — whose properties are described below. This class type is neither an aggregate (8.5.1) nor a literal type (3.9).
A closure object behaves like a function object (20.9).
The standard mentions that the closure type is a unique, non-union class type. Let’s take a look at how Clang implements lambda:
1 | int main(){ |
The above code is a lambda object that captures an int
and a double
object and takes an int
parameter. Let’s look at its LLVM-IR code:
1 | %class.anon = type { i32*, double* } |
Here are a few key parts to focus on:
1 | %class.anon = type { i32*, double* } |
It can be seen that the implementation of lambda in LLVM is an anonymous class type object that overloads operator()
, where the captured parameters are stored as data members of this class, and the parameters received during the call are those received by the operator()
.
Next, let’s look at a function object (a class that overloads operator()
) that I manually wrote to achieve the same functionality as the lambda expression above:
1 | class A{ |
Its LLVM-IR code is:
1 | %class.A = type { i32*, double* } |
Now let’s compare the three key parts mentioned above:
1 |
|
It can be seen that the hand-written function object is exactly the same as the lambda expression generated by the compiler…
The objects captured by the capture list are stored as data members of the class generated by the compiler, while the receiving parameters are obtained as parameters of the operator()
. The main difference between the IR code for the lambda and my manually written one is that the lambda does not generate a corresponding constructor.
Thus, it can be concluded that in Clang, a lambda is implemented as a function object…
However, this raises another question: since a lambda is a function object, can I access the this
pointer within the lambda’s function body (the this
of the lambda)?
The answer is no. Here’s an example:
1 | class A |
Attempting to compile code that uses this
without capturing it in the lambda will result in the following compilation error:
1 | error: 'this' cannot be implicitly captured in this context |
The capture list of the lambda can capture this
:
1 | class A |
Furthermore, having lambda
combined with STL’s <functional>
is simply a powerful technique!