UE5 Creating Predicates for TArray, TMap And TSets

While trying to sort a TArray in unreal engine you may come across a requirement to define a  PREDICATE_CLASS& Predicate. In this tutorial, we will explain this type and how to define your own. You will mostly encounter this while working in C++ with algorithms that deal with unreal engine containers like TArray and TMap.

  1. What are Predicates In Unreal Engine

A Predicate in Unreal Engine C++ API is a function that returns a bool that takes two objects of the same class and compares them.

 

How you compare the objects is up to you, you just need to return a bool when you are done. These type of functions will help unreal engine sort your TArray based on anything you would like.

 

For example you may wish to sort Actors by Mesh Size, their speed or any variable.  If you have dealt with overriding comparison operators in C++ , then you’ll be very glad to know Predicates are very similar to that.

  1. Common Functions That Require Predicates

As mentioned in the intro, you may encounter Predicates when dealing with container algorithms like 

And a few more like that and they all require something like
( const PREDICATE_CLASS& Predicate) 
as an argument.

  1. Creating Predicates In UE5 C++

Now that we have covered what they are, We will cover how to create them.

There are two main ways to define predicates. You can either use Lambdas or define a static function in the class of the objects.

The function/lambda IS the predicate and that’s what the functions like Sort() are asking you for.

They are asking you to submit a function that they will use to compare 2 objects that sit in the array. This function will get called multiple times by the Sort() function as it tries to determine what comes first.

Creating & Using Static Function Predicates

Creating your predicates as static functions in your class allows you to reuse them many times in any part of your project. It’s a write-once approach.

  1. Create the static member function

The following points are important.

Go to your class and create a static function that will take two objects from that very class. You can either pass them BY REFERENCE or as Pointers. We will explain when to use which after the following code example.

				
					class UApples
{
    double Size;
    ...
    
    static bool CompareAscending(const UApples& AppleA, const UApples& AppleB)
	{
		return AppleA.Size < AppleA.Size;
	}
	
}
				
			

In this example we are passing them in by reference because the TArray::Sort() function REQUIRES your predicate to take in references even if your TArray is made up of pointers.

So TArray<UApples*> will still require a predicate using UApples&…


If you want to use Pointers you will need to call Algo::Sort… That’s a topic for another day.

Don’t forget to make your function static.

We’ve just created our predicate, now let’s see how to use it.

  1. Use The Static Function As A Predicate

The following code is an example of a predicate (static function) in use, in the sort function. Notice how we don’t include function arguments or brackets. Just submitting the function’s name;

				
					TArray<UApples*> Apples;

Apples.Sort(UApples::CompareAscending);
				
			

Lambda functions vs Static Functions

You can either use static functions or lambda functions. The BEST way to do it is to use both. Let’s look at the pros and cons of each and why you should use both.

Static Functions Pros & Cons

The main advantage of using static functions is that they are reusable. It’s a write-once, call-many-times approach, unlike Lamdas.

 

The disadvantage is they create an ambiguous problem when you overload the predicate. Consider the following function definitions for a better understanding:

				
					static bool IsBigger(AActor* inA,AActor* inB);

static bool IsBigger(TWeakObj<AActor> inA,TWeakObj<AActor> inB)
				
			

When submitting IsBigger as the Predicate the compiler will complain and building will fail with a message along the lines of:

 

“the function IsBigger is Ambiguous” ,

 

meaning, it does not know which one between the two functions to use.
Let’s look at Lambda functions below as they will help us solve this problem.

Lambdas Pros & Cons

The main advantage of using Lambdas is they resolve the problem of function ambiguity described as a disadvantage of using static functions above as lambdas don’t have names.

 

The disadvantage of using lambdas alone is that they are not reusable. You must rewrite the lambda or copy-paste the function algorithms each time you want to use it. This is error-prone and repetitive.

But , there is a solution, let’s look at the solution to both problems below:

Using Lambdas with Static Functions

We are going to solve both disadvantages of using static functions and lamdas by taking the following approach:

  1. Create a static function(predicate)
  2. Submit Lamdas as predicates then call the static functions within the lambda body.

This approach solves both problems, we get both the reusability of static functions and eliminate the ambiguity problem since within the lamda body we use function arguments to specify exactly which function to call. See the example below:

				
					//Lamda
Algo::Sort(SomeArray, [](AActor* inA,AActor* inB)
{
    //call SomeClass's static function here.
    return SomeClass::IsBigger(inA,inB)
}
);
				
			

You will not get any compiler errors related to ambiguity here and you will also get to use reusable code as static functions for your predicates.