Question:
How do you pass a lambda expression into a function. for example:
int GetCoolInt()
{
int a = 3;
return AddToInt( [a] (int b) {
a += b;
});
}
int AddToInt(int func(int output))
{
return func(3);
}
int output = GetCoolInt();
Context of my issue:
I have the following 2 methods:
FindPlayerStartByTag
calls FindActor
to use logic that will be used in other methods.
//template <typename T>
APlayerStart* FindPlayerStartByTag(FName tag)
{
return FindActor<APlayerStart>(tag, [tag](TObjectIterator<APlayerStart> actor) {
APlayerStart* playerStart;
if (actor->PlayerStartTag == tag)
{
playerStart = *actor;
}
return playerStart;
});
}
This method is the logic to find a specific object in an iterator with the help of the FindPlayerStartByTag
logic
template <typename T>
T* FindActor(FName tag, T* func(TObjectIterator<T> actor))
{
T* returnValue;
if (!tag.IsNone())
{
// Search for actor
for (TObjectIterator<T> itr; itr; ++itr)
{
if (itr->IsA(T::StaticClass()))
{
returnValue = func(itr);
if (returnValue)
{
break;
}
}
}
}
return returnValue;
}
I currently have this error:
I'm confused as to what it means, considering a third method:
template <typename T>
T* FindActorByTag(FName tag)
{
return FindActor<T>(tag, [tag](TObjectIterator<AActor> actor)
{
T* foundActor;
for (FName currentActorTag : actor->Tags)
{
if (tag == currentActorTag)
{
foundActor = *actor;
}
}
return foundActor;
});
}
compiles just fine.
I can see that by adding template <typename T>
gets rid of the error (see the //template <typename T>
, but i don't need this as in the case of APlayerStart, i am already aware of what type the method needs to be.
Anyone help explain this?
Thanks!
Edit:
Here is a version of the method that i was using before i refactored into here:
APlayerStart* FindPlayerStartByTag(FName tag)
{
/*return FindActor<APlayerStart>(tag, [tag](TObjectIterator<APlayerStart> actor) {
APlayerStart* playerStart;
if (actor->PlayerStartTag == tag)
{
playerStart = *actor;
}
return playerStart;
});*/
APlayerStart* returnValue;
if (!tag.IsNone())
{
// Search for actor
for (TObjectIterator<APlayerStart> itr; itr; ++itr)
{
if (itr->IsA(APlayerStart::StaticClass()) && itr->PlayerStartTag == tag)
{
returnValue = *itr;
}
}
}
return returnValue;
}
but compiles.
Edit 2:
This is how i intend on using the methods:
PlayerStart = ActorHelper->FindPlayerStartByTag(PlayerStartTag);
ATreasureChestActor* treasureChestActor = ActorHelper->FindActorByTag<ATreasureChestActor>(TreasureActorTagName);
Edit 3:
The issue seems to be coming from the closure usage!
This is with the use of a closure variable:
and this is without:
Your post is still a mess, with 4 different versions of the same issue. I will focus on the first code snippet as it seems to be the closest one to a [MCVE] and I will clarify how to properly use lambdas and function objects.
int AddToInt(int func(int output))
This is a little bit misleading. I suggest changing it to the equivalent, but more used:
int AddToInt(int (*func)(int))
This means: declaring a function named AddToInt
which:
int
.As you can see, your function accept a classic C function pointer. It won't accept a function object of any type. To note here is that lambdas without capture can be converted to a function pointer.
For instance keeping the above declaration:
AddToInt([](int b) { return b + 1; }); // ok, non-capturing lambda conversion to function pointer
AddToInt([a](int b) { return a + b; }); // error cannot convert capturing lambda to function pointer
The reason is simple to understand. A non-capturing lambda can be equivalent to a free function, but a capturing lambda has a state (formed by the capture set), so it is "more" than a simple, classical free function.
As you can see, accepting function pointers is very much an archaic idiom because of these limitations (and don't even think of passing any kind of function object - e.g. a class with operator()
defined).
For accepting any kind of callable object you generally have two options: the general template or the standard std::function
object.
template <class Fn>
int AddToInt1(Fn func)
{
return func(3);
}
Now you can call AddToInt1
with any kind of callable. Depending of the type of the deduced Fn
type you can have zero overhead with this method. A downside is that you can accept any type, including non-callable ones, or ones with incorrect parameter or return types. Concepts will alleviate most of these downsides.
AddToInt1([](int b) { return b + 1; }); // OK
AddToInt1([a](int b) { return a + b; }); // OK
You also might want to add perfect forwarding (omitted in the example for brevity).
std::function
The other route is to use std::function
:
int AddToInt2(std::function<int(int)> func)
The disadvantage here is the heaviness of the std::function
object. It uses type erasure and that adds a significant amount of performance penalty (which can be perfectly acceptable depending on your usage).
AddToInt2([](int b) { return b + 1; }); // OK
AddToInt2([a](int b) { return a + b; }); // OK
Now, once you get the gist of the above there are some more problems with your code you need to figure out:
[a] (int b) { a += b;};
First of all, are you aware that this lambda does not return anything? Furthermore it tries to modify the captured by value a
which is illegal, as the lambda's operator()
is const
by default for good reason. If you want the lambda to modify the outer a
captured variable, then you need to capture it by reference:
[&a] (int b) { a += b;};
And now you have to really really be careful to not end up with a dangling reference.
But I suspect you meant:
AddToInt([a] (int b) { return a + b;});
But that is just pure speculation on my part.
Here is a fully working example:
template <class Fn>
int AddToInt1(Fn func)
{
return func(3);
}
int AddToInt2(std::function<int (int)> func)
{
return func(3);
}
int GetCoolInt()
{
int a = 3;
return AddToInt1([a] (int b) { return a + b;}); // OK
//return AddToInt2([a] (int b) { return a + b;}); // OK
}
There are some important points I just mentioned here, but elaborating on them would be equivalent to writing a full tutorial on lambdas and beyond, which is out of the scope of this site. In conclusion you have to study the subject on your own.