How do you reference & modify TArray pointers as out parameters?

A few times a year, I run into this issue and never find a solution online. Here’s the latest use case:

I have an interface function that returns an array of other interfaces as an out parameter pointer (the use case is unimportant, it’s simply to contextualise the question). Storing a reference to and modifying that array elsewhere has got me stumped! I’d want to modify that collection from objects other than the one implementing the interface. From what I understand you return a pointer to the array and can then modify the collection. Compiling that solution seems okay but breakpointing through the logic shows that the local pointer is a copy of the out parameter and not the collection referenced within the implementing object.

Am I missing something insanely obvious and thus it’s not documented online? Admittedly I’m pretty new to C++ but coming from a C# background I’d assume this should be trivial.

Simplified and condensed code below:

// Executive implementing object:
bool UPinventorySubsystem::TryGetAssociates_Implementation(TArray<TScriptInterface<IAssociate>>& AssociatesOut)
{
	AssociatesOut = Associates;
	return true;
}

bool UPinventorySubsystem::AddAssociate_Implementation(const TScriptInterface<IAssociate>& Associate)
{
	const TScriptInterface<IExecutive> Referencer = TScriptInterface<IExecutive>(this);
	return UAssociateLibrary::TryAddAssociateTo(Associate, Referencer));
}

// UAssociateLibrary
bool UAssociateLibrary::TryAddAssociateTo(const TScriptInterface<IAssociate>& Associate,
										    const TScriptInterface<IExecutive>& Executive)
{
	TArray<TScriptInterface<IAssociate>> Associates;
	Executive->Execute_TryGetAssociates(Executive.GetObject, Associates);
	Associates.Add(Associate);
	return true;
}

Reference is not the same thing as pointer. A reference cannot be re-pointed to a different memory block. Unlike pointers, when you assign to a reference, you are copying value from right to left, you are not making the reference point to a different memory block.

In your function TryGetAssociates, the reference parameter is already pointing to some memory block (defined by the caller). When doing AssociatesOut = Associates you are copying the value, but the two variables are not pointing to the same block.

When you do this :

TArray<TScriptInterface<IAssociate>> Associates;
Executive->Execute_TryGetAssociates(Executive.GetObject, Associates);
Associates.Add(Associate);

It is essentially equivalent to this :

TArray<TScriptInterface<IAssociate>> Associates;
Associates = Executive->Associates;  // COPY into local variable
Associates.Add(Associate);  // ADD to local array
// when leaving, local array is destroyed, Executive's array is left unchanged

If you want to modify Executive->Associates directly, you need to use actual pointers, however that is not supported in blueprints. If you want blueprint support, provide function(s) that let the caller modify the array, such as wrappers for Add/Remove, or simply a Set function :

bool UPInventorySubsystem::TrySetAssociates_Implementation(const TArray<TScriptInterface<IAssociate>>& InAssociates) {
    Associates = InAssociates;
    return true;
}

Then from caller code you can get, modify, and then set the array

bool UAssociateLibrary::TryAddAssociateTo(const TScriptInterface<IAssociate>& Associate,
										    const TScriptInterface<IExecutive>& Executive)
{
	TArray<TScriptInterface<IAssociate>> Associates;
	Executive->Execute_TryGetAssociates(Executive.GetObject, Associates);
	Associates.Add(Associate);
	Executive->Execute_TrySetAssociates(Executive.GetObject, Associates);
	return true;
}

Doing lots of copies is not ideal obviously.
Instead, you can provide more meaningful interface functions, such as :

bool UPinventorySubsystem::TryAddAssociate_Implementation(const TScriptInterface<IAssociate>& Associate)
{
    Associates.Add(Associate);
    return true;
}

Then you don’t even need the library method since anybody can just call that interface directly :

IExecutive::Execute_TryAddAssociate(Executive.GetObject(), Associate);

Alternatively, if you don’t need to modify the array from blueprints, you can use actual pointers :

TArray<TScriptInterface<IAssociate>>* UPinventorySubsystem::TryGetAssociates()
{
	return &Associates;
}

bool UAssociateLibrary::TryAddAssociateTo(const TScriptInterface<IAssociate>& Associate,
										    const TScriptInterface<IExecutive>& Executive)
{
    TArray<TScriptInterface<IAssociate>>* Associates = Executive->TryGetAssociates();
    if (Associates != nullptr)
    {
        Associates->Add(Associate);
        return true;
    }
}
1 Like

That Q&A Sage Badge is well-deserved :pray:

Thank you for clarifying everything and sorry for my C# terminaology/misunderstandings.

My ultimate goal with the Library function was to provide a simple boilerplate variant of IAssociate::AddAssociate_Implementation that could be used in most cases.

I’m a stickler for not limiting my code to either CPP or BPs, so your TrySetAssociates_Implementation feels like the way to go.