I have the factory that contains a map with the names of objects as key and pointer to function as value
template<class T>
static T* makeObj()
{
return new T();
}
using createFunction = std::function<void*()>;
map<std::string, createFunction> types;
template<class T>
static void add(const std::string& name)
{
types.insert(name, makeObj<T>);
}
I want to add an additional function to get a filtered list of types (get the list of inherited classes), something like this:
template<class baseType>
static std::list<std::string> getFilteredListOfTypes()
{
}
The question is how to do it. The first idea is to create a map with struct as a value and store an object of the appropriate type there, I can use dynamic_cast and check all types, but I doubt that creating an object is a good idea. Another way is to use std::is_base_of:
template< class Base, class Derived >
struct is_base_of;
is_base_of take two types, class Base I can get from my getFilteredListOfTypes, but how to get class Derived? Create an object and use decltype... but again here an object is created.
Is there a way to create such a filter function without creating objects? Is it a bad idea to create an object here? Is it a bad idea to create an object for each item of map? I am a little scared of a situation where the factory will have hundreds of totally not small types.
UPD AppFactory.h:
class AppFactory
{
public:
template<class T>
static T* makeObj()
{
return new T();
}
using createFunction = std::function<void*()>;
using registerMap = tsl::robin_map<std::string, createFunction>;
static registerMap& get();
template<class T>
static std::unique_ptr<T> createUnique(const std::string& name)
{
return std::unique_ptr<T>(create<T>(name));
}
template<class T>
static std::shared_ptr<T> createShared(const std::string& name)
{
return std::shared_ptr<T>(create<T>(name));
}
template<class T>
static T* create(const std::string& name)
{
if(get().contains(name))
{
return static_cast<T*>(get()[name]());
}
return nullptr;
}
template<class T>
static bool add(const std::string& name)
{
auto resultPair = get().insert_or_assign(name, makeObj<T>);
return resultPair.second;
}
};
#define APPFACTORY_ADD(classname) \
namespace { static bool addName = AppFactory::add<classname>(#classname); }
AppFactory.cpp:
AppFactory::registerMap& AppFactory::get()
{
static AppFactory::registerMap map;
return map;
}
Usage of factory:
...
APPFACTORY_ADD(SomeClass);
...
AppFactory::createUnique<SomeBaseClass>("SomeClass")
...
UPD 2 test implementation of getFilteredListOfTypes
struct typedata
{
createFunction func;
Object* obj;
};
using registerMap = tsl::robin_map<std::string, typedata>;
...
template<class T>
static bool add(const std::string& name)
{
typedata data;
data.func = makeObj<T>;
data.obj = new T();
auto resultPair = get().insert_or_assign(name, data);
return resultPair.second;
}
...
template<class BaseType>
static std::list<std::string> getFilteredListOfTypes()
{
std::list<std::string> typeList;
for(auto it = get().begin(); it != get().end(); ++it)
{
if(dynamic_cast<BaseType*>(get()[it->first].obj))
{
typeList.push_back(it->first);
}
}
return typeList;
}
Usage of getFilteredListOfTypes
class A : public Object
...
class B : public A
...
class C : public B
...
class D : public C
...
class E : public B
...
lst = AppFactory::getFilteredListOfTypes<A>(); // lst -> {"A", "B", "C", "D", "E"}
lst = AppFactory::getFilteredListOfTypes<B>(); // lst -> {"B", "C", "D", "E"}
lst = AppFactory::getFilteredListOfTypes<C>(); // lst -> {"C", "D"}
lst = AppFactory::getFilteredListOfTypes<D>(); // lst -> {"D"}
This answer proposes to write your own tiny "runtime reflection" framework. Hopefully one day, C++ will have proper compile-time (or runtime) reflection, and this answer will become obsolete. Until then...
TL;DR: Live demo (compiler explorer)
Our goal will be to generate an instance of a non-templated class TypeInfo
for each return type of the factory. This type has a member function bool isBaseOf(TypeInfo const& other)
that can be used at runtime, and behave like std::is_base_of
.
#include <span>
struct TypeTag{};
using TypeId = TypeTag const*;
// This is NOT a templated class.
class TypeInfo {
public:
using DirectBases = std::span<TypeInfo const* const>;
explicit constexpr TypeInfo(TypeId typeId, DirectBases directBases)
: typeId_{ typeId }
, directBases_{ directBases }
{}
constexpr bool isBaseOf(TypeInfo const& child) const {
if (*this == child) {
return true;
}
for (TypeInfo const* directBase : child.directBases_) {
if (isBaseOf(*directBase)) {
return true;
}
}
return false;
}
constexpr bool operator==(TypeInfo const& other) const {
return typeId_ == other.typeId_;
}
protected:
TypeId typeId_;
DirectBases directBases_;
};
And a compile-time map of typename T
-> TypeInfo{...}
, acting as a reflection info database:
template<typename T>
struct TypeInfoOf;
template<typename T>
constexpr TypeInfo const& typeInfoOf() {
return TypeInfoOf<T>::value;
}
With this, getFilteredListOfTypes
can be implemented as:
#include <list>
#include <string>
#include <unordered_map>
// NOTE (digression): I see a singleton anti-pattern here...
class AppFactory {
public:
struct RegisterMapValue {
// CreateFunction func;
TypeInfo const* typeInfo;
};
using RegisterMap = std::unordered_map<std::string, RegisterMapValue>;
static RegisterMap& get() {
static RegisterMap map;
return map;
}
template<class T>
static void add(const std::string& name) {
get()[name] = { /*func,*/ &typeInfoOf<T>() };
}
static std::list<std::string> getFilteredListOfTypes(TypeInfo const& base) {
std::list<std::string> typeList;
for(auto const& mapPair : get()) {
if (base.isBaseOf(*mapPair.second.typeInfo)) {
typeList.push_back(mapPair.first);
}
}
return typeList;
}
template<class BaseType>
static std::list<std::string> getFilteredListOfTypes() {
return getFilteredListOfTypes(typeInfoOf<BaseType>());
}
};
Now to construct these TypeId objects, we need to generate two things.
TypeId
object for each type.TypeInfo*
holding the direct bases of the object.template<typename T>
struct UniqueId {
static constexpr auto tag = TypeTag{};
};
template<typename T>
constexpr TypeId uniqueTypeIdOf() {
return &UniqueId<T>::tag;
}
static_assert(uniqueTypeIdOf<std::string>() == uniqueTypeIdOf<std::string>());
static_assert(uniqueTypeIdOf<unsigned>() != uniqueTypeIdOf<std::string>());
Sadly we can't automatically get the list of direct bases of a type. We'll just make a utility that takes the base types as input, turns them into a std::array<TypeInfo*>
object, and builds the TypeInfo
object:
#include <array>
#include <type_traits>
template<typename T, typename... DirectBases>
struct TypeInfoMaker {
private:
static_assert((std::is_base_of_v<DirectBases,T> && ...));
static_assert((!std::is_same_v<T,DirectBases> && ...));
using BasesArray = std::array<TypeInfo const*, sizeof...(DirectBases)>;
static constexpr auto directBases = BasesArray{ {&typeInfoOf<DirectBases>()...} };
public:
static constexpr auto value = TypeInfo{ uniqueTypeIdOf<T>(), directBases };
};
This is where the major drawback of this answer comes on: the list of base class is duplicated between the class declaration, and the TypeInfo
construction. Both must be updated together manually (unless macros are involved, but it won't be pretty).
The 2 static_assert
in TypeInfoMaker
offer some protection against mistakes: TypeInfoMaker
will produce a compile-time error if a base is incorrect (but will accept indirect bases), and should prevent circular references (TypeInfo::isBaseOf will terminate). We can't do much against missing bases.
// User-provided class (unchanged)
class A {
public:
virtual ~A() = default;
};
// The user has to declare A's reflection data as follows:
template<>
struct TypeInfoOf<A> : TypeInfoMaker<A> {};
class B : public A {};
template<>
struct TypeInfoOf<B> : TypeInfoMaker<B, A> {};
class C : public B {};
template<>
struct TypeInfoOf<C> : TypeInfoMaker<C, B> {};
class D : public C {};
template<>
struct TypeInfoOf<D> : TypeInfoMaker<D, C> {};
class E : public B {};
template<>
struct TypeInfoOf<E> : TypeInfoMaker<E, B> {};
On the plus side, populating the TypeInfoOf
map requires just a couple lines per type.
Concatenate all previous code snippets and add the following:
#include <iostream>
#include <string_view>
template<typename T>
void printFilteredList(std::string_view typeName) {
std::cout << "filteredListOfTypes<" << typeName << ">: ";
for (auto const& value : AppFactory::getFilteredListOfTypes<T>()) {
std::cout << value << ' ';
}
std::cout << '\n';
}
int main() {
AppFactory::add<A>("A");
AppFactory::add<B>("B");
AppFactory::add<C>("C");
AppFactory::add<D>("D");
AppFactory::add<E>("E");
printFilteredList<A>("A");
printFilteredList<B>("B");
printFilteredList<C>("C");
printFilteredList<D>("D");
printFilteredList<E>("E");
}