Visual Studio 2010 comes with several changes to the C++ compiler to support new features that will be available in the new version of the standard, currently known as C++0x. The new compiler includes, in addition to what was already released in the namespace std::tr1 as a service pack to Visual Studio 2008, support for lambdas, auto, static_assert, decltype and r-value references. In this article I will give an introduction to lambdas.
If you are familiar with .NET and the lambda expressions from .NET than the only novel aspect is the syntax, which is C++ specific. If you are not familiar with lambdas already, the I suggest you do some reading about lambda in mathematics and programming languages.
Let’s start with a simple program. Suppose we want to write a program that generates a sequence of random numbers, displays them in the console, then filters the even numbers from the sequence and displays the new sequence in the console. One way to write such a program is shown below.
#include <iostream> #include <vector> #include <functional> #include <algorithm> #include <iterator> #include <ctime> using namespace std; int randomizer() {return rand()%100;} struct isodd : unary_function<int,bool> { bool operator() (int x) const {return x%2==1;} }; int main() { srand((unsigned long)time(NULL)); // generate 10 random numbers vector<int> v1, v2; generate_n(back_inserter(v1), 10, randomizer); // display the numbers for(vector<int>::iterator it = v1.begin(); it != v1.end(); ++it) { cout << *it << " "; }; cout << endl; // remove the odd numbers and but the remaining numbers in a second vector remove_copy_if(v1.begin(), v1.end(), back_inserter(v2), isodd()); // display the new sequence for(vector<int>::iterator it = v2.begin(); it != v2.end(); ++it) { cout << *it << " "; }; cout << endl; return 0; }
A possible output for the program is:
41 67 34 0 69 24 78 58 62 64 34 0 24 78 58 62 64
In Visual Studio 2010 we can rewrite the code and simplifying it to:
int main() { srand((unsigned long)time(NULL)); // generate 10 random numbers vectorv1, v2; generate_n(back_inserter(v1), 10, [] {return rand()%100;}); // display the numbers for_each(v1.begin(), v1.end(), [](int i) {cout << i << " ";}); cout << endl; // remove the odd numbers and add the remaining numbers in a second vector remove_copy_if(v1.begin(), v1.end(), back_inserter(v2), [](int i) -> bool {return (i%2) == 1;}); // display the new sequence for_each(v2.begin(), v2.end(), [](int i) {cout << i << " ";}); cout << endl; return 0; }
Notice that the code is shorter and we no longer need neither the randomizer() function nor the isodd function object. However, we introduced several new constructions, called lambdas.
[] {return rand()%100;} [](int i) {cout << i << " ";} [](int i) -> bool {return (i%2) == 1;}
A lambda functions is a function object whose type is implementation dependent; its type name is only available to the compiler. A lambda function has several components as shown in the below image.
- lambda_introducer: as the name says this is the part that tells the compiler a lambda function is following. Inside the angled brackets a capture-list can be provided; this is used for capturing variables from the scope in which the lambda is created.
- lambda-parameter-declaration: used for specifying the parameters of the lambda function.
- lambda-return-type-clause: used for indicating the type returned by the lambda function. This is optional, because most of the time the compiler can infer the type. There are cases when this is not possible and then the type must be specified. For the example above, the return type (-> bool) is not necessary.
- compound-statement: this is the body of the lambda.
Here is an example for which the compiler cannot auto infer the type of the return value, which must be specified explicitly.
vector<int> v1; transform(v1.begin(), v1.end(), v1.begin(), [](int n) { if(n > 0) return n * 2; else return 0; });
The compiler issues two warnings in this case:
1>lambdas.cpp(68): error C3499: a lambda that has been specified to have a void return type cannot return a value 1>lambdas.cpp(70): error C3499: a lambda that has been specified to have a void return type cannot return a value
There are two types of lambdas: with state and stateless. The ones we saw so far are all stateless. They have no data members not do they capture any variables from their declaring scope. Such lambdas are always introduced with a empty angled brackets []. However, a lambda can capture variables, both by value and reference. In this case they are called stateful lambdas. In order to see the possibilities for capturing variables let’s start with the following sample.
void function() { int a; double b; string c; auto l = [] (int n) {}; }
The body of the lambda is not important here and we leave it empty. The following examples shows how variables a, b and c can be captured.
- [] (int n) {}: nothing is captured.
- [=] (int n) {}: a, b and c are captured by value.
- [a] (int n) {}: a is captured by value.
- [&] (int n) {}: a, b and c are captured by value.
- [&] (int n) {}: a is captured by reference.
- [a, &b, c] (int n) {}: a and c are captured by value and b by reference.
- [=, &b] (int n) {}: a and c are captured by value and b by reference (same as above).
- [&a, b, &c] (int n) {}: a and c are captured by reference and b by value.
- [&, b] (int n) {}: a and c are captured by reference and b by value (same as above).
As you can see a lambda can capture variables by value and reference in any possible combination. A special case is the capturing of pointer this, which is always done by value. It is illegal to say &this.
In the next example we define a class Range (with a minimum and a maximum) that has a function that filters those elements of a vector that are outside the range and returns a new vector with only the values in the range.
#include <iostream> #include <vector> #include <functional> #include <algorithm> #include <iterator> #include <ctime> using namespace std; template <typename T> class Range { T m_min; T m_max; public: Range(T a, T b): m_min(a), m_max(b){} vector<T> Filter(const vector<T>& input) { vector<T> output; remove_copy_if( input.begin(), input.end(), back_inserter(output), [this](T n) {return !(this->m_min <= n && n <= this->m_max);}); return output; } }; int main() { srand((unsigned long)time(NULL)); // generate 10 random numbers vector<int> v1; generate_n(back_inserter(v1), 10, [] {return rand()%100;}); // display the numbers for_each(v1.begin(), v1.end(), [](int i) {cout << i << " ";}); cout << endl; Range<int> r(1, 25); // filter the numbers outside the range vector<int> v2 = r.Filter(v1); // display the sequence with the numbers in the range for_each(v2.begin(), v2.end(), [](int i) {cout << i << " ";}); cout << endl; return 0; }
A possible output is:
45 41 24 7 10 12 98 33 7 14 24 7 10 12 7 14
It is optional to say this->min. You can directly use m_min and m_max.
In this example we use twice the same lambda for displaying the elements of a vector. To avoid this and write the lambda only once, we can store the lambda in a variable. For the type of the variable we can use the keyword auto, which in C++0x was given a new meaning.
auto print = [](int i) {cout << i << " ";}; // display the numbers for_each(v1.begin(), v1.end(), print); cout << endl;
In the next example we use a lambda function to sum the numbers in a sequence.
int main() { srand((unsigned long)time(NULL)); // generate 10 random numbers vector<int> v1; generate_n(back_inserter(v1), 10, [] {return rand()%100;}); // display the numbers for_each(v1.begin(), v1.end(), [](int i) {cout << i << " ";}); cout << endl; // compute the sum int sum = 0; for_each(v1.begin(), v1.end(), [&sum] (int n) {sum += n;}); // display the sum cout << "sum = " << sum << endl; return 0; }
57 96 17 43 2 79 24 44 14 93 sum = 469
Variable sum, from function main(), is captured by reference, so that it can be directly modified within the body of the lambda. The lambda from this example is transformed by the compiler in a function object that looks like this:
class lambda_functor { int& m_sum; public: lambda_functor(int& sum) : m_sum(sum) {} void operator() (int n) { m_sum += n; } };
So, after the compiler transforms it, this code
int sum = 0; for_each(v1.begin(), v1.end(), [&sum] (int n) {sum += n;});
actually becomes
int sum = 0; for_each(v1.begin(), v1.end(), lambda_functor(sum));
Because the compiler transforms lambdas into function objects, they can be stored in std::tr1::function. The last sample shows such a case.
#include <iostream> #include <vector> #include <functional> #include <algorithm> #include <iterator> #include <string> using namespace std; template <typename T> void apply(const vector<T>& data, const tr1::function<void (T)>& func) { for_each(data.begin(), data.end(), func); } int main() { vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); apply<int>(v1, [](int n) {cout << n * n << " ";}); cout << endl; vector>string> v2; v2.push_back("marius"); v2.push_back("bancila"); v2.push_back("codeguru"); apply<string>(v2, [](string s) {cout << s << " ";}); cout << endl; return 0; }
The output will be:
1 2 3 marius bancila codeguru
Conclusions
Lambdas are a natural step forward in the evolution of the C++ language. The main gain in using lambdas is that we no longer need to explicitly create function objects (classes with a constructr and an overloaded operator() and state). The compiler is now able to do that from a lambda expression.
For additional information about lambda functions in C++ see: