Using lambdas - C++ vs. C# vs. C++/CX vs. C++/CLI

A comparative look at lambdas in C++ and C# with focus on the differences and similarities in lambda usage across the languages and their variants.

Introduction

For a short while it was rather disappointing as a C++ developer that you did
not have lambdas while other languages like C# already had them. Of course,
that's changed now and C++ not only has lambdas but C++ lambdas have greater
syntactic flexibility than C# lambdas. This article will be a comparative look at
the lambda usage in both languages.

Note: The article does not intend to give a comprehensive syntactic coverage
of lambdas in either C# or C++. It assumes you can already use lambdas in one or
both languages. The article's focus is on the differences and similarities in
lambda usage across the languages and their variants.

So in the earlier example, the same instance of the compiler-generated class
was reused inside the for-loop for each iteration. Which explains
the output. The workaround in C# is to force it to create a new instance of the
lambda class each time by introducing a local variable.

In C++, you can specify how a capture is made, whether by copy or by
reference. In the above snippet the capture is by copy and so the code works as
expected. To get the same output as the original C# code, we could capture
by reference (if that was desired for whatever reasons).

C# does not have a syntactic way to make captured variables const.
You'd need to capture a const variable.

Local assignment

In C#, you cannot assign a lambda to a var variable. The
following line of code won't compile.

var f = x => x;

You'll get the following compiler errror.

// Cannot assign lambda expression to an implicitly-typed local variable

VB.NET apparently supports it (via Dim which is their var
equivalent). So it's a little strange that C# decided not to do that. VB.NET
generates an anonymous delegate and uses Object for all the
arguments (since deduction is not possible at compile time).

Consider the C++ code below.

auto func = [](int x) { return x; };

Here func is now of the compiler-generated lambda type. You can
also use the function<> class (although in this case it's not needed).

function<int(int)> copy = func;

When you directly invoke the lambda, the code will be something like:

0102220D call `anonymous namespace'::<lambda0>::operator() (1021456h)

When you call it via the function<> object, the unoptimized code will
look like:

Calling methods from lambdas

What the C# compiler does here is to generate a private instance
method that calls the method defined in the lambda.

privateint <LambdaMethod>(int x)
{
this.Do(x);
return x;
}

It's this method that's passed to RunFoo as a delegate. Now
imagine that you are also capturing a variable in addition to calling a member
method. The compiler now generates a class that captures the variable as well as
the this reference.

Notice how this has been captured there. Now when the
compiler-generated lambda-class's () operator is called, invoking the method is
merely a matter of calling that function and passing the captured this
pointer.

call T::Do (1021069h)

Lambdas in C++/CLI

One big disappointment for C++//CLI developers (all 7 of them) is the fact
that C++/CLI does not support managed lambdas. You can use lambdas in C++/CLI
but they will be native, and so you won't be able to easily interop with managed
code that expects for instance a Func<> argument. You'd have to write
plumbing classes to convert your native lambdas to managed delegates. An example
is shown below.

Well that's a lot of work to get that running smoothly. With some clever use
of macros and template meta programming, you can simplify the code that
generates the native and managed runner-classes. But it'll still be a kludge. So
a friendly advice to anyone planning on doing this is - don't. Save
yourself the pain.

Lambdas with WinRT and C++/CX

The dev-preview may have a subtle bug or two, but the expected behavior is
that when you capture by copy
you will incur AddRef and Release, whereas when you
capture by reference, you will not. The compiler will try and optimize this away
for you in copy-capture scenarios when it thinks it's safe to do so. And this may
be the source of one of the bugs in the dev preview where a Release is optimized
away but the AddRef is not resulting in a potential memory leak.
But it's a certainty that this will all be fixed by beta, so I wouldn't worry
too much about it.

Performance worries

Performance is always an obsession with C++ developers (and some C# and VB
developers too). So very often you find people asking in the forums if using
lambdas will slow down their code. Well without optimization, yes, it will.
There will be repeated calls to the () operator. But any recent
compiler will inline that, so you will not have any performance drop at all when
you use lambdas. In C#, you will not have compile time optimizations but the CLR
JIT compiler should be able to optimize away the extra indirections in most
scenarios. A side effect is that your binary will be a tad bulkier with all the
compiler generated classes, but it's a very small price to pay for the powerful
syntactic and semantic value offered by lambdas, in either C++ or in C#.

Conclusion

Please do submit feedback and criticism via the article forum below. All
feedback and criticism will be carefully read, and tragically sad moments of
silence will be observed for the more obnoxious responses. *grin*

Share

About the Author

Nish is a real nice guy who has been writing code since 1990 when he first got his hands on an 8088 with 640 KB RAM. Originally from sunny Trivandrum in India, he has been living in various places over the past few years and often thinks it’s time he settled down somewhere.

Nish has been a Microsoft Visual C++ MVP since October, 2002 - awfully nice of Microsoft, he thinks. He maintains an MVP tips and tricks web site - www.voidnish.com where you can find a consolidated list of his articles, writings and ideas on VC++, MFC, .NET and C++/CLI. Oh, and you might want to check out his blog on C++/CLI, MFC, .NET and a lot of other stuff - blog.voidnish.com.