I solved it the static way. It seemed the best solution given the limitations while also being forced to do it inside the constructor.
Header:
static TArray<UTexture*> JobMarkerTextures;
Cpp:
// below the #includes (this can't be assigned in the header file!)
TArray<UTexture*> AJobMarker::JobMarkerTextures = TArray<UTexture*>();
// In constructor method
if (AJobMarker::JobMarkerTextures.Num() == 0) // or use a static boolean here.
{
AJobMarker::JobMarkerTextures.Add(ULib::CreateObject<UTexture>(P_MARKER_WOOD_TEX));
AJobMarker::JobMarkerTextures.Add(ULib::CreateObject<UTexture>(P_MARKER_STONE_TEX));
}
Now they all share the same materials while still having different material instances and without having to dump a variable to the constructor.
Note that the UTexture and UMaterial MUST be created in the constructor but the UMaterialInstanceDynamic MUST be created after BeginPlay() was called while also not being able to use parameters in the constructor in c++.