C++ Lambda

Chapter V Lambda in C+20

Estimated reading time of 6 minutes 19 Views

Change of Lambda expression in C++20:

  • New option to capture the 'this' pointer.
  • Template Lambdas.
  • Use of concepts to improve broad Lambdas.
  • How to use Lambdas in 'constexpr' algorithms.
  • How to further simplify the load mode.

Lambda 表达式语法更新

In C+20, the grammar of Lambda expression has changed:

  • You can now add 'consteval ' to the list of parameters.
  • You can select the end of the specified template.
  • After the type of tail return, you can also add the 'requires ' declaration.
  • Updated Syntax:
  • the lambda introducer with an optional capture list | | template parameter list(optional) | | trailing return type | | | v v v []<tparams> () specifiters exception attr -> ret { /* code */ } ^ ^ ^ | | | | | lambda body | | | mutable, constexpr, consteval, noexcept, attributes(optional) | parameter list (optional when no specifiers added)

C++20 中的 Lambda 表达式更变一览

In C+20, Lambda has the following new characteristics:

  • Allow '[=, this] 'as a Lambda capture method and discard the hidden capture 'this' by '[=]'.P0409R2andP0806)。
  • Support package expansion in initializing Lambda such as '...args = std: move(args)]()}()P0780)。
  • Support 'static', 'thread_local' variable and structured lambda captureP1091)。
  • Template Lambda (also supports conceptual constraints)P0428R2)。
  • Simplify hidden Lambda captureP05881R1)。
  • Unstated Lambda() supports default construction and grantP0624R2)。
  • Support Lambda in the unclaimed contextP0315R4)。
  • "constexpr" algorithm improvements, especiallyP0202、 P0879andP1645

示例:在 Lambda 表达式中的结构化绑定

#include <tuple>
#include <string>

auto GetParam(){
    return std::tuple { std::string{"Hello world"}, 42};
}

int main(){
    auto[x, y] = GetParams();
    const auto ParamLength = [&x, &y]() { return x.length() + y;}();
    return ParamLength;
}
C++

C+20 limits 'this' capture if '[=]' is used in the method:

struct Baz{
    auto foo(){
        return [=] { std::cout << s << '\n'; };
    }
    std::string s;
};
C++

In CLang there are the following warnings:

Implicit capture of 'this' with a capture default of '=' is deprecated
C++

The reason for the warning is that even if '[=]' is used, 'this' will eventually be captured as a pointer. It would be better to specify the means of capture, such as '[=, this] ' or '[=,*this]'.

consteval Lambdas

The `constexpr ' introduced in C++11 allows functions to be performed at the compilation stage, but these functions may also be performed at the time of operation, however, in some cases it may be necessary to limit the function manually to only when compiled. Thus, C+20 introduced a new keyword 'consteval' to create functions that can only be valued when they are compiled, also known as "instant functions".

一个使用 consteval 函数示例:

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

Explanation: In the above code, the 'consteval' has been applied after the list of Lambda parameters, which is very similar to the use of 'constexpr', the difference being that if the 'const ' amplifier is removed, 'constexpr' Lambda can still work while running, and even the Lambda expression cannot be compiled.

By default, if the Lambda function meets the rules of the 'constexpr ' function, the compiler will implicitly mark the call operator as 'constexpr'. However, 'consteval' imposes stricter rules — these two keywords cannot be used simultaneously.

捕获参数包

使用 Lambda 表达式捕获参数包

template<typename... Args>
void call(Args... args){
    auto ret = [...capturedArgs = std::move(args)](){};
}
C++

The code could not be compiled until C+20, and if the problem was to be bypassed, the parameters must be packaged in a separate group of blocks.

Use a folding expression to output each captured object:

#include <iostream>
#include <memory>

template<class First, class... Args>
void captureTest(First&& first, Args&&... args){
    const auto printer = [first = std::move(first), ...capturedArgs = std::move(args)]{
        std::cout << first;
        ((std::cout << ", " << capturedArgs), ...);
        std::cout << '\n';
    };
    printer();
}

int main(){
    auto ptr = std::make_unique<int>(10);
    captureTest(std::move(ptr), 2, 3, 4);
    captureTest(std::move(ptr), 'a', 'b');
}
/* output:
 * 0xbd23b923d0, 2, 3, 4
 * 0, a, b
 */
C++
  • Explanation: In the above code, a `printer' object is used to capture the object instead of forwarding it as a Lambda parameter, and a `unique_ptr' is declared, with a second output point value of 0 when it is passed to Lambda twice, because the pointer has lost ownership of the memory block.

模板 Lambda 表达式

The introduction of the generic Lambda expression in C++14 means that the parameter declared with 'auto' is actually a template parameter:

[](auto x) { x; }
C++

For the above-mentioned Lambda, the compiler produces a call operator corresponding to the following template methods:

template<typename T>
void operator()(T x) { x; }
C++

However, in C++ it was not possible to change this template parameter or to use the "real" template parameter, which was improved in C++, allowing for the definition of Lambda using the template parameter.

限制 Lambda 的参数类型

If you want to limit Lambda to accepting only some kind of `vector', you can write a common Lambda:

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

However, if the `int ' parameter is used to call it (e.g. `foo (10); ' ), it may be called because there is no function for the reference type.

In C+20, you can specify when to call with template parameters:

auto foo = []<typename T>(std::vector<T> const& vec){
    std::cout << std::size(vec) << '\n';
    std::cout << vec.capacity() << '\n';
};
C++

The above-mentioned Lambda expression will be interpreted as a call operator with template parameters:

template<typename T>
void operator()(std::vector<T> const& vec) { ... }
C++

Template parameters after the capture list '[] ', if the 'int ' type is used to call it (`foo (10); ' ), the error will be shown as follows:

note: mismatched types 'const std::vector<T>' and 'int'
C++

In a pan-Lambda, there is only one variable without its template type, and access to this type requires the use of 'decltype(x)' (Lambda expression for parameter 'auto x'), which makes the code lengthy and complex:

auto f = [](auto const& x){
    using T = std::decay_t<decltype(x)>;
    T copy = x;
    T::static_function();
    using Iterator = typename T::iterator;
}
C++

In C+20, it can be written as follows:

auto f = []<typename T>(T const& x){
    /* leave out 'delctype' */
    T copy = x;
    T::static_function();
    using Iterator = typename T::iterator;
}
C++

At this point, the code is as follows:

[](auto x) { x; }
C++

The type of parameter to be obtained in this way requires the use of 'decrype':

using T = std::decay_t<decltype(x)>;
C++

However, in C+20, you can access the template parameters directly.

Perfectly relay applications in a broad variable parameter's Lambda expression: in C++17:

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

If this method is used to access the type of template parameters above, use 'decltype()', and in C+20 this step can be saved by a template Lambda:

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

概念(concept) 与 Lambda 表达式

Concepts (concepts) is an innovative way of preparing templates that allows for the setting of constraints for template parameters, thus increasing the readability of codes, increasing the speed of their compilation and prompting more explicit errors:

// 定义一个概念:
template<class T>
concept SignedIntegral = std::is_integral_v<T> && std::is_signed_v<T>;

// 使用概念:
template<SignedIntegral T>
void signedIntsOnly(T val) { }
C++
  • Explanation: In the above code, a concept was created that describes a "signed and integer type" (the type characteristics need to be used here) and then uses it to define a template function that only supports the type that matches the concept. There is no direct use of 'typenameT' here, but rather a direct reference to the name of the concept.

与 Lambda 表达式的关系

The key is this simple syntax and the binding of the parameters of the `auto' template.

简化与简化语法

The simple syntax of the concept led to the omission of the 'template ' in the preparation of the templatePart of:

void myTemplateFunc(auto param) { }
C++

Or use a "auto" of restraint:

void signedIntsOnly(SignedIntegral auto val) { }
void floatsOnly(std::floating_point auto fp) { }
C++

The syntax is similar to the generic Lambda in C++14:

void myTemplateFunction(auto val){ }
C++

For Lambda expression, this concise style can be used and additional limitations imposed on broad Lambda parameters:

auto genLambda = [](SignedIntegral auto param) { return param * param + 1; };
C++

Here the concept of `SignedIntegral' binds `auto param', making the whole expression more readable than the previous Lambda.

更复杂的示例

Define the concept of a type interface:

// IRenderable 概念,使用 requires 关键字
template<typename T>
concetp IRenderable = requires(T v){
    {v.render()}-> std::same_as<void>;
    {v.getVertCount()}-> std::convertible_to<size_t>;
};
C++

In the code above, a type concept that matches all members with 'render()' and 'getVertCount()' functions is defined and is then used to achieve a broad Lambda:

#include <iostream>
#include <concepts>

template<typename T>
concept IRenderable = requires(T v){
    {v.render()}-> std::same_as<void>;
    {v.getVertCount()}-> std::convertible_to<size_t>;
};

struct Circle{
    void render() { std::cout << "drawing circle\n"; }
    size_t getVertCount() const { return 10; };
};

struct Square{
  void render() { std::cout << "drawing square\n"; }
  size_t getVertCount() const { return 4; };
}

int main(){
    const auto RenderCaller = [](IRenderable auto& obj){
        obj.render();
    };

    Circle c;
    RenderCaller(c);

    Square s;
    RenderCaller(s);
}
C++

无状态的 Lambdas 的变化

In C++11, even the stateless Lambda expression is not built by default. However, this restriction was lifted in C+20:

#include <set>
#include <string>
#include <iostream>

struct Product{
    std::string name;
    int id { 0 };
    double price { 0.0; }
};

int main(){
    const auto nameCmp = [](const auto& a, const auto& b){ return a.name < b.name; };
    const std::set<Product, decltype(nameCmp)> prodSet{
        {"Cup", 10, 100.0}, {"Book", 2, 200.5},
        {"TV set", 1, 2000}, {"Pencil", 4, 10.5}
    };
    for(const auto& elem: prodSet) std::cout << elem.name << '\n';
}
C++
  • Explanation: In the above code, a `set' is used to store the `Product' list and, for comparison purposes, a non-state Lambda expression is passed to compare their string names.

If C++17 is used to compile the above code, there is an error 'the default constructor has been deleted ':

note: a lambda closure type has a deleted default constructor
  824 |     const auto nameCmp = [](const auto& a, const auto& b){ return a.name < b.name; };
note:   when instantiating default argument for call to 'std::set<_Key, _Compare, _Alloc>::set(std::initializer_list<_Tp>, const _Compare&, const allocator_type&) [with _Key = Product; _Compare = const main()::<lambda(const auto:1&, const auto:2&)>; _Alloc = std::allocator<Product>; allocator_type = std::allocator<Product>]'
  828 |     };
C++

C++20 中的改进

In C+20, we can store a non-state Lambda, or even copy them:

template<typename F>
struct Product{
    int id{ 0 };
    double price { 0.0 };
    F predicate;
};

int main(){
    const auto idCmp = [](const auto& a) noexcept{
        return a.id != 0;
    };

    Product p {10, 10.0, idCmp};
    [[maybe_unused]] auto p2 = p;
}
C++
  • Explanation: In the above code, a `Product' structure is defined that contains a Lambda expression as a term (predicate). In C+20, this Lambda can be stored and copied without status, without causing any error.

更高级地未求值上下文(Unevaluated Contexts)

A number of changes related to high-level examples have also been introduced in C+20, such as the unrequested context. Combining the non-state Lambda default tectonicity, the following can be done:

std::map<int, int, decltype([](int x, int y){ return x > y; }) > map;
C++

In the above-mentioned code, you can state in the 'map' container the directly specified Lambda expression and use it as a callable type for the comparator.

Lambda 表达式和 'constexpr' 算法

In C++, most of the standard algorithms are marked as 'constexpr', which makes 'constexpr' Lambdas more convenient.

示例1:使用 'std::accumulate' 与自定义 'constexpr' Lambda

#include <array>
#include <numeric>

int main(){
    constexpr std::array arr{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    static_assert(std::accumulate(std::begin(arr), std::end(arr), 0,
        [](auto a, auto b) noexcept{
            return a + b;
        }) == 5);
    return arr[0];
}
C++
  • Explanation: The `constexpr' Lambda in the above code allows the code to be calculated at the time of compilation, thereby improving running time performance.

示例2:将 'constexpr' Lambda 传递给自定义函数

#include <array>
#include <algorithm>

constexpr auto CountValues(auto container, auto cmp){
    return std::count_if(std::begin(container), std::end(container), cmp);
}

int main(){
    constexpr auto minVal = CountValues(std::array{-10, 6, 8, 4, -5, 2, 4, 6},
        [](auto a) { return a >= 0; });

    return minVal;
}
C++
  • Explanation: In the code above, a `constexpr' function `CountValues' is defined, which accepts a comparator or word for the `count_if' algorithm, which can be calculated by the compiler.

C++20 中重载模式(Overloaded Pattern)的更新

Multiple Lambda expressions were derived and exposed by reloading, which was simplified in C+20.

A simpler syntax can now be used as a result of the C+20 type template parameter extrapolation (CTAD) update. This is because C++ expands on CTAD and automatically handles aggregates, which means that there is no need for a self-defined extrapolation rule. Defines a simple type:

template<typename T, typename U, typename V>
struct Triple{
    T t;
    U u;
    V v;
};
C++

In C+20, write directly:

Triple ttt{10.0f, 90, std::string{"hello"}};
C++

Among them, the compiler automatically extrapolates 'T' as 'float', 'U' as 'int', 'V' as 'std:string'.

C++20 中的重载模式

In C+20, you can simplify the load mode by:

template<class... Ts>
struct overload: Ts...{
    using Ts::operator()...;
};
C++
  • Explanation: The code uses the new C+20 feature, making it easier to create and group them from multiple Lambda expressions.

Leave a Comment

Share this Doc

Chapter V Lambda in C+20

Or copy link

CONTENTS
Remember to rest.