Lambdas
Contents
STL
We shall examine how lambdas are used in STL.File: lambdas2.cpp
#include <algorithm> #include <array> #include <string> #include <functional> #include <iostream> using namespace std ; class customSort1 { public: bool operator()(const int& s1, const int& s2) { return s1 < s2 ; } }; class Add { public: int operator()(int a, int b) const { cout << "Inside Add operator." << endl ; return a + b ; } }; int main() { //Using lambda in stl sort algorithm array<int, 10> arr1{5, 7, 4, 2, 8, 6, 1, 9, 0, 3}; auto print1 = [&arr1](const string& rem) { for (auto element : arr1) cout << element << ' '; cout << ": " << rem << '\n'; }; sort(arr1.begin(), arr1.end()); print1("sorted with the default operator<"); auto customSort = [] (int const& s1, int const& s2) -> bool { return s1 > s2 ; }; //Pass a lambda object sort(arr1.begin(), arr1.end(), customSort); print1("sorted with the customSort "); //Inline lambda sort(arr1.begin(), arr1.end(), [] (int const& s1, int const& s2) -> bool { return s1 > s2 ; }); print1("sorted with the inline lambds "); //Using the built in functor "greater" //Signature of the greater is //template <class T> struct greater { // bool operator()(const T& lhs, const T& rhs) const; //}; sort(arr1.begin(), arr1.end(), greater<int>() ); //Use a custom functor object customSort1 customSort1Object ; sort(arr1.begin(), arr1.end(), customSort1Object ); print1("Sorted with the functor object."); //Return statements can be implicit or //explicit. //Example of implicit return auto add = [] (int a, int b) { // always returns an 'int' return a + b; }; cout << "add called:" << add(4, 5) << endl ; //Explicit return statement auto add1 = [] (int a, int b ) -> double { return a + b ; }; cout << "add1 called:" << add1(4, 6) << endl ; //inline example // initialize vector of integers vector<int> nums = {1, 2, 3, 4, 5, 8, 10, 12}; int even_count = count_if(nums.begin(), nums.end(), [](int num) { return num % 2 == 0; } ); cout << "There are " << even_count << " even numbers." << endl ; //Without using auto //A lambda is a functor object. //Add add3 = [] (int a, int b) { function<void(int, int)> add2 = [] (int a, int b) { cout << "Sum: " << a + b << endl ; }; add2(1, 2); Add add3Object ; //Assign a class object to a function object function< int(int, int) > add3 = add3Object ; add3( 10, 20 ) ; /* Add add4Object = [] (int a, int b) { // always returns an 'int' return a + b; }; Compiler Error lambdas2.cpp:143:10: error: conversion from ‘main()::<lambda(int, int)>’ to non-scalar type ‘Add’ requested 143 | Expression on the left is of class "Add". Expression on the right is a different class type and due to strong static type checking we cannnot assign a class of one type to a different type. */ } //https://stackoverflow.com/questions/65186151/conversion-from-lambda-to-non-scalar-type-requested
We have the concept of a function that can be defined with it's body.
Then came along "functors". This is a class with an operator "()" such
as:
bool operator()(int const& s1, int const& s2)
{
return s1 < s2 ;
}
that can then be called directly by creating an object of the class and
then just doing something like:
obj1( 4, 5 ) ;
The next language enhancement to this approach is lambdas. We have an
expression that is like a function but is actually a functor.
auto print1 = [&arr1](const string& messg)
{
for (auto element : arr1)
std::cout << element << ' ';
std::cout << ": " << messg << '\n';
};
In the above snippet we define a lambda "print1" that
passes the "arr1" object and takes a string as an argument.
We are basically printing out the elements of the "arr1"
object.
We first sort using the std::sort function:
sort(arr1.begin(), arr1.end());
without any custom comparators. This sorts the container
in an ascending order.
auto customSort = [] (int const& s1, int const& s2) -> bool
{
return s1 > s2 ;
};
sort(arr1.begin(), arr1.end(), customSort);
In the above we define a lambda object "customSort" that takes
2 integer arguments and returns a boolean value. This sorts the
container in a descending order. The signature for "sort" specifies
the third argument to a functor that takes 2 arguments and returns
a boolean value. Notice that we have explicitly specified the return
type with the "-> bool" part of the expression.
sort(arr1.begin(), arr1.end(), [] (int const& s1, int const& s2) -> bool
{
return s1 > s2 ;
});
print1("sorted with the inline lambds ");
We do not have to have a name for the lambda expression and can embed
the expression in the function call as shown above.
sort(arr1.begin(), arr1.end(), greater() );
In this call we are using the built in functor "greater" from the
"" header file. The signature is as:
template struct greater {
bool operator()(const T& lhs, const T& rhs) const;
};
As we can see we can use lambda expressions and functors
interchangeably.
class customSort1
{
public:
bool operator()(int const& s1, int const& s2)
{
return s1 < s2 ;
}
};
//Use a custom functor object
customSort1 customSort1Object ;
sort(arr1.begin(), arr1.end(), customSort1Object );
print1("Sorted with the functor object.");
In the above we have our own functor custom class. We create
an object of that and pass it to the sort function.
//Explicit return statement
auto add1 = [] (int a, int b ) -> double {
return a + b ;
};
cout << "add1 called:" << add1(4, 6) << endl ;
The above snippet shows how to specify the type that the lambda
expression will return. The type is specified just before the body
of the lambda expression.
//Without using auto
//A lambda is a functor object.
function<void(int, int)> add2 = [] (int a, int b) {
cout << "Sum: " << a + b << endl ;
};
add2(1, 2);
The above snippet shows an alternative to using "auto" to
declare the type for lambda. Since a lambda object is a functor,
we can use the stl function to specify that the functor takes 2
integer arguments and returns null. We can then call the functor object
just as we would call a lambda object.
Exercise
1) Complete the "TO DO" section in the below code. Discuss whether it's better to use a function as a lambda when the function is large ( in terms of the number of lines of code ).File: lambdas_ex_3.cpp
#include <iostream> #include <vector> #include <algorithm> // Required for sort and for_each using namespace std ; bool isPalindrome(const string& str) { if (str.empty()) { return true; // An empty string can be considered a palindrome } int left = 0; int right = str.length() - 1; while (left < right) { if (str[left] != str[right]) { return false; // Characters don't match, not a palindrome } left++; right--; } return true; // All characters matched, it's a palindrome } int main() { vector<string> names = {"chair", "table", "civic", "kayak" }; // Find the palindrome in the vector of strings // TO DO Use a lambda expression instead of the function // auto it = find_if(names.begin(), names.end(), isPalindrome ) ; if (it != names.end()) { cout << "Found: " << *it << endl ; } return 0; }2) Complete the "TO DO" section in the below code.
File: lambdas_ex_4.cpp
#include <iostream> #include <vector> #include <algorithm> #include <cmath> using namespace std ; bool isPerfectSquare( const int& num ) { if (num < 0) { return false; // Negative numbers cannot be perfect squares } int root = sqrt( (double)(num) ); //cout << root << " " << num << endl ; return (root * root == num); } int main() { vector<int> numbers = {1 , 4, 5, 6, 9, 10 }; //TO DO //Replace isPerfectSquare by a lambda expression //Put bool as an explicit type //Capture clause is empty //Pass a single const int& as an argument int count = count_if(numbers.begin(), numbers.end(), isPerfectSquare ); cout << count << endl ; return 0; }
Solutions
File: lambdas_ex_3s.cpp
File: lambdas_ex_4s.cpp