Is it possible to use a UClass as a TMap key?

Hello !

I find myself with several cases where I want to have a collection of UObject*, created via a collection of TSubclassOf so that it’s data driven ( using Properties )
It works well with a TArray.

My issue is that to access an UObject of a particular subclass I need to iterate over the vector, like it’s done for components, and test IsA(). I would prefer an more direct access with a hash map, as accessing an object by class is what i need 90% of the time.

Here is an example of what i’ve tried.

.h

TMap<TSubclassOf<class UState>, UState*> StatesMap; ( also tried TMap<UClass*, UState*> )

.cpp

// creation, object are added to the map at some point
StatesMap.Add(state->GetClass(), state);

// Access
// stateID here is often something like myState::StaticClass()
UState* UStateManager::GetState(const TSubclassOf<class UState>& stateID)
{
	if (StatesMap.Contains(stateID))
	{
		return StatesMap[stateID];
	}
	return nullptr;
}

Contains never returns true.
TSubclassOf has operators to be converted to and from an UClass*, so i think it’s ok on that front.

I also noticed that if I have a MyState state, then state->GetClass() == MyState::StaticClass() always return false which can be the issue.

So what can I change/add to make it work ?

Do I need to override the == operator for UClass* and the ToHash(UClass*) method ? ( could not remember the exact name )

Thanks !

AKiwi.

It looks like you are comparing your MyState class with a MyState subclass, which will fail. I’m actually trying to do the same thing as you, so I don’t yet have the answer. But that is why your comparison is failing.

Hello !
Like I said, SubClassOf can automatically be casted as a Class so the test is ok, Contains gets a class as a parameter.

After digging a little more, i noticed that if i create a data object ( blueprint ) inheriting from MyState, then its class object has a name that’s actually generated.

Basically the hierarchy becomes : UState → MyState → MyBlueprintState.

Then I end up comparing a MyDataState Class to a MyState class which returns false, which is ok I guess.

What I really need then is a way to compare a Class to the first c++ class in the hierachy when dealing with blueprinted UObject.

Okay, I got it working for my case, but with caveats.

struct FClassMapKey
{
	TWeakObjectPtr<UClass> Class;
	FClassMapKey(const UClass* InClass) : Class(InClass) {}
};
inline bool operator==(const FClassMapKey& KeyA, const FClassMapKey& KeyB)
{
	UClass* ClassA = KeyA.Class.Get();
	UClass* ClassB = KeyB.Class.Get();
	if (ClassA == nullptr)
	{
		return (ClassB != nullptr);
	}
	else if (ClassB == nullptr)
	{
		return false;
	}
	return ClassB->IsChildOf(ClassA);
}
inline uint32 GetTypeHash(const FClassMapKey& Key)
{
	return GetTypeHash(Key.Class);
}

TMap<FClassMapKey, uint32> ClassMap;
ClassMap.FindOrAdd(AActor::StaticClass()) = 0;
uint32* Value = ClassMap.Find(APawn::StaticClass); // returns 0
Value = ClassMap.Find(AActor::StaticClass); // returns 0
Value = ClassMap.Find(UActorComponent::StaticClass); // returns nullptr
ClassMap.FindOrAdd(APawn::StaticClass()) = 5;
Value = ClassMap.Find(APawn::StaticClass); // returns 5
Value = ClassMap.Find(AActor::StaticClass); // returns 5

I make a wrapper struct for the UClass* key which, when compared, will return true for subclasses. This means that if I add the AActor class, I can look it up with AActor or APawn (or any other AActor subclass).

I also got it to work using a custom KeyFuncs subclass of TDefaultMapKeyFuncs as a template parameter to TMap, but this made the TMap declaration terrible. Also, I wanted to store the UClass* keys as TWeakObjectPtrs, as the TMaps won’t be serialized. This is especially useful if you are storing classes from a module that may be hotloaded or for Blueprint UClasses that may get unloaded out from under you.