c++templatesfactoryfactory-pattern

The way to filter objects by type in the factory


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"}

Solution

  • 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)

    Basic reflection framework

    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.

    Generating a unique TypeId

    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>());
    

    Generating the base array

    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 };
    };
    

    Populating the TypeInfoOf map

    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.

    Demo/usage

    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");
    }
    

    Live demo (compiler explorer)