Generic Param Diffing in Unreal with Templated Structs
To illustrate the usage, imagine we have a base FACCParam struct and two derived types: FACCIntParam and FACCFloatParam, each implementing their own IsDiff(const TParam&) and GetUniqueKey() methods. These child structs hold specific value types (int, float) and are compared accordingly inside the generic diffing template.
In Unreal Engine, it's common to work with USTRUCT-based parameter types like FACCIntParam, FACCFloatParam, etc. These often inherit from a common base like FACCParam. But what happens when you want to write generic diffing logic, such as detecting added/removed/changed values in a templated function?
You might first think of polymorphism:
struct FACCParam
{
virtual bool IsDiff(const FACCParam& Other) const;
};
Wrong approach — USTRUCTs do not support virtual methods reliably, and Unreal's reflection system doesn’t play well with polymorphic inheritance in structs.
Correct Approach: Use Template Specialization
Instead of relying on virtual methods, use C++ templates and monomorphization. Here's why this works:
- Each
FACCIntParam,FACCFloatParam, etc. implements its ownIsDiff(const TParam&). - When using
TParamin a template, the compiler generates a concrete version at compile time. - You don’t need any virtual functions, nor any base
IsDiff.
Example: Generic Diff Template
template<typename TParam>
void DetectModifiedParams(const TArray<TParam>& NewParams, const TArray<TParam>& OldParams)
{
// Map param by their unique composite key (e.g., Key + CustomInfo)
TMap<FName, TParam> NewMap;
for (const TParam& Param : NewParams)
{
NewMap.Add(Param.GetUniqueKey(), Param);
}
TMap<FName, TParam> OldMap;
for (const TParam& Param : OldParams)
{
OldMap.Add(Param.GetUniqueKey(), Param);
}
// Compare
for (const auto& Elem : NewMap)
{
const FName& UniqueKey = Elem.Key;
const TParam& NewValue = Elem.Value;
if (const TParam* OldValue = OldMap.Find(UniqueKey))
{
if (NewValue.IsDiff(*OldValue))
{
// modified
}
}
else
{
// added
}
}
for (const auto& Elem : OldMap)
{
if (!NewMap.Contains(Elem.Key))
{
// removed
}
}
}
IsDiff stays local to each child
In FACCIntParam:
bool IsDiff(const FACCIntParam& Other) const
{
return !Equals(Other) || Value != Other.Value;
}
No need to declare anything in the base FACCParam.
Summary
- ❌ Do not use virtual methods in
USTRUCTs. - ✅ Templates let you write reusable diff logic with zero runtime cost.
- ✅ Keep
IsDiff()defined per param type. - ✅ Use
TParam::IsDiff()safely inside template functions.
Clean. Performant. Unreal-friendly.
