Lambdas
A lambda is a function with no name, written inline, right where it's used, usually to hand to something else
("call this when the timer fires", "sort using this rule"). The [ ] at the start says which outside variables
it can use. The big danger: a lambda that runs later can outlive what it captured.
The shape
[ capture ] ( parameters ) { body }
auto Add = [](int32 A, int32 B) { return A + B; };
int32 Five = Add(2, 3);
[ ]: the capture list, which outside variables the lambda can see. Empty means none.( ): parameters, exactly like a normal function.{ }: the body. The return type is worked out from thereturn.auto: every lambda has its own unnamed type that you can't write out, so you let the compiler fill it in.
Blueprint comparison: it's like dragging a red event pin into a Set Timer by Event or Bind Event node and building the logic right there, instead of making a separate named function or custom event somewhere else.
Captures: what the lambda can see
| Capture | Means |
|---|---|
[] | Nothing from outside |
[this] | The object you're in, so you can use its members and functions |
[Damage] | A copy of Damage, taken when the lambda is made |
[&Damage] | A reference to Damage, the original variable |
[=] / [&] | Everything used, by copy / by reference. Convenient, but hides what's captured; Epic's style guide prefers listing them |
Where you'll meet them in Unreal
1. Finding things in an array
Your FindNativeInputActionByTag, rewritten with FindByPredicate:
const FWarriorInputActionConfig* Found = NativeInputActions.FindByPredicate(
[&InInputTag](const FWarriorInputActionConfig& Config)
{
return Config.InputTag == InInputTag && Config.InputAction;
});
return Found ? Found->InputAction : nullptr;
The array calls your lambda once per element and returns a pointer to the first one where it returned
true, or nullptr. Same result as your loop. Whether it's clearer is a matter of taste.
(Found is a pointer into the array, so the TArray rule applies: use it
straight away.)
2. Sorting
Enemies.Sort([](const AActor& A, const AActor& B)
{
return A.GetActorLocation().Z < B.GetActorLocation().Z; // "A comes before B if..."
});
3. "Do this later": timers, async loads, delegates
FTimerHandle Handle;
GetWorldTimerManager().SetTimer(Handle, [this]()
{
Debug::Print(TEXT("Two seconds later"));
}, 2.0f, false);
Or, from the startup data lesson, the async version of
LoadSynchronous: "load this in the background, and run this lambda when it's done."
A lambda that runs right now (FindByPredicate, Sort) is safe to capture anything. A lambda that runs later (timer, async load, delegate) may run after what it captured is gone:
[this]: if the actor is destroyed before the timer fires,thisis a dangling pointer, the same family of bug as mock Q3. A timer bound withSetTimer(Handle, this, &ThisClass::Func, ...)is cleared when the actor ends play, because the timer knows its object. A lambda timer has no owner, so it isn't, and the same goes for most async-load callbacks. Clear lambda timers yourself inEndPlay, or use the weak pointer pattern below.[&LocalVariable]: the function returns, the local dies, and the lambda holds a reference to nothing, just like the TArray reference.
The safe pattern: capture a weak pointer and check it when the lambda runs:
TWeakObjectPtr<ThisClass> WeakThis(this);
StartAsyncLoad([WeakThis]()
{
if (!WeakThis.IsValid()) return; // we were destroyed while loading: do nothing
WeakThis->OnLoaded();
});
And capture plain values (int32, FName) by copy when the lambda runs later.
When not to use one
- Dynamic delegates. Lambdas can't be a
UFUNCTION. Dynamic delegates, the ones Blueprint can bind too, likeOnComponentBeginOverlap, only accept a namedUFUNCTIONviaAddDynamic(this, &ThisClass::OnOverlap). Your weapon's collision box will need exactly this. (Ordinary C++ delegates are fine with lambdas:AddLambda,CreateLambda.) - It's long. Twenty lines of lambda inside a function call is hard to read. Give it a name.
- You'd copy-paste it. Same logic in two places? Make it a function.
Quiz yourself
What is a lambda, in one sentence?
An unnamed function written inline where it's used, usually passed to something else to call.
What do the three bracket types in [ ]( ){ } hold?
[ ] captures (outside variables it can use), ( ) parameters, { } the body.
[Damage] vs [&Damage]?
A copy taken when the lambda is created, vs a reference to the original variable (which sees later changes, and dangles if the variable dies).
Why is auto used to store a lambda?
Each lambda has its own unique unnamed type that you can't write out.
Why is [this] risky in an async-load callback but fine in Sort?
Sort runs the lambda immediately while the object definitely exists. An async callback runs later, possibly after the object is destroyed, so this dangles.
What's the safe way to refer to the owning object in a delayed lambda?
Capture a TWeakObjectPtr to it, and check IsValid() at the start of the lambda.
What does FindByPredicate return?
A pointer to the first element for which the lambda returns true, or nullptr.
Why can't you bind a lambda to OnComponentBeginOverlap?
It's a dynamic delegate (usable from Blueprint), which binds through reflection with AddDynamic and needs a named UFUNCTION. A lambda has no name and can't be a UFUNCTION.
Blueprint equivalent of a lambda?
Roughly: wiring an event pin straight into a node like Set Timer by Event or Bind Event and building the logic there, instead of a separate named function.