C++17 In Detail

04 August 2020

Lambda Week: Syntax changes, C++11 to C++20


Let’s start the week with Lambda Expressions. The plan is to have a set of concise articles presenting core elements of lambda expressions. Today you can see how the syntax has evolved since C++11 and what the latest changes in C++20 are.

The Series

This blog post is a part of the series on lambdas:

Syntax in C++11

The first iteration of lambdas!

In a basic form they have the following syntax:

[]() specifiers exception attr -> ret { /*code; */ }
  1. [] - introduces the lambda expression, capture clause
  2. () - the list of arguments, like in a regular function, optional if specifiers/exception list is empty
  3. specifiers/exception/attr - mutable, noexcept - additional specifiers
  4. ret - trailing return type, in most cases not needed as the compiler can deduce the type
  5. /* code; */ - the body of the lambda

You can read the spec located under N3337 - the final draft of C++11: [expr.prim.lambda].

Some example:

// 1. the simplest lambda:
[]{};

// 2. with two params:
[](float f, int a) { return a * f; };
[](int a, int b) { return a < b; };

// 3. trailing return type:
[](MyClass t) -> int { auto a = t.compute(); print(a); return a; };

// 4. additional specifiers:
[x](int a, int b) mutable { ++x; return a < b; };
[](float param) noexcept { return param*param; };
[x](int a, int b) mutable noexcept { ++x; return a < b; };

// 5. optional ()
[x] { std::cout << x; }; // no () needed
[x] mutable { ++x; };    // won't compile!
[x]() mutable { ++x; };  // fine - () required before mutable
[] noexcept { };        // won't compile!
[]() noexcept { };      // fine

Syntax in C++14

In C++14 the “high level” syntax hasn’t changed much, but the capture clause allows you perform “capture with initialiser”, and the parameter list can take auto arguments (it means generic lambdas).

Additionally, the return type of a lambda expression follows the rules of a regular function return type deduction (auto), so in short, compilers are smarter now.

You can see the specification in N4140 and lambdas: [expr.prim.lambda].

Some examples:

The first one with a capture with an initialiser:

#include <iostream>

int main() {
    int x = 30;
    int y = 12;
    const auto foo = [z = x + y]() { std::cout << z << '\n'; };
    x = 0;
    y = 0;
    foo();
}

As you can see above the compiler can now create member variables for closure type from expressions like z = x + y.

And another significant change is a generic lambda which supports auto as an argument.

const auto foo = [](auto x, auto y) { /*...*/ };

Syntax in C++17

Since C++17 you can now use constexpr as an additional specifier for the lambda.

[]() specifiers exception attr -> ret { /*code; */ }
  1. [] - introduces the lambda expression, capture clause
  2. () - the list of arguments, like in a regular function, optional if specifiers/exception list is empty
  3. specifiers/exception/attr - mutable, noexcept, constexpr
  4. ret - trailing return type
  5. /* code; */ - the body of the lambda

Some example:

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

And additionally the capture syntax supports*this:

struct Baz {
    auto foo() {
        return [*this] { std::cout << s << std::endl; };
    }

    std::string s;
};

Another thing to have in mind is that in C++17 the dynamic exception specification is removed so in practice you can only use noexcept to mark functions and lambdas.

Syntax in C++20

Since C++20 you can now use consteval as an additional specifier for the lambda, and what’s more, you can specify a template tail!

[]<tparams>() specifiers exception attr -> ret requires { /*code; */ }
  1. [] - introduces the lambda expression, capture clause
  2. <tparams> - template tail, template arguments
  3. () - the list of arguments, like in a regular function, optional if specifiers/exception list is empty
  4. specifiers/exception/attr - mutable, noexcept, constexpr, consteval
  5. ret - trailing return type
  6. /* code; */ - the body of the lambda

Some examples:

int main() {
    const int x = 10;
    auto lam = [](int x) consteval { return x + x; };
    return lam(x);
}

Template lambdas and perfect forwarding:

auto ForwardToTestFunc = []<typename ...T>(T&& ...args) {
  return TestFunc(std::forward<T>(args)...);
};

Summary

This text aimed to illustrate the basic principles of the lambda syntax. But what's your experience? When and how do you use lambdas? or maybe you prefer to use functors? Let us know in comments below the article.

Next time

In the next article, you’ll see how to capture things from the external scope. See here: "Capturing things".

See More in Lambda Story

If you like to know more, you can see my book on Lambdas! Here are the options on how to get it and join 1000 of readers:

If you want to get additional C++ resources, exlusive articles, early access content, private Discord server and weekly curated news, check out my Patreon website: (see all benefits):

© 2017, Bartlomiej Filipek, Blogger platform
Disclaimer: Any opinions expressed herein are in no way representative of those of my employers. All data and information provided on this site is for informational purposes only. I try to write complete and accurate articles, but the web-site will not be liable for any errors, omissions, or delays in this information or any losses, injuries, or damages arising from its display or use.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.