Im trying to save a lot of code duplication using templates. Ideally I would like to do something along the lines of this (I know its garbage code):
class Registries {
public:
template<typename T>
void AddToRegistry(T thing) {
registry.push_back(thing);
}
template<typename T>
std::vector<T>* GetRegistry<T>() {
return ®istry();
}
private:
template<typename T>
std::vector<T> registry; // auto-magically created vectors for each used type
};
int main() {
Registries registries;
CoolThing thing1;
NiceThing thing2;
// ... Many N types
registries.AddToRegistry(thing1);
registries.AddToRegistry(thing2);
std::vector<CoolThing>* cool_registry = registries.GetRegistry<CoolThing>();
std::vector<NiceThing>* nice_registry = registries.GetRegistry<NiceThing>();
}
It of course doesnt work because I can't template a class member - a variable won't be created for each type, and these functions wouldnt know what one to return):
I could do this, but they'll all be stored in static memory (not ideal):
class CoolThing {
public:
static std::vector<CoolThing> registry;
};
class NiceThing {
public:
static std::vector<NiceThing> registry;
};
class Registries {
public:
template<typename T>
void AddToRegistry(T thing) {
T::registry.push_back(thing);
}
}
int main() {
Registries registries;
registries.AddToRegistry(CoolThing());
registries.AddToRegistry(NiceThing());
// ... Many N types
}
I also tried switch and if-else within a template but they seem incompatible:
class CoolThing {
public:
const static ThingType TYPE = COOL;
};
class NiceThing {
public:
const static ThingType TYPE = NICE;
};
class Registries {
public:
template<typename T>
void AddToRegistry(T thing) {
switch (T::TYPE) {
case ThingType::COOL:
cool_registry.push_back(thing);
return;
case ThingType::NICE:
nice_registry.push_back(thing);
return;
default:
return;
}
}
template<typename T>
std::vector<T>* GetRegistry<T>() {
if (T::TYPE == ThingType::COOL) {
return &cool_registry;
}
else if (T::TYPE == ThingType::NICE) {
return &nice_registry;
}
return nullptr;
}
private:
std::vector<CoolThing> cool_registry;
std::vector<NiceThing> nice_registry;
// ... Many N types
};
int main() {
Registries registries;
CoolThing thing1;
NiceThing thing2;
// ... Many N types
registries.AddToRegistry(thing1);
registries.AddToRegistry(thing2);
std::vector<CoolThing>* = registries.GetRegistry<CoolThing>();
std::vector<NiceThing>* = registries.GetRegistry<NiceThing>();
}
I feel like my options are the static way, or writing a long list of (20+ types) function overloads to add to the correct registry type.
Any template whizzes out there know an alternative? Many thanks
Another solution that does not rely on runtime type erasure (std::any
) or global singletons would be to store a tuple of std::vector<T>
:
#include <vector>
template <typename... Ts>
class Registries {
public:
template<typename T>
void AddToRegistry(T thing) {
GetRegistry<T>().push_back(thing);
}
template<typename T>
std::vector<T>& GetRegistry() {
return std::get<std::vector<T>>(registry);
}
private:
std::tuple<std::vector<Ts>...> registry;
};
struct CoolThing {};
struct NiceThing {};
int main() {
Registries<CoolThing, NiceThing> registries;
CoolThing thing1;
NiceThing thing2;
// ... Many N types
registries.AddToRegistry(thing1);
registries.AddToRegistry(thing2);
std::vector<CoolThing>& cool_registry = registries.GetRegistry<CoolThing>();
std::vector<NiceThing>& nice_registry = registries.GetRegistry<NiceThing>();
}
Note that this requires you to know all possible types when instantiating the registry.
PS: If that bothers you, here is a solution with type erasure that does not have this restriction: https://godbolt.org/z/nvWchfMaK