Hi, habrovchane. In connection with the start of recruitment to a new group in the course
“Developer C ++” , we are sharing with you the translation of the second part of the article “Lambdas: from C ++ 11 to C ++ 20”. The first part can be read
here .

In the
first part of the series we looked at lambda from the point of view of C ++ 03, C ++ 11 and C ++ 14. In this article, I described the motivations behind this powerful C ++ feature, basic usage, syntax and improvements in each of the locales. I also mentioned several borderline cases.
Now it's time to switch to C ++ 17 and look a bit into the future (very close!): C ++ 20.
')
IntroductionA small reminder: the idea of this series came after one of our recent C ++ User Group meetings in Krakow.
We had a live programming session about the “history” of lambda expressions. The conversation was conducted by a C ++ expert, Thomas Kaminsky (
see Thomas's LinkedIn profile ). Here is the event:
Lambdas: From C ++ 11 to C ++ 20 - C ++ User Group Krakow .
I decided to take the code from Thomas (with his permission!) And write articles based on it. In the first part of the series I told the following about lambda expressions:
- Basic syntax
- Lambda type
- Call operator
- Capture variables (mutable, global, static variables, class members and this pointer, move-able-only objects, save constants):
- Return type
- IIFE - Immediately Invoked Function Expression
- Conversion to a function pointer
- Return type
- IIFE - Immediately Called Expressions
- Conversion to function pointer
- Improvements in C ++ 14
- Return Type Output
- Capture with initializer
- Member variable capture
- Generalized lambda expressions
The above list is only part of the history of lambda expressions!
Now let's see what has changed in C ++ 17 and what we get in C ++ 20!
Improvements in C ++ 17Standard (draft before publication)
N659 section on lambda:
[expr.prim.lambda] . C ++ 17 introduced two significant improvements to lambda expressions:
- constexpr lambda
- Capture * this
What do these innovations mean to us? Let's see.
constexpr lambda expressionsStarting with C ++ 17, the standard implicitly defines
operator()
for a lambda type as
constexpr
, if possible:
From expr.prim.lambda # 4 :
The function call operator is a function of constexpr if the declaration of the condition parameter of the corresponding lambda expression is followed by constexpr, or it satisfies the requirements for the function constexpr.
For example:
constexpr auto Square = [] (int n) { return n*n; }; // implicitly constexpr static_assert(Square(2) == 4);
Recall that in C ++ 17 a
constexpr
function must follow these rules:
- it should not be virtual (virtual);
- its return type must be a literal type;
- each of its parameter types must be a literal type;
- her body must be = delete, = default, or a compound statement that does not contain
- asm-definitions
- goto expressions
- tags,
- try block or
- definition of a variable of non-literal type, static variable or variable of stream memory for which initialization is not performed.
How about a more practical example?
template<typename Range, typename Func, typename T> constexpr T SimpleAccumulate(const Range& range, Func func, T init) { for (auto &&elem: range) { init += func(elem); } return init; } int main() { constexpr std::array arr{ 1, 2, 3 }; static_assert(SimpleAccumulate(arr, [](int i) { return i * i; }, 0) == 14); }
Play with the code here:
@WandboxThe code uses
constexpr
lambda, and then it is passed to a simple algorithm
SimpleAccumulate
. The algorithm uses several C ++ 17 elements: the
constexpr
add-
constexpr
to
std::array
,
std::begin
and
std::end
(used in the
for
loop with a range) are now also
constexpr
, so this means that all code can be executed at compile time.
Of course, this is not all.
You can capture variables (assuming that they are also
constexpr
):
constexpr int add(int const& t, int const& u) { return t + u; } int main() { constexpr int x = 0; constexpr auto lam = [x](int n) { return add(x, n); }; static_assert(lam(10) == 10); }
But there is an interesting case when you do not pass the captured variable further, for example:
constexpr int x = 0; constexpr auto lam = [x](int n) { return n + x };
In this case, in Clang we can get the following warning:
warning: lambda capture 'x' is not required to be captured for this use
This is probably due to the fact that x can be changed on the spot with each use (if you do not pass it on or you do not take the address of that name).
But please let me know if you know the official rules for this behavior. I found only (from the
cppreference ) (but I can't find it in the draft ...)
(Translator's note: as our readers write, probably, we mean the substitution of the value 'x' in each place where it is used. It’s impossible to change it for sure.)A lambda expression can read the value of a variable without grabbing it if the variable
* has a constant non-volatile
integer or enum type and was initialized with constexpr
or
* is constexpr
and has no changeable members.Be prepared for the future:
In C ++ 20, we will have
constexpr
standard algorithms and perhaps even some containers, so
constexpr
lambdas will be very useful in this context. Your code will look the same for the runtime version, as well as for the
constexpr
version (compile time version)!
In a nutshell:
constexpr
lambda allows you to
constexpr
with template programming and possibly have a shorter code.
Now let's move on to the second important feature available in C ++ 17:
Capture of * thisCapture * thisDo you remember our problem when we wanted to capture a member of the class? By default, we capture this (as a pointer!), And therefore we may have problems when temporary objects go out of scope ... This can be fixed by using the capture method with an initializer (see the first part of the series). But now, in C ++ 17, we have another way. We can wrap a copy of * this:
Play with the code here:
@WandboxCapturing a desired member variable with an initializer capture protects you from possible errors with temporary values, but we cannot do the same when we want to call a method like:
For example:
struct Baz { auto foo() { return [this] { print(); }; } void print() const { std::cout << s << '\n'; } std::string s; };
In C ++ 14, the only way to make the code more secure is to capture
this
with an initializer:
auto foo() { return [self=*this] { self.print(); }; } C ++ 17 : auto foo() { return [*this] { print(); }; }
Something else:
Please note that if you write
[=]
in a member function,
this
captured implicitly! This can lead to errors in the future ... and it will become obsolete in C ++ 20.
Here we come to the next section: the future.
Future with C ++ 20In C ++ 20 we get the following functions:
- Allow
[=, this]
as lambda capture - P0409R2 and cancel the implicit capture of this via [=]
- P0806 - Package extension in
lambda init-capture: ... args = std::move (args)] () {}
- P0780 - static,
thread_local
and lambda capture for structured bindings - P1091 - lambda template (also with concepts) - P0428R2
- Simplify implicit lambda capture - P0588R1
- Constructive and assignable lambda stateless by default - P0624R2
- Lambda in a non- calculated context - P0315R4
In most cases, the newly introduced functions “clear” lambda usage, and they allow for some advanced use cases.
For example, with
P1091, you can capture a structured binding.
We also have explanations related to the seizure of this. In C ++ 20, you will get a warning if you grab
[=]
in the method:
struct Baz { auto foo() { return [=] { std::cout << s << std::endl; }; } std::string s; }; GCC 9: warning: implicit capture of 'this' via '[=]' is deprecated in C++20
If you really need to capture it, you should write
[=, this]
.
There are also changes associated with advanced use cases, such as unresolved contexts and stateless lambdas, which can be constructed by default.
With both changes you can write:
std::map<int, int, decltype([](int x, int y) { return x > y; })> map;
Read the motives of these functions in the first version of the sentences:
P0315R0 and
P0624R0 .
But let's look at one interesting feature: lambda templates.
Lambda templateIn C ++ 14, we obtained generalized lambdas, which means that the parameters declared as auto are template parameters.
For lambda:
[](auto x) { x; }
The compiler generates a call statement that corresponds to the following generic method:
template<typename T> void operator(T x) { x; }
But there was no way to change this template parameter and use the actual template arguments. In C ++ 20 this will be possible.
For example, how can we limit our lambda to work only with vectors of some type?
We can write a common lambda:
auto foo = []<typename T>(const auto& vec) { std::cout<< std::size(vec) << '\n'; std::cout<< vec.capacity() << '\n'; };
But if you call it with the int parameter (for example,
foo(10);
), you can get some kind of hard-to-read error:
prog.cc: In instantiation of 'main()::<lambda(const auto:1&)> [with auto:1 = int]': prog.cc:16:11: required from here prog.cc:11:30: error: no matching function for call to 'size(const int&)' 11 | std::cout<< std::size(vec) << '\n';
In C ++ 20 we can write:
auto foo = []<typename T>(std::vector<T> const& vec) { std::cout<< std::size(vec) << '\n'; std::cout<< vec.capacity() << '\n'; };
The aforementioned lambda allows the template call operator:
<typename T> void operator(std::vector<T> const& s) { ... }
The template parameter follows the capture clause
[]
.
If you call it with
int (foo(10);)
, you will get a more pleasant message:
note: mismatched types 'const std::vector<T>' and 'int'
Play with the code here:
@WandboxIn the above example, the compiler can warn us about inconsistencies in the lambda interface, rather than in the code inside the body.
Another important aspect is that in a universal lambda you only have a variable, not its type of pattern. Therefore, if you want to access it, you must use decltype (x) (for lambda expressions with an argument (auto x)). This makes some code more verbose and complex.
For example (using the code from P0428):
auto f = [](auto const& x) { using T = std::decay_t<decltype(x)>; T copy = x; T::static_function(); using Iterator = typename T::iterator; }
Now you can write as:
auto f = []<typename T>(T const& x) { T::static_function(); T copy = x; using Iterator = typename T::iterator; }
In the section above, we had a brief overview of C ++ 20, but I have another additional usage example for you. This technique is possible even in C ++ 14. So read on.
Bonus - LIFTING LIFTINGCurrently, we have a problem when you have function overloads, and you want to transfer them to standard algorithms (or anything that requires some callable object):
// two overloads: void foo(int) {} void foo(float) {} int main() { std::vector<int> vi; std::for_each(vi.begin(), vi.end(), foo); }
We get the following error from GCC 9 (trunk):
error: no matching function for call to for_each(std::vector<int>::iterator, std::vector<int>::iterator, <unresolved overloaded function type>) std::for_each(vi.begin(), vi.end(), foo); ^^^^^
However, there is a trick in which we can use lambda, and then call the desired overload function.
In the basic form, for simple value types, for our two functions, we can write the following code:
std::for_each(vi.begin(), vi.end(), [](auto x) { return foo(x); });
And in the most general form, we need to recruit a little more:
Pretty complicated code ... right? :)
Let's try to decipher it:
We create a generic lambda and then pass all the arguments we get. To define it correctly, we need to specify noexcept and the type of the return value. That's why we need to duplicate the calling code — to get the right types.
Such a LIFT macro works in any compiler that supports C ++ 14.
Play with the code here:
@WandboxConclusionIn this post, we looked at significant changes in C ++ 17, and made a review of new features in C ++ 20.
You may notice that with each iteration of the language lambda expressions are mixed with other elements of C ++. For example, before C ++ 17, we could not use them in the context of constexpr, but now it is possible. Similarly with generalized lambdas starting from C ++ 14 and their evolution in C ++ 20 in the form of template lambdas. Did I miss something? Maybe you have some exciting example? Please let me know in the comments!
LinksC ++ 11 -
[expr.prim.lambda]C ++ 14 -
[expr.prim.lambda]C ++ 17 -
[expr.prim.lambda]Lambda Expressions in C ++ | Microsoft docsSimon Brand -
Passing overload sets to functionsJason Turner -
C ++ Weekly - Ep 128 - C ++ 20's Template Syntax For LambdasJason Turner -
C ++ Weekly - Ep 41 - C ++ 17's constexpr Lambda SupportWe invite everyone to the traditional
free webinar at the rate that will take place tomorrow, June 14th.