The new C++0x standard adds lambda expressions to the language. Visual Studio 2010 CTP is already supporting this new feature that brings functional techniques to C++ too.
What is a lambda expression? It’s basically a function. In F# it’s an anonymous function, in C# it’s an anonymous delegate; in C++ it’s in fact an anonymous function object. Whenever you create a new lambda function, the compiler creates a function object for you.
int main() { auto l_pow2 = [](int n) {return n*n;}; std::cout << "5 pow 2 = " << l_pow2(5) << std::endl; }
That is equivalent to:
struct LambdaFunctor { int operator()(int n) const { return n * n; } }; int main() { LambdaFunctor l_pow2; std::cout << "5 pow 2 = " << l_pow2(5) << std::endl; }
Of course, the lambda functor can be more complicated, when the lambda function captures state from the local scope. However, that is beyond the scope of my post. I recommend that you read more about lambdas in C++ on the VC++ blog.
Question is, what are these lambdas good for? Well, they mostly come in handly with algorithms that take predicates (function objects) as arguments. I'll try to give you some examples in this post.
Let's first consider a filter function, that takes a sequence (vector of T) and a predicate that indicates what values should be filtered, and returns a new sequence. That would look like this:
template < class T > std::vector< T > Filter(const std::vector< T >& sequence, std::tr1::function< bool (T) > predicate) { std::vector< T > result; for(auto it = sequence.begin(); it != sequence.end(); ++it) if(predicate(*it)) result.push_back(*it); return result; }
We can use this filter function to extract the odds numbers from a sequence (vector).
#include < iostream > #include < vector > #include < algorithm > #include < functional > using namespace std; int _tmain(int argc, _TCHAR* argv[]) { std::vector< int > nums; for(int i = 0; i < 10; ++i) nums.push_back(i); // get the odds numbers std::vector< int > odds = Filter< int >(nums, [](int i) {return (i % 2) == 1;}); // print the new sequence for_each(odds.begin(), odds.end(), [](int n){std::cout << n << std::endl;}); return 0; }
1 3 5 7 9
You could see in the above example that a second lambda function is used for printing the numbers to the console.
Since the Filter function is a template function it could be used with other types too. In the next example we'll see how to filter words that have at least 4 letters from a sequence of words.
#include < iostream > #include < vector > #include < algorithm > #include < functional > #include < string > using namespace std; int _tmain(int argc, _TCHAR* argv[]) { std::vector< string > snums; snums.push_back("one"); snums.push_back("two"); snums.push_back("three"); snums.push_back("four"); snums.push_back("five"); // filter the words, notice the new lambda std::vector< string > bigwords = Filter< string >(snums, [](string w) {return w.length() > 3;}); // print the selected words for_each(bigwords.begin(), bigwords.end(), [](string s){std::cout << s << std::endl;}); return 0; }
three four five
Let's consider a second example, a Find (template) function, that takes a sequence and a predicate (that checks a condition for an element), and returns the first element in the sequence for which the predicate returned true.
template < class T > T Find(const std::vector< T >& sequence, std::tr1::function< bool (T) > predicate) { for(auto it = sequence.begin(); it != sequence.end(); ++it) if(predicate(*it)) return *it; throw std::runtime_error("Item not found"); }
We'll use this function to find the first element in a sequence that is greater than a given value.
int _tmain(int argc, _TCHAR* argv[]) { std::vector< int > nums; nums.push_back(1); nums.push_back(3); nums.push_back(5); nums.push_back(7); nums.push_back(9); int min; cout << "find first after: "; cin >> min; try { int val = Find< int >(odds, [min](int i){return i > min;}); cout << val << endl; } catch(std::runtime_error& ex) { cout << ex.what() << endl; } return 0; }
If you input 4 for instance, it will return 5. If you input 10, an exception will be thrown. You can see that this time the lambda function is [min](int i){return i > min;}. This means that it captures by value the min variable from the local scope, so that it can compare each element with that given value.
The last example I'm going to show is an accumulator function (also known as aggregate or fold). This function takes a sequence of elements, a seed (or initial value) and a function that specifies how to aggregate the elements, and returns the aggregate.
template < class TSource, class TAccumulate > TAccumulate Aggregate(const std::vector< TSource >& sequence, TAccumulate seed, std::tr1::function< TAccumulate (TSource, TAccumulate) > func) { TAccumulate acc = seed; for(auto it = sequence.begin(); it != sequence.end(); ++it) acc = func(acc, *it); return acc; }
First, we can use it to compute the sum of all elements in a sequence.
int _tmain(int argc, _TCHAR* argv[]) { std::vector< int > nums; for(int i = 1; i <= 10; ++i) nums.push_back(i); int sum = Aggregate< int, int >(nums, 0, [](int e, int acc) {return e + acc;}); cout << "sum = " << sum << endl; int prod = Aggregate< int, int >(nums, 1, [](int e, int acc) {return e * acc;}); cout << "prod = " << prod << endl; return 0; }
sum = 55 prod = 3628800
The first lambda function above sums the current element with the previous sum, which initially is given as 0. The result is 55. The second lambda function multiplies the current element with the previous product, which initially is 1. The result is 3628800.
But the Aggregate function can be used with other types too. Here is a last example with strings.
int _tmain(int argc, _TCHAR* argv[]) { std::vector< string > words; words.push_back("the"); words.push_back("quick"); words.push_back("brown"); words.push_back("fox"); words.push_back("jumps"); words.push_back("over"); words.push_back("the"); words.push_back("lazy"); words.push_back("dog"); string sentence = Aggregate< string, string >( words, "", [](string workingSentence, string next){return next + " " + workingSentence;}); cout << sentence << endl; return 0; }
dog lazy the over jumps fox brown quick the
These were several examples of how lambda functions help us write more generic, and less verbose code. I suggest you read more about lambdas in C++ here.
1 Reply to “Lambdas in C++”