Lambdas: from C ++ 11 to C ++ 20. Part 2

Original author:
  • Transfer
Hi, Habrovsk. In connection with the start of recruitment into a new group at the course “C ++ Developer” , 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 lambdas in terms 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 language standards. I also mentioned a few borderline cases.
Now it's time to move on to C ++ 17 and take a look at the future (very close!): C ++ 20.


A little reminder: the idea for 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 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 talked about the lambda expressions as follows:

  • Basic syntax
  • Lambda type
  • Call operator
  • Capturing variables (mutable, global, static variables, class members and this pointer, move-able-only objects, storing constants):

    • Return type
    • IIFE - Immediately Invoked Function Expression
    • Conversion to a function pointer
    • Return type
    • IIFE - Immediately Invoked Expressions
    • Convert to a function pointer
  • Improvements in C ++ 14

    • Return type output
    • Capture with initializer
    • Capture a member variable
    • Generic 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 ++ 17

Standard (draft before publication) N659 section on lambdas: [expr.prim.lambda] . C ++ 17 brought two significant improvements to lambda expressions:

  • constexpr lambda
  • Capture * this

What do these innovations mean for us? Let's figure it out.

constexpr lambda expressions

Starting with C ++ 17, the standard implicitly defines the operator()lambda type as constexprif possible:
From expr.prim.lambda # 4 :
The function call operator is a constexpr function if the declaration of the condition parameter of the corresponding lambda expression is followed by constexpr, or it satisfies the requirements for the constexpr function.

For instance:

constexpr auto Square = [] (int n) { return n*n; }; // implicitly constexpr
static_assert(Square(2) == 4);

Recall that in C ++ 17, a constexprfunction must follow these rules:

  • it should not be virtual;

    • its return type must be a literal type;
    • each of the types of its parameters must be a literal type;
    • its body must be = delete, = default or a compound statement that does not contain
      • asm definitions
      • goto expressions,
      • tags
      • try block or
      • the definition of a non-literal variable, a static variable, or a streaming memory variable for which initialization is not performed.

What about a more practical example?

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);

You can play around with the code here: @Wandbox

The code uses constexprlambda, and then it is passed to a simple algorithm SimpleAccumulate. The algorithm uses several elements of C ++ 17: additions constexprto std::array, std::beginand std::end(used in a loop forwith a range) are now also constexpr, so this means that all the code can be executed at compile time.

Of course, this is not all.

You can capture variables (provided 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 in place with each use (if you do not pass it on or take the address of this name).

But please tell me if you know the official rules for this behavior. I found only (from cppreference ) (but I can’t find it in the draft ...)

(Translator's note: as our readers write, I probably mean substituting the value of 'x' in every place where it is used. Change it for sure impossible.) A

lambda expression can read the value of a variable without capturing it if the variable
* has a constant non-volatileinteger or enumerated type and has been initialized with constexpror
* is constexprand does not have mutable members.

Get ready for the future:

In C ++ 20, we will have constexprstandard algorithms and, possibly, even some containers, so constexprlambdas will be very useful in this context. Your code will look the same for the run-time version as well as for the version constexpr(compile-time version)!

In a nutshell:

constexprlambda allows you to conform to boilerplate programming and possibly have a shorter code.

Now let's move on to the second important feature available in C ++ 17:

Capture of * this
Capture * this

Do 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 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:

struct Baz {
    auto foo() {
        return [*this] { std::cout << s << std::endl; };
    std::string s;
int main() {
   auto f1 = Baz{"ala"}.foo();
   auto f2 = Baz{"ula"}.foo(); 

You can play around with the code here: @Wandbox

Capturing the required member variable using capture with the initializer 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 code safer to capture thiswith an initializer is:

auto foo() {
    return [self=*this] { self.print(); };
Но в C ++ 17 это можно сделать чище:
auto foo() {
    return [*this] { print(); };

One more thing:

Note that if you write [=]in a member function, it is thiscaptured implicitly! This may lead to errors in the future ... and it will become obsolete in C ++ 20.

So we come to the next section: the future.

The future with C ++ 20

In C ++ 20, we get the following functions:

  • Allow [=, this]as lambda capture - P0409R2 and cancel the implicit capture of this via [=]- P0806
  • Package Extension lambda init-capture: ... args = std::move (args)] () {}- P0780
  • static thread_localand lambda capture for structured bindings - P1091
  • lambda pattern (also with concepts) - P0428R2
  • Simplifying Implicit Lambda Capture - P0588R1
  • Constructive and assignable lambda without saving default state - P0624R2
  • Lambdas in an uncalculated context - P0315R4

In most cases, the newly introduced functions “clear” the lambda use, and they allow for some advanced use cases.

For example, with P1091 you can capture a structured binding.

We also have clarifications related to capturing this. In C ++ 20, you will get a warning if you capture [=]in a 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 this, you must write [=, this].

There are also changes related to advanced use cases, such as stateless contexts and stateless lambdas that can be constructed by default.

With both changes you can write:

std::map y; })> map;

Read the motives for these features in the first version of the sentences: P0315R0 and P0624R0 .

But let's look at one interesting feature: lambda templates.

Lambdas template

In C ++ 14, we got generalized lambdas, which means that parameters declared as auto are template parameters.

For lambda:

[](auto x) { x; }

The compiler generates a call statement that matches the following boilerplate method:

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 general lambda:

auto foo = [](const auto& vec) { 
        std::cout<< std::size(vec) << '\n';
        std::cout<< vec.capacity() << '\n';

But if you call it with an int parameter (for example, foo(10);), you may get some hard-to-read error: In instantiation of 'main():: [with auto:1 = int]':   required from here 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 = [](std::vector const& vec) { 
        std::cout<< std::size(vec) << '\n';
        std::cout<< vec.capacity() << '\n';

The above lambda allows the template call statement:

void operator(std::vector 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' and 'int'

You can play around with the code here: @Wandbox

In the above example, the compiler can warn us about inconsistencies in the lambda interface than in the code inside the body.

Another important aspect is that in a universal lambda you only have a variable, not its template type. Therefore, if you want to access it, you must use decltype (x) (for a lambda expression with the argument (auto x)). This makes some code more verbose and complicated.

For example (using the code from P0428):

auto f = [](auto const& x) {
    using T = std::decay_t;
    T copy = x;
    using Iterator = typename T::iterator;

Now you can write as:

auto f = [](T const& x) {
    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 use case for you. This technique is even possible in C ++ 14. So read on.

Bonus - LIFTing with lambdas

Currently, we have a problem when you have function overloads and you want to pass them to standard algorithms (or anything that requires some called object):

// two overloads:
void foo(int) {}
void foo(float) {}
int main()
  std::vector 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::iterator, std::vector::iterator,
   std::for_each(vi.begin(), vi.end(), foo);

However, there is a trick in which we can use a lambda and then call the desired overload function.

In 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 type a little more:

#define LIFT(foo) \
  [](auto&&... x) \
    noexcept(noexcept(foo(std::forward(x)...))) \
   -> decltype(foo(std::forward(x)...)) \
  { return foo(std::forward(x)...); }

Pretty complicated code ... right? :)

Let's try to decrypt it:

We create a generalized lambda and then pass all the arguments that we get. To determine it correctly, we need to specify noexcept and the type of the return value. That's why we have to duplicate the calling code - in order to get the correct types.
Such a LIFT macro works in any compiler that supports C ++ 14.

You can play around with the code here: @Wandbox


In this post we looked at significant changes in C ++ 17, and reviewed the new features in C ++ 20.

You may notice that with each iteration of the language, lambda expressions mix with other C ++ elements. For example, before C ++ 17, we could not use them in the context of constexpr, but now it is possible. Similarly with generic lambdas starting with C ++ 14 and their evolution into 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!


C ++ 11 - [expr.prim.lambda]
C ++ 14 - [expr.prim.lambda]
C ++ 17 - [expr.prim.lambda]
Lambda Expressions in C ++ | Microsoft Docs
Simon Brand - Passing overload sets to functions
Jason Turner - C ++ Weekly - Ep 128 - C ++ 20's Template Syntax For Lambdas
Jason Turner - C ++ Weekly - Ep 41 - C ++ 17's constexpr Lambda Support

We invite everyone to the traditional free webinar for the course, which will be held tomorrow June 14th.

Also popular now: