Help With C++ Sort By Predicate

I am new to lambdas and predicates. I am trying to sort an array of sensed (via the UPerceptionComponent) actors by distance from the pawn.

GetSensedActors().Sort([ControlledMob](const AActor*& LHS, const AActor*& RHS)
 { 
     float distanceOne = (LHS->GetActorLocation() - ControlledMob->GetActorLocation()).Size();
     float distanceTwo = (RHS->GetActorLocation() - ControlledMob->GetActorLocation()).Size()
     return  distanceOne > distanceTwo ; 
});

This gives me a compiler error:
note: see reference to function template instantiation ‘void TArray::Sort>(const PREDICATE_CLASS &)’ being compiled
with
[
PREDICATE_CLASS=AMythicAIController::FindClosestSensedEnemy::<lambda_83149f4068b4539ad9be9f20e1b7889e>
]

Thanks in advance!

1 Like

I’d actually use a simple Struct with the () operator overloaded to help you here (I do something very similar in my own projects).

struct FSortByDistance
{
	FSortByDistance(const FVector& InSourceLocationg)
		: SourceLocation(InSourceLocation)
	{

	}

	/* The Location to use in our Sort comparision. */
	FVector SourceLocation;

	bool operator()(const AActor* A, const AActor* B) const
	{
		float DistanceA = FVector::DistSquared(SourceLocation, A.GetActorLocation());
		float DistanceB = FVector::DistSquared(SourceLocation, B.GetActorLocation());

		return DistanceA > DistanceB;
	}
};

Then to use it with your Sort, you just call:

GetSensedActors().Sort(FSortByDistance(ControlledMob->GetActorLocation()));
2 Likes

Relatively new to C++ (about 1.5 years using it, completely self taught… I have big knowledge gaps). Just to make sure I understand correctly .Sort() is giving FSortByDistance AActor* A and AActor* B, correct?

Is this a better practice, in general, than using the lambda method?

Correct.

Think of Lamba’s just as a function pointer, you’re just setting that function pointer to the Struct’s () operator. You don’t have to use a struct, I just do if I need to pass parameters that aren’t contained within the array as it’s easier to read IMO and you can reuse that struct in other arrays (assuming they use the same types).

Actually, using a struct with state is identical to using a lambda with a capture. A lambda is not like a function pointer, though capture-less lambdas are convertible to function pointers.

The FSortByDistance code above is equivalent to the following with a lambda:

FVector SourceLocation = ControlledMob->GetActorLocation();
GetSensedActors().Sort([SourceLocation](const AActor& A, const AActor& B)
{
    float DistanceA = FVector::DistSquared(SourceLocation, A.GetActorLocation());
    float DistanceB = FVector::DistSquared(SourceLocation, B.GetActorLocation());

    return DistanceA > DistanceB;
});

But while lambdas are more than capable of expressing stateful predicates, you may still want to define a standalone FSortByDistance struct to allow code reuse.

However, note that I have changed the code above to pass AActor by reference instead of by pointer. This is due to a legacy decision that causes TArray::Sort() to auto-deference pointers, and will have been the reason for your compile errors. Unfortunately, we can’t realistically revert that behaviour now, so instead, you should prefer to use Algo::Sort over TArray::Sort, as this correctly passes pointers to the predicate as expected:

struct FSortByDistance
{
    explicit FSortByDistance(const FVector& InSourceLocation)
        : SourceLocation(InSourceLocation)
    {
    }

    /* The Location to use in our Sort comparison. */
    FVector SourceLocation;

    bool operator()(const AActor* A, const AActor* B) const
    {
        float DistanceA = FVector::DistSquared(SourceLocation, A->GetActorLocation());
        float DistanceB = FVector::DistSquared(SourceLocation, B->GetActorLocation());

        return DistanceA > DistanceB;
    }
};

Algo::Sort(GetSensedActors(), FSortByDistance(ControlledMob->GetActorLocation()));

Hope this helps,

Steve

2 Likes