How do i remove items from a TArray while iterating?

I have a TArray of AActor pointers that i want to perform an action to each actor and if a certain condition is met, remove it from the array while iterating. I tried using normal for loop with i and non-const iterators and for each loop to make this work, but it either crashes or it misses some items. Besides making a copy of the TArray and iterate that and remove from the original, what other way is there to accomplish this? Thank you.

Hey there, traditionally you could achieve this by iterating the array backwards (starting at the end) and removing as you go. If you wanted a more semantic solution you could try one of the following (I’ve used the example of removing all nullptrs):

TArray<FBla*> A;

with a lambda predicate:

A.RemoveAll([](const FBla* Ptr){
    return Ptr == nullptr;
}); 

If you didn’t mind allocating a new array, you could do this in one line:

auto FilteredArray = A.FilterByPredicate([](const FBla* Ptr){
    return Ptr != nullptr;
}); 

The other way is to iterate the array backwards, but this is generally not as clear as the previous two methods:

for (int32 Index = A.Num()-1; Index >= 0; --Index)
{
    if (A[Index] == nullptr)
    {
        const bool bAllowShrinking = false;
        A.RemoveAt(Index, 1, bAllowShrinking);
    }
}
2 Likes

Thank you for your answer :slight_smile: The removal from the array happens inside various methods that are called on the for, so i can’t use those suggestions directly. I’ll do the reverse for loop then :slight_smile:

The first solution with the iterator doesn’t seem to work, My It does not have any functions except Reset, and some operators, but no remove functions or other functions whatsoever… any ideas?

Ah, my apologies. It appears that RemoveCurrent only exists on the TMap iterators, and not TArray (it probably should exist on TIndexedContainerIterator as well). I’ve amended the answer for now.

If you need to use local variables you can use this [&WorldTimeSeconds] or [WorldTimeSeconds] or [&]:

const float WorldTimeSeconds = GetWorld()->TimeSeconds;
...
if (bHasExpiredMessage)
{
	ChatMessages.RemoveAll([WorldTimeSeconds](FHUDMessage& HUDMessage){
		return HUDMessage.IsExpired(WorldTimeSeconds);
	});
}

This is messy. If WorldTimeSeconds is needed, it can be the only one passed by value or reference like

ChatMessages.RemoveAll([WorldTimeSeconds](FHUDMessage& HUDMessage)

Or if a bigger object

ChatMessages.RemoveAll([&BiggerObject](FHUDMessage& HUDMessage)

It’s bad practice to pass everything by refernce

Yep, thanks for the clarification, no need for the down-vote though, that is just a sign of arrogance.

If I want to delete FBla* while removing from TArray, what should I do?