Lamda expression, parafunction (function object)

Keywords: C++

Introduction of Lambda expression

Some function object classes are only used to define an object, and this object is used only once, which is a bit wasteful. Moreover, defining a function object class and using it can be far apart, and it can be cumbersome to see exactly what its operator() member function does.

For function object classes that are only used once, can they be defined directly where they are used?

Lambda expressions solve this problem by defining and creating anonymous function objects and reducing the number of function object classes in the program to simplify programming.

The syntax format of Lambda is as follows:  

[Capture List] Captures contextual variables for use by Lambda functions.

[Parameter List] If parameter passing is not required, it can be omitted with ().

[mutable] Variable specification. By default, the Lambda function is always a const function, and mutable can cancel its constancy. When using this modifier, the parameter list cannot be omitted (even if there are no parameters).

[throw] exception declaration. An exception thrown by a specified function. This part and mutable are added according to the actual situation and can be omitted.

[Return value type], when the return value type is void or the return value type is clear, you can omit this section and let the compiler automatically infer the return value type.

Function body cannot be omitted, but it can be empty

Instance demo

Capturing lists using lambda expressions

  • [] No variables are captured.
int main()
{
    int a = 0, b = 1;
    auto f1 = [] { return a; };               // error, no external variables captured
}
  • [&] Captures all variables in the external scope and uses them as references in the body of the function (captured by reference).
int main()
{
    int a = 0, b = 1;
    auto f2 = [&] { return a++; };            // OK, captures all external variables, and performs a self-addition operation on a

    cout << f2() << endl;//0
    cout << a << endl;//1
}
  • [=] Captures all variables in the external scope and uses them as copies in the function body (captured by value).
int main()
{
    int a = 0, b = 1;
    auto f3 = [=] { return a; };              // OK, captures all external variables, and returns a
    auto f4 = [=] { return a++; };            // error, a is captured by copying and cannot be modified

    cout<<f3()<<endl;//0
    cout<<a<<endl;//0

}
  • [=, &foo] Captures all variables in the external scope by value and foo variables by reference.
int main()
{
    int a = 0, b = 1;
    auto f7 = [=, &b] { return a + (b++); };  // OK, captures all references to external variables and b, and adds B by itself

    cout << f7() << endl;//1
    cout << a << endl;//0
    cout << b << endl;//2
}
  • [bar] Captures the bar variable by value without capturing other variables.
int main()
{
    int a = 0, b = 1;
    auto f5 = [a] { return a + b; };          // error, no capturing variable b
}
  • [this] captures this pointer in the current class  , Make the lambda expression have the same access rights as the current class member function. If you've already used &or=, add this option by default. This is captured so that member functions and member variables of the current class can be used in lamda.
class A
{
public:
    int i_ = 0;
    int getvalue() { return i_; }
    void func(int x, int y)
    {
        auto x1 = [] { return i_; };                    // error, no external variables captured
        auto x2 = [=] { return i_ + x + y; };           // OK, capture all external variables
        auto x3 = [&] { return i_ + x + y; };           // OK, capture all external variables
        auto x4 = [this] { return i_; };                // OK, capture this pointer
        auto x5 = [this] { return i_ + x + y; };        // error, no x,y captured
        auto x6 = [this, x, y] { return i_ + x + y; };  // OK, capture this pointer, x, y
        auto x7 = [this] { return i_++; };              // OK, captures this pointer, and modifies the member's value
        auto x8 = [this] { return getvalue(); };        // OK, captures this pointer, accesses member functions of the current class
    }
};

Lambda parameter list

int main()
{
    int a = 0, b = 1;
    //Reference
    auto function1 = [](int first, int second) { return first + second; };
    cout << function1(a,b)<< endl;
    //No reference
    auto function2 = [] { cout<<"hello world"<<endl; };
    function2();

}

  Variable specification mutable

int main()
{
    int i = 100;
    //Auto function 1 = [i] {I = 10;} // error, captured I is the const attribute
    auto function2 = [i]()mutable { i = 10; return i; };  // The mutable modifies the const property of the captured i, but I itself is not modified
    cout << function2() << endl;//10
    cout << i << endl;//100
}

3. The Essence of Lambda Expression

A lambda expression essentially generates an anonymous function class object and overrides the operator() method in the anonymous function class.

int main()
{
    auto print = [] {cout << "hello world" << endl; };
    print();
}

The compiler will translate the above sentence into the following code:

class printclass
{
public:
    void operator()()const
    {
        cout << "hello world" << endl;
    }
};

int main()
{
    //Create an object with the class's constructor, where print is a function object
    auto print = printclass();
    print();
}

4. Affine Functions (Function Objects)

A class of overloaded function call operators (), whose object is called a function object, which calls an overloaded operator() behaves like a function, also known as a function-like function.

class printclass
{
public:
    void operator()()const
    {
        cout << "hello world" << endl;
    }
};

int main()
{
    //print is now a function object
    auto print = printclass();
    print();//Function object call overload () is similar to a function, so it is also called a function-like
}

Two highlights of function objects are:

1) Function objects can contain states;

2) The function object is a type and can be used as a template parameter.

5. Scenarios for Lamdba Expression

Lamdba expression applied to STL algorithm library

#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
	auto Mygreater = [](int a, int b) {return a < b; };
	int a[] = { 45,12,34,77,90,11,2,4,5,55 };
	sort(a, a + 10, Mygreater);
	for (int i = 0; i < 10; i++)
	{
		cout << a[i] << " ";
	}
		
}

(2) Lamdba expression is applied to function pointer and function

#include<iostream>
#include<algorithm>
#include <functional>
using namespace std;

int main()
{
	
	int x = 8, y = 9;
	auto add = [](int a, int b) { return a + b; };
	std::function<int(int ,int)> Add = [](int a, int b) { return a + b; };

	cout << "add: " << add(x, y) << endl;
	cout << "Add: " << Add(x, y) << endl;

}

(3) Lamdba expression as a function parameter

#include<iostream>
#include <functional>
using namespace std;

using FuncCallback = std::function<void(void)>;

void DataCallback(FuncCallback callback)
{
	std::cout << "Start FuncCallback!" << std::endl;
	callback();
	std::cout << "End FuncCallback!" << std::endl;
}

auto callback_handler = [&]() {
	std::cout << "This is callback_handler"<<endl;
};

int main()
{
	
	DataCallback(callback_handler);
	
}

Posted by munkee on Thu, 21 Oct 2021 09:41:01 -0700