Skip to main content

One post tagged with "template"

View All Tags

· 2 min read
Schartier Isaac

Generic Param Diffing in Unreal with Templated Structs

Example Context

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 approachUSTRUCTs 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 own IsDiff(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 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.