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 — USTRUCT
s 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
TParam
in 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
USTRUCT
s. - ✅ 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.