Search code examples
c++templatescastingreferenceunreal-engine4

C++ Template method return ref to private map value where value's type is parent of T


I have a problem blowing my mind actually, and a challenge for someone, could you help me with that ? :

class UItemEntity : public UObject
{
  GENERATE_BODY()

  public:
    template<typename T=FItemComponent>
    T& GetComponent()
    {
        auto Result = Components[TYPE_ID(T)];

        T Comp = reinterpret_cast<T>(Result);

        return Comp;
    }

  private:
    /** Map from Component's type ID to one of their instance associated to this entity. */
    TMap<const char*, FItemComponent> Components;
}

(It's Unreal c++ code but still c++ :P)

Example of usage :

struct FTestComponent : public FItemComponent { }

UItemEntity* Item;
FTestComponent& Comp = Item->GetComponent<FTestComponent>();

I cannot figure out how to cast the value retrieved from the map... I tried static, dynamic and reinterpret cast but none succeeded.

Here is the kind of error I have :

ItemEntity.h: [C2440] 'reinterpret_cast': cannot convert from 'ValueType' to 'T'

It might be an architecture problem, but I don't know how to work around :/ And I really need a GetComponent on this ItemEntity.

Thanks !

EDIT :

With the help of @Frank, I finally succeed in, there was some Unreal stuff to take into account.

Here it is :

class UItemEntity : public UObject
{
  GENERATE_BODY()

  public:
    template<typename T=FItemComponent>
    T& GetComponent()
    {
        const bool IsItemComponentType = std::is_base_of_v<FItemComponent, T>;
        check(IsItemComponentType);

        FItemComponent* ItemComp = *Components.Find(TYPE_ID(T));

        return Cast<T>(ItemComp);
    }

  private:
    /** Map from Component's type ID to one of their instance associated to this entity. */
    TMap<const char*, FItemComponent*> Components;
}

Solution

  • There may be some Unreal-specific shenanigans at play that I'm not aware of, but in general-purpose C++ code, it would look like this:

    class UItemEntity : public UObject
    {
      GENERATE_BODY()
    
      public:
        template<typename T>
        T& GetComponent()
        {
            static_assert(std::is_base_of_v<FItemComponent, T>);
    
            return *static_cast<T*>(Components.at(TYPE_ID(T)).get());
        }
    
      private:
        /** Map from Component's type ID to one of their instance associated to this entity. */
        TMap<const char*, std::unique_ptr<FItemComponent>> Components;
    }
    

    Explanation:

    • Elements are stored as pointers since different types can be stored in it.
    • at() is used because there is nothing the function can return if the entry is missing.
    • static_cast<> lets the compiler known that the casting from base to derived is intentional.
    • The pointer is dereferenced into a reference with *.
    • I replaced the default template parameter with a static_assert(), according to the discussion in the comments.

    You may want to use std::shared_ptr, std::weak_ptr, std::reference_wrapper or even a raw pointer instead of std::unique_ptr depending on the circumstances. However, it's hard to tell which is the correct one without some more context, so I used the baseline unique_ptr for this example.