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.
Basic usage in C# and C++
Here's a very simple C# method that I'll use to demo C# lambdas:
static void RunFoo(Func<int, int> foo)
{
Console.WriteLine("Result = " + foo(3) + "\r\n");
}
And here's the corresponding C++ version:
void RunFoo(function<int(int)> foo)
{
std::cout << "Result = " << foo(3) << "\r\n\r\n";
}
Both versions take a function that has an int
parameter and
returns an int
. Here's a very simple C# example showing how
the method is called with a lambda.
RunFoo(x => x);
Notice how the argument type as well as the return type are inferred by the
compiler. Now here's the C++ version.
RunFoo( [](int x) -> int { return x; });
The argument type and return type need to be specified in C++. Well, not
exactly, the following will work too.
RunFoo( [](int x) { return x; });
Notice how I've removed the return type specification. But now, if I make a
small change, it will not compile anymore.
RunFoo( [](int x) { x++; return x; });
You get the following error.
The intellisense tooltip explains what happened there.
This code will compile.
RunFoo( [](int x) -> int { x++; return x; });
Note that this may change in a future release where the return type will be
deduced even for multi-statement lambdas. It is unlikely that C++ will support
type-deduction in lambda arguments though.
Capturing variables - C# vs C++
Both C# and C++ let you capture variables. C# always captures variables by
reference. The following code's output will make that fairly obvious.
var foos = new List<Func<int, int>>();
for (int i = 0; i < 2; i++)
{
foos.Add(x =>
{
Console.WriteLine(i);
return i;
});
}
foos.ForEach(foo => RunFoo(foo));
foos.Clear();
That will output:
2
Result = 2
2
Result = 2
I reckon most of you know why that happens, but if you don't here's why.
Consider this very basic code snippet.
int i = 5;
RunFoo(x => i);
The compiler generates a class (pseudo-code) that looks something like below:
sealed class LambdaClass
{
public int i;
public LambdaClass(){}
public int Foo(int x){ return i;}
}
And the call to RunFoo
is compiled as (pseudo-code):
var local = new LambdaClass();
local.i = 5;
RunFoo(new Func<int, int>(local.Foo));
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.
for (int i = 0; i < 2; i++)
{
int local = i;
foos.Add(x =>
{
Console.WriteLine(local);
return local;
});
}
foos.ForEach(foo => RunFoo(foo));
This forces the compiler to create separate instances of the lambda class
(there's only one generated class, with multiple instances). Now take a look at
similar C++ code.
std::vector<function<int(int)>> foos;
for (int i = 0; i < 2; i++)
{
foos.push_back([i](int x) -> int
{
std::cout << i << std::endl;
return i;
});
}
for each(auto foo in foos)
{
RunFoo(foo);
}
foos.clear();
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).
for (int i = 0; i < 2; i++)
{
foos.push_back([&i](int x) -> int
{
std::cout << i << std::endl;
return i;
});
}
What happens in C++ is very similar to what happens in C#. Here's some
pseudo-code that shows one plausible way the compiler might implement this
(simplified version):
class <lambda0>
{
int _i;
public:
<lambda0>(int i) : _i(i) {}
int operator()(const int arg)
{
std::cout << i << std::endl;
return i;
}
};
If you look at the disassembly, you will see a call to the ()
operator where the lambda is executed, something like the following:
00CA20CB call `anonymous namespace'::<lambda0>::operator() (0CA1415h)
The const-ness of captured variables
C++ captures variables as const
by default, whereas C# does
not. Consider the following C# code.
int val = 10;
RunFoo(x =>
{
val = 25;
return x;
});
Now the syntactic equivalent C++ code follows.
int val = 10;
RunFoo([val](int x) -> int
{
return x;
});
To lose the const
-ness of the captured variable, you need to
explicitly use the mutable
specification.
RunFoo([val](int x) mutable -> int
{
val = 25; return x;
});
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.
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:
0102222B call std::tr1::_Function_impl1<int,int>::operator() (102111Dh)
- - - >
010284DD call std::tr1::_Callable_obj<`anonymous namespace'::<lambda0>,0>::_ApplyX<int,int &> (10215FAh)
- - - >
0102B73C call `anonymous namespace'::<lambda0>::operator() (1021456h)
Of course, the compiler will trivially optimize this so the release mode
binary code will be identical in both cases.
Calling methods from lambdas
The following example shows a method being called from a C# lambda.
void Do(int x) { }
void CallDo()
{
RunFoo(x =>
{
Do(x);
return x;
});
}
What the C# compiler does here is to generate a private
instance
method that calls the method defined in the lambda.
private int <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.
private class <Lambda>
{
public int t;
public Program __this;
public <Lambda>() {}
public int <CallDo>(int x)
{
this.__this.Do(x + this.t);
return x;
}
}
This is a little more obvious in C++ because you have to explicitly capture
the this
pointer to call a member function from the lambda.
Consider the example below.
int Do(int h)
{
return h * 2;
}
void Test()
{
auto func = [this](int x) -> int
{
int r = Do(1);
return x + r;
};
func(10);
}
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.
class LambdaRunner
{
function<int(int)> _lambda;
public:
LambdaRunner(function<int(int)> lambda) : _lambda(lambda)
{
}
int Run(int n)
{
return _lambda(n);
}
};
The above class is the native implementation of the lambda runner. The
following class is the managed wrapper around it.
ref class RefLambdaRunner
{
LambdaRunner* pLambdaRunner;
int Run(int n)
{
return pLambdaRunner->Run(n);
}
public:
RefLambdaRunner(function<int(int)> lambda)
{
pLambdaRunner = new LambdaRunner(lambda);
}
Func<int, int>^ ToDelegate()
{
return gcnew Func<int, int>(this, &RefLambdaRunner::Run);
}
void Close()
{
delete pLambdaRunner;
}
};
Using it would look something like the following.
auto lambda = [](int x) -> int { return x * 2; };
auto lambdaRunner = gcnew RefLambdaRunner(lambda);
int result = lambdaRunner->ToDelegate()(10);
lambdaRunner->Close();
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
You can use lambdas in C++/CX with WinRT types.
auto r = ref new R();
r->Go();
auto lambda = [r]()
{
};
auto lambda = [&r]()
{
}
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*
History
- November 3, 2011 - Article first published.
- November 4, 2011
- Minor typo and formatting fixes
- Added section on calling methods from lambdas