Modern C++ is a common phrase that job specifications will list for developer positions including Audio Programmers. Besides the vast array of audio specific knowledge that you should be fluent in (DSP, signal analysis, aspects of human hearing, Acoustics, music production and more) you should be highly competent in C++. Modern C++ is another vague expression that you must prepare yourself for. Modern C++ basically refers to fluency in most language feature since C++11 even though not every company uses C++17 features.

In this article I want to give you a brief introduction to lambda expressions, a modern C++ language feature. Resources are listed below at the end of the article. I highly suggest you look at those if you need help understanding lambda expressions. As always, remember to experiment with new coding habits on your own! All code used here can be found on my GitHub. I also suggest the great tutorial series by our forum member, MatKat, here. His lesson on lambda expressions is also well written.

What is a Lambda Expression?

A lambda expression is similar to a function. Lambda expression allow one to encapsulate a number of lines of code, manipulate some data and return some data. Like a function you can:

  • pass values by reference or value
  • name a lambda expression and call it later
  • return stuff of any type

Unlike a function a lambda expression does not necessarily need to be named. A question you might be asking is…

Why Bother Using Something That Is Basically A Function?

Lambda expressions behave similarly to a function but they have some different behavior. Consider this…

What if we want to pass a function to a function?

This is not possible directly. However, we can pass a pointer to a function.

Another thing to consider…

What if we want to have some internal state within our function?

We could use the static keyword for the relevant variables somewhere in the function. An alternative is to make a class that contains the variables and the single function that we want to use (a functor).

Lambda expressions are an alternative to do both of these things with less code and simplified data management. To understand how to derive these two aspects, I highly suggest watching Arthur O’Dwyer’s CppCon Presentation on Lambdas from First Principles linked below. Let’s continue on with the basics.

How To Write a Lambda Expression

A lambda expression contains a few essential parts:

  • A Capture List which is denoted with ‘[ ]’. This is followed by…
  • Function Argument(s) which is denoted with ‘( )’. This is followed by…
  • Function Body which is denoted with ‘{ }’
  • When a lambda expression is used there will be invocation parenthesis just like a normal function. This is denoted by ‘( )’

The Simplest Lambda Expression

Let’s see these aspects at work. The following code sample shows the same lambda expression repeated twice. One has minimal comments and the other is fully commented. They do the same thing.:

int main()
{
	/*this is a basic lambda expression...*/
	[]() {
		std::cout << "this is a basic lambda expression...\n";
	}();

	/*this is the same basic lambda expression...*/
	[/*it captures nothing...*/](/*it takes no arguments...*/) {
		/*this is the lambda expression function body...*/
		std::cout << "this is a basic lambda expression...\n";
	}(/*I want to invoke the lambda expression...*/);
}

The result of running this program is:

this is a basic lambda expression...
this is a basic lambda expression...

Passing Values To a Lambda Expression

Let’s actually take a moment to pass a value to the lambda expression. This can be done by:

  1. Specifying the argument
  2. Passing values for the arguments when we invoke the lambda expression
int main()
{
	/*another lambda expression...*/
	[](int inputValue) {
		std::cout << "my inputValue = " << inputValue <<"\n";
	}(100);
}

The result of running this program is:

my inputValue = 100

Naming a Lambda Expression

Of course, it is not always useful to make a lambda expression that must be invoked immediately. Perhaps it is best to name the lambda expression and use it elsewhere down the line.

Let’s do something related to our last code block. We will use the auto keyword to deduce a return type for this lambda expression. This function will return the input divided by a float value of 2.12 . We will invoke this lambda expression twice.

int main()
{
	/*another lambda expression...*/
	auto myLambdaExpression = [](int inputValue) {
		std::cout << "my inputValue = " << inputValue << "\n";
		return inputValue / 2.12F;
	};
	/*assume some other code happened here...*/
	std::cout << "my outputValue = " << myLambdaExpression(100) << "\n";
	std::cout << "my outputValue = " << myLambdaExpression(12) << "\n";
}

The result of running this program is:

my inputValue = 100
my outputValue = 47.1698
my inputValue = 12
my outputValue = 5.66038

The Capture List

The capture list (also called capture block) allows another way to pass variables to the lambda expression as either a value or a reference (just like a function). The function arguments section does this too however, there is a difference. The capture block is used for variables that are within scope. The next code block demonstrates passing values by reference to the lambda expression:

int main()
{
	/*assume we have some internal variables to our main...*/
	std::string nameLongTown = "Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch";
	int indexString = 0;
	/*another lambda expression...*/
	auto myLambdaExpressionByReference = [&nameLongTown, &indexString](int inputValue) {
		std::cout << "the current town is " << nameLongTown << " !\n";
		std::cout << "the " << indexString << " letter is " << nameLongTown.at(indexString) << " !\n";
		indexString += 2;
		std::cout << "the " << indexString << " letter is " << nameLongTown.at(indexString) << " !\n";
		std::cout << "remove letter " << inputValue << " !\n";
		nameLongTown[inputValue] = '_';
		std::cout << "the current town is now " << nameLongTown << " !\n\n";
	};
	myLambdaExpressionByReference(2);
	myLambdaExpressionByReference(15);
}

The result of running this program is:

the current town is Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch !
the 0 letter is L !
the 2 letter is a !
remove letter 2 !
the current town is now Ll_nfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch !

the current town is Ll_nfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch !
the 2 letter is _ !
the 4 letter is f !
remove letter 15 !
the current town is now Ll_nfairpwllgwy_gyllgogerychwyrndrobwllllantysiliogogogoch !

It is also possible to capture all variables within a scope in a few ways.

  • [=] capture all variables by value
  • [&] capture all variables by reference
  • there are other options as well but I suggest checking out the C++ standard and experimenting on your own.

The following code block demonstrates how to do this.

int main()
{
	int a = 0;
	int b = 110.03;
	/*another lambda expression...*/
	auto myLambdaExpressionAll = [=](auto inputValue) {
		std::cout << a << " + " << b << " + " << inputValue << " = " << a+b+inputValue << "\n";
	};
	/*another lambda expression...*/
	auto myLambdaExpressionAllAgain = [=](auto inputValue) {
		std::cout << a << " + " << inputValue << " = " << a + inputValue << "\n";
	};
	myLambdaExpressionAll(33.33);
	myLambdaExpressionAllAgain(0.1);
}

The nice thing is that the lambda expression will only copy over the values that are actually used in the expression (as far as I understand…). The result of running this program is:

0 + 110 + 33.33 = 143.33
0 + 0.1 = 0.1

Using a Lambda Expression Within a Function

Of course, the most important thing about lambda expressions is that we can pass the name of the lambda expression as a function parameter. This is shown in a small example below:

int main()
{
	auto MyVector = std::vector<int>{ 1,13,4,5,3,6,3,1,3,9,4 };
	/*another lambda expression*/
	auto isGreaterThan4 = [](int valueCandidate) {
		return valueCandidate > 4;
	};
	/*another lambda expression*/
	auto is4 = [](int valueCandidate) {
		return valueCandidate == 4;
	};

	auto above4Count = std::count_if(MyVector.begin(), MyVector.end(), isGreaterThan4);
	auto is4Count = std::count_if(MyVector.begin(), MyVector.end(), is4);
	std::cout << "above4Count " << above4Count << "\n";
	std::cout << "is4Count " << is4Count << "\n";
}

The result of running this program is:

above4Count 4
is4Count 2

Conclusion

The point of this article was to familiarize you with lambda expressions. They are not the most common feature in modern C++ but they are useful to understand. It is important that you are not thrown off by the syntax even if it may seem less readable at times. When reading the code provided by others, take it slow and just remember the 4 essentials parts of a lambda expression.

Lambda expressions are useful as an additional tool in standard modern C++. You might find that they will not always be an optimal choice compared to function objects, structs and function pointers. However, they will exhibit utility often in the back end for DSP calculations.

Be good to each other and take it easy…

-Will ☜(゚ヮ゚☜)

Resources

[1] CppCon 2015: Arthur O’Dwyer “Lambdas from First Principles: A Whirlwind Tour of C++

[2] Microsoft Documents: Lambda Expression Syntax

Liked it? Take a second to support William Fehlhaber on Patreon!

Shopping cart

Subtotal
Shipping and discount codes are added at checkout.
Checkout