C++ Lambda

Chapter II Lambda in C++11

Estimated reading time of 20 minutes We've got 78 stories.

Final draft on C++11N3337The Lambda expression reads as follows:
Lambda Express:

  1. Purpose and Syntax:
    • Lambda expression provides a simple way to create a simple function object.
    • Example:
    • #include <algorithm> #include <cmath> void abssort(float* x, unsigned N){ std::sort(x, x + N, [](float a, float b){ return std::abs(a) < std::abs(b); }); }
    • Syntax:
    • lambda-expression: lambda-introducer lambda-declaratoropt compound-statement lambda-introducer: [ lambda-captureopt ] lambda-capture: capture-default capture-list capture-default, capture-list lambda-default: & = lambda-list: capture-opt capture-list, capture ...opt capture: identifier & identifier this lambda-declarator: ( parameter-declaration-clause ) mutableopt exception-specificationopt attribute-specifier-seqopt trailing-return-typeopt
  2. Results of calculations for Lambda expression
    • Calculating the Lambda expression generates a prvalue temporary object (closure object).
    • Lambda expression should not appear in uncalculated operations.
    • The behaviour of a closed object is similar to that of a function object.
  3. Lambda Expression Type
    • Lambda expression is the only unnamed non-joint type and becomes closed type.
    • The closed-pack type is stated in the smallest block, class or name spatial domain that contains the corresponding Lambda expression.
  4. Default behaviour of Lambda expression
    • If the Lambda expression does not contain the Lambda declarator, the default is '()'.
    • If the Lambda expression does not contain the type of tail return, the default type of tail return is the following:
      • If the composite statement is in ',', returns the expression type.
      • Otherwise, back 'void'.
  5. Functions of closed type call operators
    • Closed type has a common inline function calling operator, and its parameters and the type of return are described by the parameter statement in the Lambda expression and the tail-return type.
    • If there is no 'mutable' after the parameter statement, the function calls the operator as 'cont'.
    • The operator is neither virtual nor declared `volatile'.
    • Any anomaly in the Lambda Declarant applies to the function for which the operator is called.
  6. Function pointer conversion without captured Lambda expression
    • The uncaptured Lambda expression has a public, non-virtual `const' conversion function, which is converted to a function pointer with the same parameter and return type.
    • The conversion function returns a value that is the address of a function that has the same effect as a function that calls a closed-pack type.
  7. A composite statement for Lambda expression
    • The composite statement of the Lambda expression generates a function that calls the operator, but the composite statement is considered part of the Lambda expression in terms of name search and type determination.
  8. Capture rules
    • If Lambda captures contains the default catch'&', there should not be '&' before the identifier in the catch list.
    • If Lambda captures include the default capture'=', there should be no `this' before the identifier in the catch list and there should be'&' before each identifier.
    • The identifier or 'this' in the catch list cannot be repeated.
  9. Local Lambda Expression
    • If the smallest closed field of the Lambda expression is a block field, it is called the local Lambda expression; otherwise, there should not be a catch list in the Lambda introduction.
  10. Find identifiers in the capture list
    • The identifier in the capture list is searched using the unqualified name search rule, and each search should find a variable with automatic storage duration stated in the local Lambda expression's arrival field.
  11. Invisible capture
    • If the Lambda expression has a catch default and a composite statement odr-use `this' or a variable with automatic storage duration, these entities are hidden.
  12. Capture of entities
    • If the entity is caught visibly or implicitly, the entity is called caught. The captured entity is odr-used in a field containing Lambda expression.
  13. Lambda expression in default parameters
    • The Lambda expression that appears in the default parameter should not explicitly or implicitly capture any entity.
  14. Capture by Value
    • An entity captures by value if it is hidden and captures the default value is '=', or is visibly captured and captures do not include '&'.
  15. Capture by Reference
    • Entities capture by reference if it is hidden or visible, but not by value.
  16. Capture conversion of embedded Lambda expression
    • If a Lambda expression m2 captures an entity and the entity is captured by its directly closed Lambda expression m1, the m2 capture is converted according to the M1 capture method.
  17. id expression captured by value
    • The id expression of each value-based entity is converted to access to the corresponding unnamed data member in the closed-pack type.
  18. capture in decltype operator
    • x in decltype(x)) is considered an access to the corresponding data member in the closed-pack type.
  19. Constructive functions and grant operators of closed type
    • The closed-pack type associated with the Lambda expression has the deleted default tectonic function and the deleted copying grant operator. It has a copy construction function for a hidden declaration, and it may have a moving tectonic function for a hidden declaration.
  20. Parsing function of closed type
    • The type of closed package associated with the Lambda expression has a parsing function with a hidden declaration.
  21. Initialization of captured entities
    • In assessing the Lambda expression, each corresponding non-static data member of the closed object that is captured by value is used for direct initialization.
  22. Life cycle captured by reference
    • Undefined behaviour may result if a function is called at the end of the life cycle of the entity captured by reference.
  23. Expand captured package
    • After capture, follow the ellipsis to indicate that the package is expanded.

Supplement: Mobile tectonic functions and mobile grant operators

  • Example of realization of moving construction function:
class MyClass{
  int* data;

  MyClass(int size): data(new int[size]){ }

  MyClass(MyClass&& other/* 此处接受一个右值 */) noexcept: data(other.data){
    // 将其他对象的数据指针置为空,表示资源所有权已经转移
    other.data = nullptr;
  }

  ~MyClass(){
      delete[] data;
  }

  MyClass(const MyClass&) = delete;
  MyClass& operator=(const MyClass&) = delete;
};

int main(){
  MyClass obj1(10);   // 调用普通构造函数
  MyClass obj2 = std::move(obj1); // 调用移动构造函数

  // 此时 ojb1.data 为 nullptr,资源所有权已经转移给 obj2
}
C++
  • The purpose of moving the construction function:
    • Improved performance: When dealing with large data structures or resource-intensive objects, mobile tectonic functions can significantly reduce unnecessary in-depth copying operations and thus improve procedural performance.
    • Resource management: Mobile tectonic functions can manage resource transfer more efficiently in achieving resource management classes (e.g., smart pointers, container classes).
  • When will the mobile construction function be called:
    • When a temporary object (right value) is used to initialize another object.
    • If return value optimization (ROV) is enabled when a local object is returned, a mobile construction function may be called.
    • When using a standard library function such as 'std:move' to convert an object to a right reference.
  • Note:
    • Mobile tectonic functions are usually performed in conjunction with mobile grant operators to ensure the correct behaviour of the object under the moving semantic.
    • When moving a construction function, it is usually necessary to disable the copying of the construction function and the copying of the grant operator to avoid unnecessary copies.
  • Move grant operator
    • #include <iostream> #include <utility> MyClass{ public: int* data; MyClass(int size): data(new int[size]) { } MyClass(MyClass&& other) noexcept: data(other.data) { other.data = nullptr; } MyClass& operator=(MyClass&& other) noexcept{ if(this != other){ // 释放当前对象持有的资源 delete[] data; // 获取源对象的资源 data = other.data; // 将源对象的资源指针置为空 other.data = nullptr; } return *this; } ~MyClass(){ delete[] data; } MyClass(const MyClass&) = delete; MyClass& operator=(const MyClass&) = delete; }; int main(){ MyClass obj1(10); MyClass obj2(20); obj2 = std::move(obj1); /* 此时obj1.data为nullptr, obj2.data为obj1.data的地址 */ }

本章的主要内容

  • Basic syntax for Lambda
  • How to Capture Variables
  • How to capture a member variable
  • Return type for Lambda
  • What's a closed object?
  • How to convert Lambda to a function pointer and use it in a C-style API
  • What is IIFE?
  • How to inherit from Lambda and why it works

Lambda 表达式的语法

The syntax structure of the Lambda expression is as follows:

[]() specifiers exception attr -> ret { /* code */ }
^ ^  ^                            ^
| |  |                            |
| |  |                            optional: trailing return type
| |  |
| |  optional: mutable, exception specification or noexcept, attributes
| |
| parameter list (optional when no specifiers added)
|
Lambda introducer with an optional capture list
C++

On the explicit definition of Lambda expression in C++:

  1. Results of calculations for Lambda expression
    • Calculating the Lambda expression generates a temporary object (closure object) that is prvalue (pure rvalue, pure right value).
    • Lambda expression should not appear in uncalculated operations.
    • The behaviour of a closed object is similar to that of a function object.
  2. Lambda Expression Type
    • Lambda expression is the only unnamed non-joint type and becomes closed type.
    • The closed-pack type is stated in the smallest block, class or name spatial domain that contains the corresponding Lambda expression.

Lambda 表达式的一些示例

  1. Simplest Lambda expression:[]{};This Lambda expression only requires '[]' and empty '{}' as a function. The list of parameters '()' is optional.
  2. Lambda expression with two arguments:[](float f, int a) { return a * f; } [](int a, int b) { return a < b; }This is one of the most common types of Lambda expression where parameters are passed by '()' part, which is the same as the normal function and does not require specified return type, and the compiler automatically extrapolates.
  3. Back with tail type Lambda expression:[](MyClass t) -> int { auto a = t.compute(); print(a); return a; };This Lambda expression defines the return value type, and the tail return type also applies to the general function statement from C++11. Supplement: Type of tail return
    • End-return type syntax: The tail-return type uses the keyword `auto' and >' sign.
    • auto functionName(parameters) -> returnType
    Example:
    1. End-to-end type of simple function
    auto add(int a, int b) -> int{ return a + b; }
    1. End-return type of template function
    template<typename T, typename U> auto add(T a, U b) -> decltype(a + b){ return a + b; }
    1. End-return type of Lambda expression
    auto lambda = [](int a, int b) -> int { return a + b; }
  4. Additional amplifiers:[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; };In this example, the Lambda function has been preceded by the addition of a qualifier: 'mutable' (to change the captured variable) and 'noexcept', and 'mutable noxcept' in the third Lambda expression is a fixed order, which cannot be compiled if written as 'noexcaptable', and 'mutable' and 'noexcept' need to be added to the expression.
  5. For optional '()':
[x] { std::cout << x; } // 不需要'()'
[x] mutable { ++x; }; // 编译错误,因为mutable存在,故需要'()'
[x]() mutable { ++x; }; // 编译正常
[] noexcept { };    // 编译错误,因为noexcept存在,故需要'()'
[]() noexcept { }; // 编译正常
C++

It also applies to `constexpr' and `consteval' in C++ 17 and C++ 20.

属性

The syntax of the Lambda expression also allows the use of attributes introduced in the form of '[[attr_name]'. However, if the attribute is applied to Lambda, it is applied to the type of operator called, not to the operator itself. Try the following expression:

auto myLambda = [](int a) [[nodiscard]] { return a * a; };
C++

Clang produces the following error message:

error: 'nodiscard' attribute cannot be applied to types
C++

编译展开

For example: for_each:

#include <iostream>
#include <algortihm>
#include <vector>

int main(){
    // 定义一个functor与下面一般的 Lambda 表达式做对比
    struct{ /* anonymous */
        void operator()(int x) const{
            std::cout << x << 'x';
        }
    } someInstances;

    const std::vector<int> v{1, 2, 3, 4, 5};
    std::for_each(v.cbegin(), v.cend(), someInstance);
    std::for_each(v.cbegin(), v.cend(), [](int x){
        std::cout << x << '\n';
    });
}
C++

In this example, the compiler will have the following Lambda expression:

[](int x) { std::cout << x << '\n'; }
C++

Convert to an anonymous imitation function in the following simplified form:

struct{
    void operator()(int x){
        std::cout << x << '\n';
    }
}someInstances;
C++

The specific expansion of the compiler is as follows:

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
  std::vector<int, std::allocator<int> > v = std::vector<int, std::allocator<int> >{std::initializer_list<int>{1, 2, 3, 4, 5}, std::allocator<int>()};

  class __lambda_7_39
  {
    public:
    inline /*constexpr */ void operator()(int x) const
    {
      std::operator<<(std::cout.operator<<(x), '\n');
    }

    using retType_7_39 = void (*)(int);
    inline constexpr operator retType_7_39 () const noexcept
    {
      return __invoke;
    };

    private:
    static inline /*constexpr */ void __invoke(int x)
    {
      __lambda_7_39{}.operator()(x);
    }

    public:
    // inline /*constexpr */ __lambda_7_39(__lambda_7_39 &&) noexcept = default;
    // /*constexpr */ __lambda_7_39() = default;

  };

  std::for_each(v.cbegin(), v.cend(), __lambda_7_39{});
  return 0;
}
C++

Lambda 表达式的类型

编译器生成闭包类型

  • The compiler produces a single closed type (close type) for each Lambda expression, which cannot be predicted.
  • Therefore, `auto' (or `decrype') needs to be used to extrapolate type
auto myLambda = [](int a) -> double { return 2.0 * a; };
C++

不同的闭包类型

Even if the two Lambda expressions are identical, their types are different:

auto firstLam = [](int x) { return x * 2; };
auto secondLam = [](int x) { return x * 2; };
C++

The compiler must declare two unique unnamed types for each Lambda:

#include <type_traits>
int main(){
    const auto firstLam = [](int x) { return x * 2; };
    const auto secondLam = [](int x) { return x * 2; };
    static_assert(!std::is_same(decltype(firstLam),
                    decltype(secondType)>::value,
                     "must be different!");
}
C++

In the eyes of the compiler, it is clear that these two Lambda expressions are generated by two different types of closed:

#include <type_traits>

int main()
{

  class __lambda_4_25
  {
    public:
    inline /*constexpr */ int operator()(int x) const
    {
      return x * 2;
    }

    using retType_4_25 = int (*)(int);
    inline constexpr operator retType_4_25 () const noexcept
    {
      return __invoke;
    };

    private:
    static inline /*constexpr */ int __invoke(int x)
    {
      return __lambda_4_25{}.operator()(x);
    }


    public:
    // /*constexpr */ __lambda_4_25() = default;

  };

  const __lambda_4_25 firstLam = __lambda_4_25{};

  class __lambda_5_27
  {
    public:
    inline /*constexpr */ int operator()(int x) const
    {
      return x * 2;
    }

    using retType_5_27 = int (*)(int);
    inline constexpr operator retType_5_27 () const noexcept
    {
      return __invoke;
    };

    private:
    static inline /*constexpr */ int __invoke(int x)
    {
      return __lambda_5_27{}.operator()(x);
    }


    public:
    // /*constexpr */ __lambda_5_27() = default;

  };

  const __lambda_5_27 secondLam = __lambda_5_27{};
  /* PASSED: static_assert(!std::integral_constant<bool, false>::value, "must be different"); */
  return 0;
}
C++

Use 'std::function'

Although the type of Lambda is not known with certainty, it is possible to specify the signature of Lambda and store it in 'std:action':

/* std::function<返回值类型(接受参数类型)> */
std::function<double(int)> myFunc = [](int a) -> double { return 2.0 * a; };
C++

It should be noted that 'std:function' is a heavy-weight object because it deals with all callable objects whose internal mechanisms are more complex, involving type conversion or memory dynamic distribution, and the size is now checked:

#include <functional>
#include <iostream>

int main(){
     const auto myLambda = [](int a) noexcept -> double { return 2.0 * a; };

     const std::function<double(int)> myFunc = [](int a) noexcept -> double { return 2.0 * a; };

     std::cout << "sizeof(myLambda) is " << sizeof(myLambda) << '\n';
     std::cout << "sizeof(myFunc) is " << sizeof(myFunc) << '\n';

     return myLambda(10) == myFunc(10);
}
/* output:
 * sizeof(myLambda) is 1
 * sizeof(myFunc) is 64
 */
C++
  • Since 'my Lambda' is just a non-state Lambda, it is also an empty category with no data on the member fields, and its size is only byte.
  • The version of 'std:Function' is much larger and is 64 bytes (different compilers and compilers and current operating system versions will result in different values), and if possible relying on `auto' inferences to obtain the smallest closed object.

    The compiler must declare two unique unnamed types for each Lambda:

    构造函数与复制

    1. Closed type of Lambda expression
      • According to the C++ norm:
        • The closed type associated with the Lambda expression has a deleted default construction function.
        • There is also a deleted copy grant operator for the closed type.
    2. Unable to default construct and grant, i.e. general copying operation
      As the default construction function and the copying grant operator are disabled, the following code is miscompiled:auto foo = [&x, &y]() { ++x; ++y; }; decltype(foo) fooCopy;_Other Organisererror: no matching constructor for initialization of 'decltype(foo)'
    3. Copy it, Lambda.
      Although it is not possible to construct and assign values to Lambda by default, it is possible to copy Lambda:#include <type_traits> int main(){ const auto firstLam = [](int x) noexcept { return x * 2; }; const auto secondLam = firstLam; static_assert(std::is_same<decltype<firstLam), decltype(secondLam)>::value, "must be the same!"); } /* verify the same type of firstLam and secondLam */
    4. Copy capture variable
      When copying Lambda, its status is also copied. This is particularly important when it comes to capturing variables. The closed-pack type stores the captured variable as a member field, copying Lambda replicates these data member fields.
    5. C+20 Improvements
      In C+20, the non-state Lambda will have the default construction function and the grant operator, making it more flexible and user-friendly.

    Lambda 表达式的调用运算符

    1. Internal realization of the Lambda expression
      • The code prepared in the function of the Lambda expression is compiled into the code in the 'oporator()' function for the closed-pack type.
    2. Default Behaviour
      • In C++11, 'oporator()'default is a 'const' inline member function.
      • Lambda expression:
      • auto lam = [](double param) { /* do something 8*/ };
      • When compiled and expanded:
      • struct __anonymousLambda{ inline void operator()(double param) const { /* do something */ } };

    重载

    1. Rambda expression does not support reloading
      • Lambda expression cannot define "reload" version and cannot accept different parameter types:
      • auto lam = [](double param) { /* do something */ }; auto lam = [](int param) { /* do something */ };The above code could not be compiled because the compiler could not convert the two Lambdas to a single functor and could not redefined the same variable.
    2. Reloading using imitation functions
      • Reload with functor
      • struct MyFunctor{ inline void operator()(double param) const { /* do something */ }; inline void operator()(int param) const { /* do something */ }; };
      • 'MyFunctor' can now deal with the parameters of 'double' and 'int' types.

    Lambda 表达式的修饰符和捕获

    修饰符(modifier)

    1. Default declaration: By default, the call operator ( 'operator()') generated by the Lambda expression is a `const' in-council member function.
    2. Other amplifiers: In C++11, 'mutable' and 'noexceept' can be used to qualify the operator:
      Lambda expression:auto myLambda = [](int a) mutable noexcept { /* do something */ };When compiled and expanded:struct __anonymousLambda{ inline void operator()(int a) noexcept { /* do something */ }; };

    捕获(capture)

    • The catch sentence: '[]' not only introduces Lambda expression, but also contains a list of captured variables called the catch sentence.
    • Capture variable: The capture variable is stored as a member variable (non-static data member) in a closed-pack type and can be accessed in Lambda.

    捕获方式

    • '[ & ]': Captures all auto-stored variables in the field by reference.
    • '[=]': Captures all automatically stored variables in the field by value.
    • '[x, &y]': Visible catch 'x' by value and catch 'y' by reference.
    • '[args...]': Capture template parameter packages by value.
    • '[&args...]': Capture template parameter packages by reference.

    Example of capture:

    int x = 2, y = 3;
    const auto l1 = []() { return 1; };         // 无捕获
    const auto l2 = [=]() { return x; };        // 全部按值捕获
    const auto l3 = [&]() { return y; };        // 全部按引用捕获
    const auto l4 = [x]() { return x; };        // 仅按值捕获 x
    const auto l5 = [&y]() { return y; };       // 仅按引用捕获 y
    const auto l6 = [x, &y]() { return x * y; };// x 按值捕获, y 按引用捕获
    const auto l7 = [=, &x]() { return x + y; };// 全部按值捕获, x 按引用捕获
    const auto l8 = [&, y]() { return x - y; };// 全部按引用捕获, y 按值捕获
    C++

    捕获变量行为

    • Capture by value: Variables are copied when Lambda is defined.
      • Lambda expression:std::string str{"Hello Lambda"}; auto foo = [str]() { std::cout << str << '\n'; } foo();
      • When compiled and expanded:struct _unnamedLambda{ _unnamedLambda(std::string s): str(s) { } void operator()() const { std::cout << str << '\n'; } std::string str; };
    • Capture by reference: The variable uses the current value on Lambda call.
      • Lambda Expressionint x, y = 1; const auto foo = [&x, &y]() noexcept { ++x; ++y; }; foo()
      • When compiled and expanded:struct _unnamedLambda{ _unnamedLambda(int& a, int& b): x(a), y(b) { } void operator()() const noexcept{ ++x; ++y; } int& x; int& y; };
    • Note:
      • Capture mode: While it is easy to capture all variables '[=]' or '[/]', the explicit capture variable is safer and the unintended side effects are avoided.
      • Life cycle: C++ closed packages do not extend the life cycle of the capture reference and ensure that the variable captured during Lambda ' s call still exists.

    mutable 关键字

    By default, the `operator()' of the Lambda expression is marked as `const' and therefore cannot modify the captured variable in Lambda. However, to change this behaviour, a 'mutable 'key word will need to be added after the list of parameters, which actually removes 'const': Lambda expression from the call operator statement of the closed type:

    int x = 1;
    auto foo = [x]() mutable { ++x; };
    C++

    Compile development:

    struct __lambda_x1{
        void operator()(){ ++x; }
        int x;
    };
    C++

    使用 mutable 拷贝捕获两个变量

    #include <iostream>
    
    int main(){
        const auto print = [](const char* str, int x, int y){
            std::cout << str << ": " << x << " " << y << '\n';
        };
    
        int x = 1, y = 1;
        print("in main()", x, y);
    
        auto foo = [x, y, &print]() mutable {
            ++x;
            ++y;
            print("in foo()", x, y);
        };
    
        foo();
        print("in main()", x, y);
    }
    /* output:
     * in main(): 1 1
     * in foo(): 2 2
     * in main(): 1 1
     */
    C++

    In the above code, the Lambda expression captures 'x' and 'y' by copy and 'print' by reference. Within 'foo', the values of 'x' and 'y' are modified, but these changes do not affect the original variables 'x' and 'y' in the external sphere.

    通过引用捕获变量

    When captured by reference, Lambda can modify the quoted value without using 'mutable':

    int x = 1;
    std::cout << x << '\n';
    const auto foo = [&x]() noexcept { ++x; };
    foo();
    std::cout << x << '\n';
    
    /* output:
     * 1
     * 2
     */
    C++
    关于 mutable 和 const

    When using 'mutable', the resulting closed object cannot be marked as 'const' because this will prevent the call to Lambda:

    int x = 10;
    const auto lam = [x]() mutable { ++x; };
    // lam(); 将导致编译出错
    C++

    The reason for the error is that the non-const member function cannot be called on 'cont' objects.

    捕获变量的实例-调用计数器

    Example background: Lambda expression is useful when it requires the use of algorithms in the standard library and changes its default behaviour. In 'std::sort', it is usually possible to customize a comparative function, and now you can introduce a counter to enhance the functions of the comparator. Example code:

    #include <algorithm>
    #include <iostream>
    #include <vector>
    
    int main(){
        std::vector<int> vec = {0, 5, 2, 9, 7, 6, 1, 3, 4, 8};
        size_t compCounter = 0;
    
        std::sort(vec.begin(), vec.end(), [&compCounter](){
            ++compCounter;
            return a < b;
        });
    
        std::cout << "Number of comparisons: " << compCounter << '\n';
        for(const auto& v: vec) std::cout << v << ',';
    }
    /* output:
     * Number of comparisons: 54
     * 0,1,2,3,4,5,6,7,8,9,
     */
    C++

    捕获全局变量

    Use '[=]' in Lambda expression to capture all variables by value, but not for global variables:

    #include <iostream>
    
    int global = 10;
    
    int main(){
        std::cout << global << '\n';
    
        auto foo [=]() mutable noexcept { ++global; };
        foo();
        std::cout << global << '\n';
    
        const auto increaseGlobal = []() noexcept { ++global; };
        increaseGlobal();
        std::cout << global << '\n';
    
     /* compile error
      * const auto moreIncreaseGlobal = [global]() noexcept { ++global; };
      * moreIncreaseGlobal();
      * std::cout << global << '\n';
      */
    }
    /* output:
     * 10
     * 11
     * 12
     */
    C++

    The Lambda expression always refers to the global object, regardless of how you capture it, and does not create local copies.
    The last moreIncreaseGlobal() could not be compiled using Clang, suggesting that global variables could not be captured.

    捕获静态变量

    Similar to the catch of global variables, the same problem is encountered in the capture of static objects:

    #include <iostream>
    
    void bar(){
        static int static_int = 10;
        std::cout << static_int << '\n';
    
        auto foo = [=]() mutable noexcept { ++static_int; };
        foo();
        std::cout << static_int << '\n';
    
        const auto increase = []() noexcept { ++static_int; };
        increase();
        std::cout << static_int << '\n';
    
     /* compile error
      * const auto moreIncrease = [static_int]() { ++static_int; };
      * moreIncrease();
      * std::cout << static_int << '\n';
      */
    }
    /* output:
     * 10
     * 11
     * 12
     */
    C++

    As with the global variable, the static variable cannot be captured by value, and the use of Clang for compilation would cause errors, as it cannot capture a variable with a non-automatic storage duration.

    Capture Members Variables and'this'指针

    Captures a member variable in a class member function would be more complex because all data members are associated with the 'this' pointer.
    An example of an error:

    #include <iostream>
    
    struct Baz{
        void foo(){
            const auto lam = [s]() { std::cout << s; };
            lam();
        }
        std::string s;
    };
    
    int main(){
        Baz b;
        b.foo();
    }
    C++

    Reason for error: Could not capture 'Baz:::s' and 'this' pointer was not captured.

    struct Baz{
        void foo(){
            const auto lam = [this]() { std::cout << s; };
            lam();
        };
        std::string s;
    };
    C++

    The member variable can be captured by using the `this' pointer.

    从方法返回 Lambda

    #include <iostream>
    
    struct Baz{
        std::function<void()> foo(){
            return [=, this] { std::cout << s << '\n'; }
        }
        std::string s;
    };
    
    int main(){
        auto f1 = Baz{"abc"}.foo(); /* temporary object */
        auto f2 = Baz{"xyz"}.foo(); /* temporary object */
        f1();
        f2();
        Baz b("ex");
        auto func = b.foo();
        func();
    }
    /* output:
     *
     *
     * ex
     */
    C++

    The 'foo()' method returns a member variable of the Lambda capture class.
    The following is similar

    struct Bar{
        std::string const& foo() const { return s; };
        std::string s;
    };
    
    auto&& f1 = Bar{"abc"}.foo();   // dangling reference
    C++

    Or...

    std::function<void()> foo(){
        return[s] { std::cout << s << '\n'; };
    }
    C++

    Both 'f1' and 'f2' in the codes above are temporary objects, which may lead to the problem of overdrafting reference, leading to undefined behaviour.
    Other problems may arise when the catch of 'this' may exceed the object's life cycle in Lambda, especially in the symmetrical call (async) and multi-wiltreding.

    只能移动对象(moveable-only object)

    For an object that can only be moved (e.g. `unique_ptr ' ), it cannot be captured in Lambda expression as a capture variable, but only by reference, but this does not transfer the ownership of the object:

    #include <iostream>
    #include <memory>
    
    int main(){
        std::unique_ptr<int> p(new int{10});
    
        // 按值捕获 - 编译错误
        // auto foo = [p]() {};
    
        // 按引用捕获 - 可通过编译,但不转移所有权
        auto foo_ref = [&p]() { std::cout << *p << '\n'; };
        foo_ref();
    }
    /* output:
     * 10
     */
    C++

    In such cases, the only way to catch 'std::unique_ptr' is by reference, and then the method cannot transfer ownership of the pointer.
    Solutions: Use initialized capture in C++: By initializing capture, a moving object can be captured in the Lambda expression, thereby transferring ownership.

    #include <iostream>
    #include <memory>
    
    int main(){
        std::unique_ptr<int> p(new int{10});
    
        // 使用初始化捕获 - 转移所有权
        auto foo = [p = std::move(p)](){
            std::cout << *p << '\n';
        };
    
        foo();
    
        if(!p) std::cout << "p is nullptr after being moved" << '\n';
    }
    /* output:
     * 10
     * p is nullptr after being moved
     */
    C++

    保持常量性(const preserving)

    If a constant variable is captured, its constantity is preserved:

    #include <iostream>
    #include <type_traits>
    
    int main(){
        const int x = 10;
        auto foo = [x]() mutable{
            std::cout << std::is_const<decltype(x)>::value << '\n';
            // x = 11; 编译错误
        }
        foo();
    }
    /* output:
     * 1
     */
    C++

    It is clear from the above code that even though the term 'mutable' is used in Lambda expression, the constant of 'x' is retained and cannot be modified.

    参数包捕获

    In the catch sentence, variable parameter templates (variadic templates) can also be used to capture parameter packages:

    #include <iostream>
    #include <tuple>
    
    template<class... args>
    void captureTest(Args... args){
        const auto lambda = [args...]{
            const auto tup = std::make_tuple(args...);
            std::cout << "tuple size: " << std::tuple_size<decltype(tup)>::value << '\n';
            std::cout << "tuple 1st: " << std::get<0>(tup) << '\n';
        };
        lambda();
    }
    
    int main(){
        captureTest(1, 2, 3, 4);
        captureTest("Hello Lambda", 10.0f);
    }
    /* output:
     * tuple size: 4
     * tuple 1st: 1
     * tuple size: 2
     * tuple 1st: Hello Lambda
    C++

    Compile development:

    #include <iostream>
    #include <tuple>
    
    template<class ... Args>
    void captureTest(Args... args)
    {
    
      class __lambda_6_22
      {
        public:
        inline auto operator()() const
        {
          const auto tup = std::make_tuple(args... );
          (std::operator<<(std::cout, "tuple size:  ") << std::tuple_size<decltype(tup)>::value) << '\n';
          (std::operator<<(std::cout, "tuple 1st: ") << std::get<0>(tup)) << '\n';
        }
    
        private:
        Args... args;
    
        public:
        __lambda_6_22(const type_parameter_0_0... & _args)
        : args{_args...}
        {}
    
      };
    
      const auto lambda = __lambda_6_22{args};
      lambda();
    }
    
    /* First instantiated from: insights.cpp:15 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    void captureTest<int, int, int, int>(int __args0, int __args1, int __args2, int __args3)
    {
    
      class __lambda_6_22
      {
        public:
        inline /*constexpr */ void operator()() const
        {
          const std::tuple<int, int, int, int> tup = std::make_tuple(__args0, __args1, __args2, __args3);
          std::operator<<(std::operator<<(std::cout, "tuple size:  ").operator<<(std::integral_constant<unsigned long, 4>::value), '\n');
          std::operator<<(std::operator<<(std::cout, "tuple 1st: ").operator<<(std::get<0>(tup)), '\n');
        }
    
        private:
        int __args0;
        int __args1;
        int __args2;
        int __args3;
    
        public:
        __lambda_6_22(int & ___args0, int & ___args1, int & ___args2, int & ___args3)
        : __args0{___args0}
        , __args1{___args1}
        , __args2{___args2}
        , __args3{___args3}
        {}
    
      };
    
      const __lambda_6_22 lambda = __lambda_6_22{__args0, __args1, __args2, __args3};
      lambda.operator()();
    }
    #endif
    
    
    /* First instantiated from: insights.cpp:16 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    void captureTest<const char *, float>(const char * __args0, float __args1)
    {
    
      class __lambda_6_22
      {
        public:
        inline /*constexpr */ void operator()() const
        {
          const std::tuple<const char *, float> tup = std::make_tuple(__args0, __args1);
          std::operator<<(std::operator<<(std::cout, "tuple size:  ").operator<<(std::integral_constant<unsigned long, 2>::value), '\n');
          std::operator<<(std::operator<<(std::operator<<(std::cout, "tuple 1st: "), std::get<0>(tup)), '\n');
        }
    
        private:
        const char * __args0;
        float __args1;
    
        public:
        __lambda_6_22(const char * ___args0, float & ___args1)
        : __args0{___args0}
        , __args1{___args1}
        {}
    
      };
    
      const __lambda_6_22 lambda = __lambda_6_22{__args0, __args1};
      lambda.operator()();
    }
    #endif
    
    int main()
    {
      captureTest(1, 2, 3, 4);
      captureTest("Hello Lambda", 10.0F);
      return 0;
    }
    C++

    A variable parameter template can be used to capture the parameter package in the Lambda expression, and the captured parameter package can be stored in the 'tuple' object for easy access and operation.

    返回类型推断

    In many cases, the return type of Lambda expression can be omitted, starting with C++11, so that the compiler can extrapolate the return type as long as all returns return the same type of expression:

    #include <type_traits>
    
    int main(){
        const auto baz = [](int x) noexcept{
            if(x < 20) return x * 1.1;  // return double
            else return x * 2.1;        // return double
        };
        static_assert(std::is_same(dobule, decltype(baz(10))>::value, "has to be the same);
    }
    C++

    In the Lambda expression above, both returns are of a return type of double, so the compiler can extrapolate the type of return.

    尾置返回类型语法

    Use a tail-return type syntax to clearly specify the type of return:

    #include <iostream>
    
    int main(){
        const auto testSpeedString = [](int speed) noexcept{
            if(speed > 100) return "you're a super fast";
            else return "you're a regular";
        };
        auto str = testSpeedString(100);
        str += " driver";
        std::cout << str;
    }
    C++

    The above code is subject to a compilation error because no + = operator, adjusted:

    auto testSpeedString = [](int speed) -> std::string {
            if(speed > 100) return "you're a super fast";
            else return "you're a regular";
        };
        auto str = testSpeedString(100);
        str += " driver";
    /* output:
     * you're a regular driver
     */
    C++

    Note that the `noexcept' needs to be removed when `std:string' is clearly set here because 'std:string' is created to throw an anomaly.
    Or use 'std::string_literals' and return 'you're a regular's' as 'std:string' type.

    Here it is also possible to achieve a `std:string' inheritance category, and a reload of `opator+=:

    #include <iostream>
    
    class speedString: public std::string{
    public:
        using std::string::string;  // 继承 std::string 的构造函数
    
        speedString& operator+=(const std::string& rhs){
            std::string::operator+=(rhs);
            return *this;
        }
    
        // 重载 operator+= 以支持 const char* 类型
        speedString& operator+=(const char* rhs){
            std::string::operator+=(rhs);
            return *this;
        }
    };
    
    int main(){
        const auto testSpeedString = [](int speed) noexcept -> speedString{
            if(speed > 100) return "you're a super fase";
            else return "you're a regular";
        };
    
        speedString str = testSpeedString(100);
        str += " driver";
        std::cout < str;
    }
    /* output:
     * you're a regular driver
     */
    C++

    函数指针转换(Conversion Function Pointer)

    If the Lambda expression does not capture any variables, the compiler can convert them to a general function pointer, as described in the standard as follows:

    For the Lambda expression that is not captured, the closed type has a public, non-fiction, non-visible const conversion function, which is converted to a function pointer with the same parameters as the closed type function and the return type. The value returned by this conversion function should be the address of a function that, when called, has the same effect as the caller of a closed-pack function.

    For example:

    #include <iostream>
    void callWith10(void (*bar)(int)){
        bar(10);
    }
    
    int main(){
        struct{
            using f_ptr = void(*)(int);
            void operator()(int s) const { return call(s); }
            operator f_ptr() const { return &call; }
        private:
            static void call(int s) { std::cout << s << '\n'; };
        } baz;
    
        callWith10(baz);
        callWith10([](int x) { std::cout << x << '\n'; };
    }
    C++

    Explanation:

    1. 'callWith10()': 'void(*bar(int)' is a function pointer that points to a function that returns to the `void' type, the parameter type `int', the `callWith10()' function accepts a function pointer of this type as a parameter and then calls the function and transmits the parameter `10 '.
    2. 'using f_ptr = void(*) (int) (equivalent to typedef void (*f_ptr) (int)); 'defined a function pointer type'f_ptr'.
    3. 'void object(((int s) const {return call(s);}'re overloading'oporator()' so that objects'baz' objects can be called as functions and will call private static member functions'call'.
    4. 'Petrator f_ptr() const {return & call;}' defines a hidden conversion operator from a structural body type to a function pointer type, i.e., the structural body example'baz' can be secretly converted to a function pointer to a static member function'call'.

    Example: Use Lambda to call 'std::qsort' in C library for inverse sorting:

    #include <iostream>
    #include <cstdlib>
    
    int main(){
        int values[] = {8, 9, 2, 5, 1, 4, 7, 3, 6};
        constexpr size_t numElements = sizeof(values) / sizeof(values[0]);
    
        std::qsort(values, numElements, sizeof(int),
                    [](const void* a, const void* b) noexcept {
            return (*(int*)b - *(int*)a);
        }
        );
    
        for(const auto& val: values) std::cout << val << ", ";
    }
    /* output:
     * 9, 8, 7, 6, 5, 4, 3, 2, 1
     */
    C++

    In the above code, 'std:: qsort' only accepts a function pointer as a comparator, and the compiler can secretly convert the passaway Lambda expression to a function pointer.

    Summary:

    1. No Lambda to function pointer captured:
      • Uncaptured Lambda expression can be converted to a function calling operator with the same parameters and return type of local function pointer.
      • This conversion is done automatically by the compiler and is easily used when C-style feedback is required.
    2. Simulation function (functor) graphic conversion:
      • By defining a conversion operator, a function can be converted to a function pointer in a prominent way.
      • This is very useful when it comes to the transfer of complex objects (e.g., analogue functions) to a functional pointer interface.

    一个棘手的案例

    The cases are as follows:

    #include <type_traits>
    
    int main(){
        auto funcPtr = +[]{};
        static_assert(std::is_same(decltype(funcPtr), void(*)()>::value);
    }
    C++

    Compile development:

    #include <type_traits>
    
    int main()
    {
    
      class __lambda_8_19
      {
        public:
        inline /*constexpr */ void operator()() const
        {
        }
    
        using retType_8_19 = auto (*)() -> void;
        inline constexpr operator retType_8_19 () const noexcept
        {
          return __invoke;
        }
    
        private:
        static inline /*constexpr */ void __invoke()
        {
          __lambda_8_19{}.operator()();
        }
    
    
        public:
        // /*constexpr */ __lambda_8_19() = default;
    
      };
    
      using FuncPtr_8 = auto (*)() -> void;
      FuncPtr_8 funcPtr = +__lambda_8_19{}.operator __lambda_8_19::retType_8_19();
      /* PASSED: static_assert(std::integral_constant<bool, true>::value); */
      return 0;
    }
    C++

    The source code uses '+', which is a one-digit operator that can be used for a pointer, so the compiler converts the non-state Lambda to a functional pointer and then assigns the value to 'funcPtr', instead of a one-track operator '+', 'funcPtr' is a general closed object, and 'static_assert' is also invalid.

    In this case, the one-dollar operator '+' and 'static_cast ' have the same effect, and if the compiler is not expected to create too many examples of functions, the following can be done:

    template<typename F>
    void call_function(F f){
        f(10);
    }
    
    int main(){
        call_function(static_cast<int(*)(int)>([](int x) {
            return x + 2;
        }));
        call_function(static_cast<int(*)(int)>([](int x) {
            return x * 2;
        }));
    }
    C++

    In the code above, the compiler needs only to create an example of 'call_funaction' because it accepts only one function pointer'int(*)', and if 'static_cast' is removed, the compiler will create two different examples of 'call_function' for each Lambda.

    IIEF(Immediately Invoked Expression Function) - 立即调用的函数表达式

    Direct call to Lambda expression example:

    #include <iostream>
    
    int main(){
        int x = 1, y = 1;
        [&]() noexcept { ++x; ++y; }();
        std::cout << x << ',' << y;
    }
    /* output:
     * 2, 2
     */
    C++

    At this point, the Lambda expression was created without being assigned to any closed object, but directly by '() 'call.

    Such a Lambda expression is more useful when initializing a complex `const' object.

    const auto val = [](){
    /* do something */
    }();
    C++

    At this time, 'val' is a type constant returned by Lambda expression:

    /* val1 是 int */
    const auto val1 = []() { return 10; }();
    /* val2 是 std::string */
    const auto val2 = []() -> std::string { return "ABC"; }();
    C++

    A more specific example: using IIFE as assistant Lambda to create a constant - IIFE and HTML within the function

    #include <iostream>
    
    void Valiate(const std::string&) {}
    
    std::string BuildHred(const std::string& link,
                            const std::string& text){
        const std::string html = [&link, &text] {
            const std::string inText = text.empty() ? link : text;
            return "<a href=\"" + link + "\">" + inText + "</a>";
        }();
        Validate(html);
        return html;
    }
    
    int main(){
        try{
            const auto ahref = BuildHref("ppqwqqq.space", "ppQwQqq");
            std::cout << ahref;
        }
        catch (...) {
            std::cout << "bad format...";
        }
    }
    C++

    above the code, the 'BuildHref' function, accepts two parameters, then generates one'<a></a>'HTML labels, based on input parameters, build 'html 'variables, using 'text' as internal HTML values if they are not empty, or using 'link'. Use IIEF to make the expression simpler with multiple input parameters: create an independent Lambda expression, then mark its variable as 'cont', and then pass the 'const' variable to 'ValidateHTML'.

    提高 IIEF 代码可读性的方法

    1. Avoid 'auto'
      • Specify the type clearly so as to see more clearly the type of variable:
      • const bool EnableErrorReporting = [&]() { if(HighLevelWarningEnabled()) return true; if(HighLevelWarningEnabled()) return UsersWantReporting(); return false; }();
    2. Add Comment:
      • Add a note after the word, indicating it's "IIEF":
        • const bool EnableErrorReporting = [&]() { if (HighLevelWarningEnabled()) return true; if (HighLevelWarningEnabled()) return UserWantReporting(); return false; }(); // call it now

    Lambda 表达式的继承与多态

    Inheritance of the Lambda expression: As the compiler expands the Lambda expression to a generic function object with 'operator()', it can be inherited from this type:

    #include <iostream>
    
    template<typename Callable>
    class ComplexFunctor: public Callable{
    public: explicit ComplexFunctor(Callable f): Callable(f) { }
    }
    
    template<typename Callable>
    ComplexFunctor<Callable> MakeComplexFunctor(Callable&& cal){
        return ComplexFunctor<Callable>(cal);
    }
    
    int main(){
        const auto func = MakeComplexFunctor([]() {
            std::cout << "Hello Functor\n";
        });
        func();
    }
    C++

    In this example, the `ComplexFunctor' category is inherited from the template parameter `Callable' and, if it is to be inherited from Lambda, additional operations must be added, as it is not possible to specify the exact type of the closed type (unless it is encapsulated in `std:function'), so the `MakeComplexFunctor' function is required to carry out the template parameter extrapolation and to obtain the Lambda closed type.

    Multiple Lambda Inheritance: Example: Succession from two Lambdas and creation of a reload collection:

    #include <iostream>
    
    template<typename TCall, typename UCall>
    class SimpleOverLoaded: public TCall, UCall{
    public:
        SimpleOverLoaded(TCall tf, UCall uf): TCall(tf), UCall(uf){}
        using TCall::operaotr();
        using UCall::operator();
    };
    
    template<typename TCall, typename UCall>
    SimpleOverLoaded<TCall, UCall> MakeOverloaded(TCall&& tf, UCall&& uf){
        return SimpleOverLoaded<TCall, UCall>(tf, uf);
    }
    
    int main(){
        const auto func = MakeOverloaded(
            [](int) { std::cout << "Int!\n"; },
            [](float) { std::cout << "Float!\n"; }
        );
        func(10);
        func(10.0f);
    }
    /* output:
     * Int!
     * Float!
     */
    C++

    Here you can inherit from two templates and display 'oporator' to expose them.

    为什么需要显式暴露

    When searching for the correct reload function, compilers require that they be in the same field:

    #include<iostream>
    
    struct BaseInt{
        void Func(int) { std::cout << "BaseInt...\n"; };
    };
    
    struct BaseDobule{
        void Func(double) { std::cout << "BaseDouble...\n"; }
    };
    
    struct Derived: public BaseInt, BaseDouble{
        using BaseInt::Func;
        using BaseFunc::Func;
    };
    
    int main(){
        Derived d;
        d.Func(10.0);
    }
    /* output:
     * BaseDouble...
     */
    C++

    If there is no `using' statement, the compiler will miss because `Func()' can come from `BaseInt' or `BaseDouble', and the compiler cannot decide which to use.

    在容器中存储 Lambda 表达式

    Storage of Lambda with function pointer:
    The Lambda expression cannot be created and given by default, however, using a non-state Lambda expression to convert to a function pointer, although it cannot be stored directly, it can save a function pointer converted from the Lambda expression:

    #include <iostream>
    #include <vector>
    
    int main(){
        using Func = void(*)(int&);
        std::vector<TFunc> ptrFuncVec;
    
        ptrFuncVec.push_back([](int& x) { std::cout << x << '\n'; });
        prtFuncVec.push_back([](int& x) { x *= 2; });
        ptrFuncVec.push_back(ptrFuncVec[0]);
    
        int x = 10;
        for(const auto& entry: ptrFuncVec) entry(x);
    }
    /* output:
     * 10
     * 20
     */
    C++

    There are three variables in 'ptrFuncVec':

    1. Output input parameter values.
    2. Modify Value
    3. is the first copy, again.

    This method, while valid, is limited to the non-state Lambda expression.

    Use std::funaction to seal Lambda:
    In order to be able to use other state Lambda expressions in the packaging, 'std::function' can be used to process not only integer numbers, but also string objects:

    #include <iostream>
    #include <functional>
    #include <algorithm>
    #include <vector>
    
    int main(){
        std::vector<std::function<std::string(const std::string&)>> vecFilters;
    
        size_t removedSpaceCounter = 0;
        const auto removeSpaces = [&removedSpaceCounter](const std::string& str){
            std::string tmp;
            std::copy_if(str.begin(), str.end(), std::back_inserter(tmp),
                        [](char ch) { return !isspace(ch); });
            removedSpaceCounter += str.length() - tmp.length();
            return tmp;
        }
    
        const auto makeUpperCase = [](const std::string& str){
            std::string tmp = str;
            std::transform(tmp.begin(), tmp.end(), tmp.begin(),
                            [](unsigned char c) { return std::toupper(c); });
            return tmp;
        };
    
        vecFilters.emplace_back(removeSpaces);
        vecFilters.emplace_back([](const std::string& x){
            return x + " Amazing";
        });
        vecFilters.emplace_back([](const std::string& x){
            return x + " Modern";
        });
        vecFilters.emplace_back([](const std::string& x){
            return x + " C++";
        });
        vecFilters.emplace_back([](const std::string& x){
            return x + " World!";
        });
        vecFilters.emplace_back(makeUpperCase);
    
        const std::string str = "   H e l l o     ";
        auto temp = str;
        for(const auto& entryFunc: vecFilters) temp = entryFunc(temp);
        std::cout << temp << '\n';
        std::cout << "Removed spaces: " << removedSpaceCounter << '\n';
    }
    /* output:
     * HELLO AMAZING MODERN C++ WORLD!
     * Removed spaces: 12
     */
    C++

    This code, store 'std::function in the container'Enable to use any type of function object, including the Lambda expression for capturing variables.

    Leave a Comment

    Share this Doc

    Chapter II Lambda in C++11

    Or copy link

    CONTENTS
    Remember to rest.