Download

Using Overrides to call Overloads without having to Cast



class A
{ virtual A* GetContext() { return this; } //Returning the base class

virtual void PrintMessage() { //log "This is A"; }
 }

class B : public A
{ virtual B* GetContext() override { return this; } // overriding in the hopes that by simply calling GetContext() will call derived GetContext()

virtual void PrintMessage() override { //log "This is B"; }
 }




class Manager
{ TArray<A*> context; //we have an array of the parent class, so we don't have to make an array for every derived class

void CallContext(A*); //Here is an overloaded function that takes A
{
  [INDENT=2]//print "This is the base overload"
A->PrintMessage();[/INDENT]
  }

void CallContext(B*);  //And the same that takes B
{
  [INDENT=2]//print "This is the derived overload"
B->PrintMessage();[/INDENT]
  }
    void Initialize()
{
    context.Add(NewObject<A>());
    context.Add(NewObject<B>());

    for (auto* objectContext : context)
    {
  [INDENT=2]//The hope is that because we stored an object of B in the context array, that by simply calling virtual GetContext() of any object in the context array...
// we can then access different overloads, so we have access to specific logic in that derived class, while only calling from a single place[/INDENT]
  
        CallContext(objectContext->GetContext());
    }
}
 }


Is this logically possible? In my head, even though objectContext is of class A type in the loop, that by calling the virtual GetContext() I could access an overload for a derived class, without casting or knowing any of the derived classes in Initialize().

When ran, in the output log I get:
“This is the base overload”
“This is A”
“This is the base overload”
“This is B”

I just realized, this is because B = A, therefore it simply calls the first overload, right?

Hint: Covariant return types :slight_smile:

Thank you for your reply, but I do have covariant return types:



virtual A* GetContext();
virtual B* GetContext() override;


Here is my actual code, in case I’m making some simple mistake and overlooking it.

Context.h
[SPOILER]




UCLASS(Abstract)
class QUERIES_API UContext : public UObject
{
    GENERATED_BODY()

public:

    virtual ~UContext()
    {

    }

    virtual UContext* GetContext() PURE_VIRTUAL(UContext::GetContext, return this; );

    virtual void PrintMessage() PURE_VIRTUAL(UContext::PrintMessage, );

    TSubclassOf<UContext> GetSubContext()
    {
        return this->StaticClass();
    }

protected:

    UPROPERTY()
    ACharacter_Base* subject = nullptr;

};

UCLASS()
class QUERIES_API UContext_Battle : public UContext
{
    GENERATED_BODY()

public:

    virtual UContext_Battle* GetContext() final
    {
        return this;
    }

    void PrintMessage() final
    {
        UE_LOG(LogTemp, Log, TEXT("This is the battle context. UContext_Battle::PrintMessage"));
    }

};

UCLASS()
class QUERIES_API UContext_City : public UContext
{
    GENERATED_BODY()

public:

    virtual UContext_City* GetContext() final
    {
        return this;
    }

    virtual void PrintMessage() final
    {
        UE_LOG(LogTemp, Log, TEXT("This is the city context. UContext_City::PrintMessage"));
    }

};



[/SPOILER]

Manager.h
[SPOILER]




    void CallContext(UContext* inContext)
    {
        UE_LOG(LogTemp, Log, TEXT("This is the base Context. Manager::CallContext"));
        inContext->PrintMessage();
    }

    void CallContext(UContext_Battle* inContext)
    {
        UE_LOG(LogTemp, Log, TEXT("This is the battle Context. Manager::CallContext"));
        inContext->PrintMessage();
    }

    void CallContext(UContext_City* inContext)
    {
        UE_LOG(LogTemp, Log, TEXT("This is the city Context. Manager::CallContext"));
        inContext->PrintMessage();
    }



[/SPOILER]

Manager.cpp
[SPOILER]



void AManager::InitializeManager()
{
    TArray<UContext*> context;

    context.Add(NewObject<UContext_Battle>());
    context.Add(NewObject<UContext_City>());

    for (auto* objectContext : context)
    {
        CallContext(objectContext->GetContext()); // Here we should be calling the overload based on which derived class of UContext this object is
        FString name = objectContext->GetSubContext()->GetName(); // To see if we can get the actual subclass
        UE_LOG(LogTemp, Log, TEXT("%s is the context. Manager::InitializeManager()"), *name);
    }

}


[/SPOILER]

Covariant isn’t magic, it just simplify some cases (like factory pattern or clone pattern).

Resolving virtuals occurs at run-time. As long as you hold TArray<Base*> then compiler choose function with Base* as parameter, because this is all he knows at compile-time. So, even if you use covariants then called function with base class pointer have signature from base class. Covariant are valid because upcasting are always safe and thus implicit.

That will always call function CallContext(UContext inContext)* because of auto* type.

I suppose I shouldn’t try to be more clever than the compiler ;p Thank you

I was hoping I was being too clever. Thank you

Meant to ask, is there no way to get the functionality I’m looking for besides if statements?
I saw that you can do type conversions for switch statements, but it’s overkill since I only plan for one place to check types.

Hmm… to not undo work already done I think I would do something like this:



UCLASS() class UContext : public UObject {GENERATED_BODY() public: void PrintMessage() {UE_LOG(LogTemp, Warning, TEXT("This is the base Context. Manager::CallContext"));}};
UCLASS() class UContext_City : public UObject {GENERATED_BODY() public: void PrintMessage() {UE_LOG(LogTemp, Warning, TEXT("This is the city Context. Manager::CallContext"));}};
UCLASS() class UContext_Battle : public UObject {GENERATED_BODY() public: void PrintMessage() {UE_LOG(LogTemp, Warning, TEXT("This is the battle Context. Manager::CallContext"));}};




template<typename T = UContext>
static void CallContext(UObject* Context)
{
    if (auto* CTT = Cast<T>(Context))
    {
        CTT->PrintMessage();
    } else {check(false);}
}


And remove the three different CallContext() you have there.

Then a call to it would be something like this:



UContext_Battle* Battle = NewObject<UContext_Battle>(Context,TEXT("BattleContext"));
​​​​​​​
CallContext<UContext_Battle>(Battle);


That would certainly work for the example I provided, thank you for that. However, I should have specified why I’m trying to achieve this.

I am using the UGameplayTask system. I’m creating a system based on Dave Mark’s Utility AI (See this lecture for a create talk on elegantly simple AI if you’d like) for evaluating for a task.

The basis of his system is that your tasks are assigned evaluators. These evaluators take in a (generic) context object for the task to create a score.

What I’d like to be able to do is have contexts for different situations (battle, city) so I can have specific information pertaining to them. I’m trying to keep the logic as simple as possible and avoid casting.

I hoped that by having a virtual GetContext() on each context object I would be able to simply call that on an array of tasks who store a base member of Context*, while still having the evaluator select the correct overload.

I may just resort to using delegates and a switch(ContextEnum).

I do this pretty often with Blueprint classes, the only thing I do differently is I actually give a template object (CDO) when I use the NewObject<> function:



Proxy = NewObject<UMachine>(Context,
                MachineClass,
               *MachineClass->GetName(),
                RF_NoFlags,
                MachineClass->GetDefaultObject(false)
            );


Then I call InitMachine() without casting.
I have several subclasses of “UMachine” and I don’t go casting anything around…

Gotcha. The thing is I’m not calling functions on the sub contexts other than GetContext(). My AManager class has an array of UObjectives. These objectives hold their own UContext object, could be 1 of a number of subclasses based on the situation.

I plan to pass these UObjectives into an assigned evaluator. My desired design is that the evaluator calls objectiveObject->contextObject->GetContext(), which will return the subclass. Using the subclass, it’s supposed to select the correct evaluation function.

Sorry if I’m not being clear, I’m still designing it so I have no code that could clarify what I mean. The previous stuff in this post was to test the theory of accessing an overload with an overridden getter without knowing the subclass.