Lambdas in VC++ 2010

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
	vector v1, 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:



About the Author

Marius Bancila

Marius Bancila is a Microsoft MVP for VC++. He works as a software developer for a Norwegian-based company. He is mainly focused on building desktop applications with MFC and VC#. He keeps a blog at www.mariusbancila.ro/blog, focused on Windows programming. He is the co-founder of codexpert.ro, a community for Romanian C++/VC++ programmers.

Comments

  • Correction

    Posted by hspc on 01/29/2010 05:03am

    Nice article, I think this line: # [&] (int n) {}: a, b and c are captured by value. should be: # [&] (int n) {}: a, b and c are captured by reference.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • When individual departments procure cloud service for their own use, they usually don't consider the hazardous organization-wide implications. Read this paper to learn best practices for setting up an internal, IT-based cloud brokerage function that service the entire organization. Find out how this approach enables you to retain top-down visibility and control of network security and manage the impact of cloud traffic on your WAN.

  • U.S. companies are desperately trying to recruit and hire skilled software engineers and developers, but there is simply not enough quality talent to go around. Tiempo Development is a nearshore software development company. Our headquarters are in AZ, but we are a pioneer and leader in outsourcing to Mexico, based on our three software development centers there. We have a proven process and we are experts at providing our customers with powerful solutions. We transform ideas into reality.

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date