No viable conversion from TArray to TArray

Hey guys,
dont understand what is wrong here.

I have following structure ABaseCharacter as a Base class, and APlayerCharacter : ABaseCharacter.

then I have two arrays, TArray baseChars and TArray chars.

Now if I try to do baseChars = chars,

compiler tells no viable conversion from TArray to TArray…

I cant understand why, those two types inherited, why it doesnt understand that?

How can I pass TArray with realisation class pointers to TArray with base pointers?

Generally speaking, the point of having a common base class for objects (like your ABaseCharacter) is so you can pass pointers using that simple base class and then you simply do a cast to whatever child class when you need it.

If you turn on RTTI (It’s a boolean named bUseRTTI in your .Build.cs file), it may be able to infer the type and do the copy, but RTTI is slow. I think you’d be better off only storing those objects in a TArray of ABaseCharacter and casting them using UE4’s Cast template like so:

TArray<ABaseCharacter> PlayerCharacters;

// Now if I need to call some APlayerCharacter code specifically, you would just cast your ABaseCharacter pointer like so:

// For this example, just assume PlayerCharacters has 1 entry in the array, normally you'd do a check before accessing directly like this:

APlayerCharacter* MyPlayerCharacter = Cast<APlayerCharacter>(PlayerCharacters[0]);
check(MyPlayerCharacter); // or use an If statement here
MyPlayerCharacter->DoPlayerCharacterStuff();

Thank you!

Hi,

Unfortunately, Matt’s suggestion of RTTI will not help you here. It is always illegal to treat a container of Derived* as if it was a container of Base*.

The reason that TArray does not let you do such a conversion is because it is type-unsafe. Imagine the following:

void AddUnrelatedCharacter(TArray<ABaseCharacter*>& Array)
{
    Array.Add(GetWorld()->SpawnActor(AUnrelatedCharacter::StaticClass(), ...));
}

void SetUpActors()
{
    TArray<AMyCharacter*> Array;
    AddUnrelatedCharacter(Array); // X
}

You want line X to compile, but if it did, your TArray would end up holding an AUnrelatedCharacter*.

This is not a limitation of UE4, but a known trait of languages with mutable state:

In addition to Matt’s suggestion of always dealing in TArray and casting on each use, I suggest two other options. You can use explicit constructor syntax to convert your container:

TArray<AMyCharacter*> MyChars;
TArray<ABaseCharacter*> BaseChars(MyChars);

This will make a copy of the array. This may not be desirable. For example: adding a new actor to BaseChars will add it to the copy, not the original array. Also, copying can be slow if you’re having to do it frequently or your array is large.

Alternatively, you can use reinterpret_cast to take advantage of the fact that UObject*s always have the same address as their Super:

void ReadFromArray(const TArray<ABaseCharacter*>& ConstBaseChars);

TArray<AMyCharacter*> MyChars;
const TArray<ABaseCharacter*>& ConstBaseChars = reinterpret_cast<const TArray<ABaseCharacter*>&>(MyChars);
ReadFromArray(ConstBaseChars);

Note that you need to be aware of what you are doing here. This is saying ‘treat the memory of my array as if it was a different type’. We are taking advantage of the fact that TArray is layout-compatible with TArray. This should be fine if you only plan to read from the container, which is why we make it a const reference.

But it is not fine in general:

  • Do not cast away const, or you end up with the same case as I mentioned earlier:

    // Bad!
    TArray<ABaseCharacter*>& BaseChars = const_cast<TArray<ABaseCharacter*>&>(ConstBaseChars);
    AddUnrelatedCharacter(BaseChars);

  • Do not reinterpret_cast if you want an array of a pointer type other than a base UObject:

    UCLASS()
    class AMyCharacter : public ABaseCharacter, public IBase
    {
    // …
    };

    void ReadFromInterfaceArray(const TArray<IBase*>& BaseInterfaces);

    // Bad!
    const TArray<IBase*>& BaseInterfaces = reinterpret_cast<const TArray<IBase*>&>(MyChars);

    ReadFromInterfaceArray(BaseInterfaces);

If in doubt, avoid this construct entirely.

Hope this helps,

Steve

Thank you Steve!