I ran into a situation where I want to process a lot of data in data containers of various types through various methods.
The first thought is a template. Something like this (code not working):
template<typename Function>
void ForDataDo(Function Func) {
TMap<int32, int32> A;
TMap<int32, bool> B;
Func(A);
Func(B);
}
template<typename T>
void ProcessX(TMap<int32, T> InData) {
// Do stuff with InData
}
void Start() {
ForDataDo(ProcessX);
}
Is this possible at all? Or am I limited to copy pasting the data containers and the method names?
I kept the example as simple as possible. The code does not work but the complex examples on cppreference and stackoverflow I found did not work.
How do you feel about a template instead
#define ForDataDo(Func) { \
TMap<int32, int32> A; \
TMap<int32, bool> B; \
Func(A); \
Func(B); \
}
Ahh I have only very rarely used macros. But aren’t macros just plain text to be inserted elsewhere? how is the macro going to make a working template?
This does not do it:
#define ForDataDo(Func) { \
TMap<int32, int32> A; \
TMap<int32, bool> B; \
Func(A); \
Func(B); \
}
#define ProcessX(InData) { \
}
void Start() {
ForDataDo(ProcessX);
}
In the first post I had set up some kind of template idea but it won’t let me pass a function on a class to a template function and certainly not if the argument has parameters of its own.
Yes they are.
Keep ProcessX as a regular template function as it was.
You only need a macro for “ForDataDo”.
The problem is passing a function pointer to a template, this is not possible. Templates do not have an address, they generate additional functions on-the-fly at compile time depending on what they are called with. There might not even be any generated function if you don’t use the template. You could pass a specified template, like ForDataDo(ProcessX<int32>)
but that would probably defeat the purpose you are after.
#define ForDataDo(Func) { \
TMap<int32, int32> A; \
TMap<int32, bool> B; \
Func(A); \
Func(B); \
}
template<typename T>
void ProcessX(TMap<int32, T> InData) {
// Do stuff with InData
}
void Start() {
ForDataDo(ProcessX);
}
1 Like
Alternatively, you can wrap your template in a class, and use that class as typename.
This might be a bit less ugly than macro method.
template<typename T>
void ForDataDo() {
TMap<int32, int32> A;
TMap<int32, bool> B;
T::Func(A);
T::Func(B);
}
struct ProcessX {
template<typename T>
static void Func(TMap<int32, T> InData) {
// Do stuff with InData
}
};
void Start() {
ForDataDo<ProcessX>();
}
1 Like
This works, I like it. Still running into trouble though that I can’t seem to pass on variables to different tasks.
Imagine I have 30+ different types of data containers to process in a specific order.
Using your example this can be done very well, but I can’t modify the functions enough.
Let’s say we have at least 2 functions and all have to go in the same order which I want to set once.
Function 1: ProcessX (of the example)
Function 2: Takes an additional parameter to run the same ordered list of properties from another source struct.
It’s like a “for properties on struct do” but it’s not that simple / optimal to execute with UE.
Example what would make looping over both containers of the map arguments required:
struct FS_TaskMergeData {
template<typename T>
static void Exec(TMap<FName, T>& RefData, const TMap<FName, T> InRequestedData) {
}
};
//
ForDataDo<FS_TaskMerge>(... ?);
I can’t make much sense of what you are trying to do.
How about you do it “normally” with 2 types, then see if/how we can templatize it.
I’ll take another look after I had some sleep, can’t think right now
I’m probably making things more complex then they are.
Alright, I’ve thought about the process and I know what I want but not how
.
What would help me the most is to have some kind of a reusable iterator for a selection of properties, so that I can say: “Do some logic for these 10 properties in order”. This is what we did with the method “ForDataDo” which calls a variable function over the maps.
To finish it I need to add some complexity which I realize might not be supported with templates, or is then likely to create a Frankenstein.
The complexity is that there will be a new second method, say ProcessY, will will also process “something” in the same order with the same map properties as we made before but differenct is that it has to do this over two structs at once, which contain their own maps. So the function parameter differs (additional input), the iterator differs (except for the properties and order)
Like I said, I feel like you can only mix templates up to some point before it becomes a frankenstein haha.
So probably it is this strange “iterator” that I need, which can run over either one or two structs at once for an ordered list of properties. This iterator then gets the current value(s) and passes them on to a specified function like we did with ProcessX.
It’s weird isn’t it? I might have forget about this kind of property iteration in c++ but then I’d have to copy paste long lists of maps to process into multiple functions and risk forgetting a map at some point.
ProcessY would look somewhat like this and calls a template method (TProcessY) for the maps:
void ProcessY(const FS_MapContainer InExternalMaps) {
// Same maps as in ProcessX, same order
//, but now we have 2 containers of them to iterate over simultaneously.
// TProcessY is a template method for the maps
TProcessY(MapA, InExternalMaps.MapA);
TProcessY(MapB, InExternalMaps.MapB);
TProcessY(MapC, InExternalMaps.MapC);
}
What bothers me is, in first example you created empty maps A and B in ForDataDo, for the sake of the example I guess. But now you want something slightly different and I have no idea where MapA,MapB,MapC are supposed to come from in ProcessY example.
Is MapA the same type as InExternalMaps.MapA ? If so I don’t really see how that is different. You can add additional parameters to your function without issues :
template<typename T>
void TProcessY(TMap<FName, T> Data, TMap<FName, T> ExternalData)
{
}
Or is the function ProcessY you showed here, supposed to be merged with ForDataDo somehow, so that you only have to write the list of maps once?
Something like this perhaps ?
struct FS_MapContainer {
TMap<FName, int32> MapA;
TMap<FName, bool> MapB;
TMap<FName, float> MapC;
};
template<typename FuncContainerType>
void ForDataDo(FS_MapContainer& Data, FS_MapContainer* ExternalData = nullptr) {
FuncContainerType::Exec(Data.MapA, ExternalData ? &ExternalData->MapA : nullptr);
FuncContainerType::Exec(Data.MapB, ExternalData ? &ExternalData->MapB : nullptr);
FuncContainerType::Exec(Data.MapC, ExternalData ? &ExternalData->MapC : nullptr);
}
struct ProcessX {
template<typename T>
static void Exec(TMap<FName, T> Data, void* Dummy)
{
// Do stuff with Data
}
};
struct ProcessY {
template<typename T>
static void Exec(TMap<FName, T> Data, TMap<FName, T>* ExternalData)
{
// Do stuff with Data and ExternalData
}
};
void Start() {
FS_MapContainer Data;
FS_MapContainer ExternalData;
ForDataDo<ProcessX>(Data, nullptr);
ForDataDo<ProcessY>(Data, &ExternalData);
}