How Does TArray::ContainsByPredicate Work?

Hello, I was wondering if anyone knows how the TArray::ContainsByPredicate works. I was watching a tutorial video without any voice overs or subtitles so I’m pretty much trying to understand the code by reading. Here’s what they made:

//The HitArrayToAdd is the function parameter variable
for(const auto& Hit : HitArrayToAdd)
{
     //HitArray is a TArray<FHitResult>
     if(!HitArray.ContainsByPredicate([&](const FHitResult& InHit){ return InHit.GetActor() == 
     Hit.GetActor(); } ))
     {
          //If NOT something then add the HitResult to the HitArray
         //Dont know what the if statement checks and how it works
          HitArray.Add(Hit);
     }
}

This code is inside a function that has a parameter of type TArray<FHitResults> and the code above does some check or something which somehow determines if the HitResult is already in the HitArray.

The coder didn’t pass anything into the ContainsBtPredicate so how does it check anything or how does it know what to check?

Can anyone explain how this works or how ContainsByPredicate work?

Contains by predicate uses something called a lambda (the predicate part of compare by predicate).
There’s a good video on it by TheCherno. He talks about them in a purely C++ content, but it still applies to Unreal for obvious reasons.

The square brackets are where you pass in any variable you want.
The & symbol means pass everything that the function has (ie reference to self, the variable parameters, local variables, etc), by reference. Meaning you can change the variables from inside the lambda.
You can use the = symbol to pass everything by copy. Meaning you can still change it, but nothing outside the lambda will ever know that change happened since you’re only modifying a copy.

To just pass in the hit, you can replace the & with “Hit” since that’s what the variable is called. You don’t need to include the type, unlike functions.

!HitArray.ContainsByPredicate([Hit](const FHitResult& InHit){ 
return InHit.GetActor() == Hit.GetActor(); 
}

It’s not strictly necessary since you’re only passing an FHitResult, but you should probably pass it by reference (add a & before Hit: &Hit to do so).

You can pass in multiple variables by separating them by commas.

!HitArray.ContainsByPredicate([Hit, HitArrayToAdd[0], HitArrayToAdd[1], etc](const FHitResult& InHit){ 
return InHit.GetActor() == Hit.GetActor(); 
}

You can also rename the variable names in the parenthesis (const FHitResult& InHit). So you could have const FHitResult& Element. I personally don’t like the name InHit since it tells you nothing- I’d recommend ArrayElement or something to that degree.

1 Like

To add some info to an already perfect explanation :slight_smile:
A Lambda is more or less the c++ way to write a function inside a function.

Instead of doing this:

bool UMyClass::DoThis() {

  // DoStuff

  return DoThat();
}

you can do

bool UMyClass::DoThis() {
  auto DoThat = []() -> bool {
    return true;
  };

  return DoThat();
}

Sometimes you need the "-> " this is called a trailing type.

You can also call a lambda recursively by passing a lambda into itself:

auto HidePropertiesRecursive = [&DetailBuilder](auto&& HidePropertiesRecursive, const FString& InPropertyName, const UClass* InContainerClass) {
	
	
	// DoStuff
	
	HidePropertiesRecursive(HidePropertiesRecursive, PropPath, InContainerClass);
};

About the parameter naming of lambdas… I’m a bit bothered by “InHIt” as well but it really depends on what style you are using through the entire source code, you should keep that consistent. I reserve “In, Ref, Out” for method parameters but in loops and lambdas I like to remove that prefix and Suffix with an X to remind myself I am doing something in a loop.

for (AActor* ActorX : AllActors) {
  
}
2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.