[ Algorithms C++ ]
In C++, a lambda expression is a convenient way to define an anonymous function object or functor. It was introduced in C++11 and allows you to create inline functions for short snippets of code that are not going to be reused and therefore do not require a name. Lambda expressions are often used to encapsulate a few lines of code that are passed to algorithms or asynchronous functions, improving code readability and maintainability
A lambda expression can be defined in its simplest form as follows:
The return type in lambda expressions is usually evaluated by the compiler itself, and you don’t need to specify it explicitly. The →return-type
part can also be ignored in many cases. However, in some complex situations, such as conditional statements, the compiler may not be able to determine the return
type, and explicit specification is required
A functor
A functor, or function object, is a C++ class that acts like a function. It is an object that can be treated as though it is a function or function pointer. Functors are most commonly used along with STLs in a scenario where a function requires more information than it can accept as a parameter. Functors are objects that can be called like a function by overloading the function call operator (). They can be used to encapsulate a few lines of code that are not going to be reused and therefore do not require a name. Functors can simplify tasks and improve efficiency in many cases
Example functor.cpp
bellow:
functor.cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// Define a functor for the predicate
class is_odd {
public:
bool operator () (const int n) const { return (n % 2 == 1); }
};
int main() {
vector<int> vec {3, 1, 4, 1, 5, 9};
// Pass a functor object
auto odd_it = find_if(cbegin(vec), cend(vec), is_odd());
// odd_it will be an iterator to the first odd element (if there is one)
if (odd_it != cend(vec))
cout << "First odd element is: " << *odd_it << endl;
//-> First odd element is: 3
}
We can simplify this code by using Lambda expression
Example lambda.cpp
bellow:
lambda.cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
vector<int> vec {3, 1, 4, 1, 5, 9};
// Pass a lambda expression
auto odd_it = find_if(cbegin(vec), cend(vec),
[](int n) { return (n % 2 == 1); }
);
// odd_it will be an iterator to the first odd element (if there is one)
if (odd_it != cend(vec))
cout << "First odd element is: " << *odd_it << endl;
//-> First odd element is: 3
}
An Assignment
Example Assignment.cpp
bellow:
Assigment.cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
void print_vec(vector<int>& vec){
for(int i=0; i < vec.size(); i++){
cout << vec[i] << " ";
};
cout <<endl;
}
int main() {
vector<int> vec {3, 1, 4, 1, 5, 9};
//Before sorting
cout <<"Before sorting " << endl;
print_vec(vec);
// -> 3 1 4 1 5 9
// Pass a lambda expression
sort(vec.begin(), vec.end(),
[](int a, int b) { return (a > b); }
);
//Print vector elements
cout <<"After sorting " << endl;
print_vec(vec);
// -> 9 5 4 3 1 1
}
Lambda Expressions Practical
Without Lambda expression
Example Without_Lambda.cpp
bellow:
Without_Lambda.cpp
#include <string>
#include <iostream>
using namespace std;
// Case-insensitive string equality. Returns true if lhs and rhs are equal, otherwise false
bool equal_strings(const string& lhs, const string& rhs) {
// If the two strings have different lengths
if (lhs.size() != rhs.size())
return false;
// If the two strings have the same lengths...
auto lit=cbegin(lhs);
auto rit = cbegin(rhs);
while (lit != cend(lhs) && rit != cend(rhs)) { // Iterate over both strings
if (toupper(*lit) != toupper(*rit)) // Convert to upper case and compare
return false; // Characters are different
++lit;
++rit;
}
return true; // No differences found
}
void test(const string& str1, const string& str2) {
cout << str1 << " and " << str2 << " are" <<
(equal_strings(str1, str2) ? "" : " not")
<< " equal" << endl;
}
int main() {
test("lambda", "Lambda"); //-> lambda and Lambda are equal
test("lambda", "lambada");//-> lambda and lambada are not equal
}
With Lambda expression
With_Lambda.cpp
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;
bool equal_strings(const string& lhs, const string& rhs) {
// Call equal() algorithm function using a lambda expression
return equal(cbegin(lhs), cend(lhs), cbegin(rhs), cend(rhs),
[] (char lc, char rc) { return toupper(lc) == toupper(rc); }
);
}
void test(const string& str1, const string& str2) {
cout << str1 << " and " << str2 << " are" <<
(equal_strings(str1, str2) ? "" : " not")
<< " equal" << endl;
}
int main() {
test("lambda", "Lambda"); //-> lambda and Lambda are equal
test("lambda", "lambada");//-> lambda and lambada are not equal
}
Lambda Expression and Variables
The capture clause of a lambda expression is used to specify which variables are captured and whether they are captured by value or by reference. The capture clause is optional, and if it is not specified, the lambda expression cannot capture any variables from the surrounding scope.
Example_Capture.cpp
#include <iostream>
using namespace std;
int global{99}; // Non-local variable
int main() { // Scope containing the lambda expression
static int answer{42}; // Static variable in containing scope
const int one{1}; // Local variable in containing scope
const int& r_one{one}; // Local variable in containing scope
[]() { // Start of lambda body
cout << global << endl; // Lambda body can access non-local variables
cout << answer << endl; // Lambda body can access static variables
// Does not compile with Visual C++
//cout << one << endl; // Lambda body can read local variables, provided...
// Does not compile with Visual C++, gcc or clang
//cout << r_one << endl; // Lambda body can read local variables, provided...
}; // End of lambda body
} // End of scope containing the lambda expression
Example capture_lambda Example_Capture_lambda.cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
using namespace std;
int main() {
vector<string> words{ "a", "collection", "of", "words", "with", "varying", "lengths" };
int n{5};
// Find the first element with more than 5 characters
auto res = find_if(cbegin(words), cend(words),
[n] (const string& str) { return str.size() > n; } // Lambda captures local variable n
);
// Display it
if (res != cend(words)) {
cout << R"(The first word which is more than )" << n << R"( letters long is ")";
cout << *res << R"(")" << endl;
}
}
// -> The first word which is more than 5 letters long is "collection"
Capture in Lambda Expression
In C++, a lambda expression can capture variables from the surrounding scope using an implicit or explicit capture. Implicit capture, denoted by [=]
or [&]
, captures all variables by value or reference, respectively. Explicit capture, denoted by [var]
or [&var]
, captures a specific variable by value or reference.
There are a few considerations when choosing between implicit and explicit captures:
-
Error-proneness: Explicit capture is generally considered less error-prone, especially when dealing with heavy objects. Implicit capture can lead to unintended side effects if the captured variables are modified outside the lambda.
-
Encapsulation and safety: Explicit capture allows you to access only the variables you need, which can help the compiler detect when you access a variable you shouldn’t. It also keeps the local state of the method (its invariants, for the lifetime of the function during its execution) safer.
-
Functionality and implementation: Implicit and explicit captures are functionally and implementation-wise identical. The choice between them depends on the specific requirements of your code.
-
Best practices: As a matter of style, it is recommended to always capture
*this
explicitly, even in the presence of capture-defaults.
Explicit capture Explicit_capture.cpp
bellow:
Explicit_capture.cpp.cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int main() {
vector<string> words{ "a", "collection", "of", "words", "with", "varying", "lengths" };
int max{5}, idx{-1}; // Add another variable for the index of the match
auto res = find_if(cbegin(words), cend(words),
// Explicit capture, capture idx by reference
[max, &idx] (const string& str) { ++idx; return str.size() > max; }
);
if (res != cend(words)) {
cout << R"(The first word which is more than )" << max << R"( letters long is ")";
cout << *res << R"(")" << endl;
cout << "Its index is " << idx << endl;
}
// The first word which is more than 5 letters long is "collection"
// Its index is 1
}
In C++, a lambda expression can capture the *this
pointer by value or by reference to access the members of the current object. Capturing *this
by value is important in scenarios where the lambda expression is asynchronously dispatched from a non-static member function, as the pointer might not be valid when the function runs.
Capture This capture_this.cpp
bellow:
capture_this.cpp
#include <iostream>
using namespace std;
class Test {
int time{10}; // Data member
public:
void countdown() { // Member function which calls a lambda expression
[this] () { // Capture class object by reference
if (time > 0)
cout << time << endl;
else if (time == 0)
cout << "Liftoff!" << endl;
--time;
}(); // Call the lambda function
}
};
int main() {
Test test;
for (int i = 0; i < 12; ++i)
test.countdown();
}
Output.cpp
10
9
8
7
6
5
4
3
2
1
Liftoff!
References
- https://www.geeksforgeeks.org/lambda-expression-in-c/
- https://learn.microsoft.com/en-us/cpp/cpp/lambda-expressions-in-cpp?view=msvc-170
- https://www.programiz.com/cpp-programming/lambda-expression
- Example from James Raynard Learn Advanced Modern C++ Udemy.
- https://learn.microsoft.com/en-us/cpp/cpp/lambda-expressions-in-cpp?view=msvc-170
- https://en.cppreference.com/w/cpp/language/lambda