Search code examples
c++pointersabstract-factory

How to create pointer to different instantiation of template class


In making my first experiments with Abstract Factory in C++(while reading Modern C++ Design - A. Alexandrescu - part 9) i have a question. If classes hierarchy looks like this:

struct B {}; 

struct D1 :public B {};
struct DD1 : public D1 {};
struct DD2 : public D1 {};

struct D2 :public B {};
struct DD3 : public D2 {};
struct DD4 : public D2 {};

I have this abstract factory code:


//abstract factory version 1

struct AbstractFactoryImpl
{
    virtual D1* CreateD1() = 0;
    virtual D2* CreateD2() = 0;
};

struct AbstractFactory : public AbstractFactoryImpl
{
    virtual D1* CreateD1() { return new D1; };
    virtual D2* CreateD2() { return new D2; };
};

template<class ... Ts>
struct ConcreteFactory :public AbstractFactory
{
    using params = std::tuple<Ts...>;
    using T1 = typename std::tuple_element_t<0, params>;
    using T2 = typename std::tuple_element_t<1, params>;

    virtual D1* CreateD1()override
    {
        static_assert(std::is_base_of_v<D1, T1>);
        return new T1;
    };
    virtual D2* CreateD2()override
    {
        static_assert(std::is_base_of_v<D2, T2>);
        return new T2;
    };
};

And using it in client code like this.

//version 1
    AbstractFactory* pFactory;
    pFactory = new ConcreteFactory<DD1, DD3>;
    D1* pD1 = pFactory->CreateD1();
    D2* pD2 = pFactory->CreateD2();

    pFactory = new ConcreteFactory<DD2, DD4>;
    pD1 = pFactory->CreateD1();
    pD2 = pFactory->CreateD2();

It works what i want and looks like a big part of examples in internet.

But in this version of abstract factorys if i want to add more classes in heirarchy(D3,DD5,DD6...) i must create too much code manually so i want to make code more generic.

//abstract factory version 2
template<typename T>
struct AbstractFactoryImpl_
{
    virtual T* Create() = 0;
};

template<typename T>
struct AbstractFactory_ :public AbstractFactoryImpl_<T>
{
    virtual T* Create() override
    {
        return new T;
    }
};

template<class ...Ts>
struct ConcreteFactory_ : public AbstractFactory_<Ts>...
{
    using params = std::tuple<Ts...>;
    using T1 = typename std::tuple_element_t<0, params>;
    using T2 = typename std::tuple_element_t<1, params>;

    template<class T>
    T* Create()
    {
        if constexpr (std::is_base_of_v<T, T1>)return AbstractFactory_<T1>::Create();
        else if constexpr (std::is_base_of_v<T, T2>)return AbstractFactory_<T2>::Create();

    }
};

And it works in client code like this:

        //version 2
    //AbstractFactory* pFactory; -- can`t use because AbstractFactory in version 2 is template class.
    auto pFactory_1 = new ConcreteFactory_<DD1, DD3>;
    D1* pD1_ = pFactory_1->Create<D1>();
    D2* pD2_ = pFactory_1->Create<D2>();

    auto pFactory_2 = new ConcreteFactory_<DD2, DD4>;
    pD1_ = pFactory_2->Create<D1>();
    pD2_ = pFactory_2->Create<D2>();

So it works but i must create two different pointers(pFactory_1,pFactory_2) to different instances of ConcreteFactory_. This is what not expected from Abstract Factory. In first version it was ok because of virtual enheritance to call Create on Base class pointer. But here i have template base class. So i cant call on it pointer Create(). So the question is how to make pointer or something else may be std::any o std::variant to make it possible in this Abstract Factory class design? The client code i wanna make to work is same like first version.

    TYPE* pFactory_ = new ConcreteFactory_<DD1, DD3>;
    D1* pD1_ = pFactory_->Create<D1>();
    D2* pD2_ = pFactory_->Create<D2>();

    auto pFactory_ = new ConcreteFactory_<DD2, DD4>;
    pD1_ = pFactory_->Create<D1>();
    pD2_ = pFactory_->Create<D2>();

Full code: [https://cppinsights.io/s/552bbe01]


Solution

  • With some changes you might have:

    // Way to "pass" Type.
    template <typename T> struct Tag{};
    
    template <typename T>
    struct AbstractFactory
    {
        virtual ~AbstractFactory() = default;
        virtual std::unique_ptr<T> Create(Tag<T>) const = 0;
    };
    
    template <typename ... Ts>
    struct AbstractFactories : virtual AbstractFactory<Ts>...
    {
        using AbstractFactory<Ts>::Create ...;
    };
    
    template <typename T, typename T2>
    struct ConcreteFactory : virtual AbstractFactory<T>
    {
        std::unique_ptr<T> Create(Tag<T>) const override { return std::make_unique<T2>(); }
    };
    
    template <typename Base, typename ...Ts>
    struct ConcreteFactories;
    
    template <typename ... Ts1, typename ...Ts2>
    struct ConcreteFactories<AbstractFactories <Ts1...>, Ts2...> : AbstractFactories <Ts1...>, ConcreteFactory<Ts1, Ts2>...
    {
        using ConcreteFactory<Ts1, Ts2>::Create ...;
    };
    

    With usage

    void Test(AbstractFactories<D1, D2>& factory)
    {
        std::unique_ptr<D1> d1 = factory.Create(Tag<D1>{});
        std::unique_ptr<D2> d2 = factory.Create(Tag<D2>{});
        // ...
    }
    
    int main()
    {
        ConcreteFactories<AbstractFactories<D1, D2>, DD1, DD3> factory1;
        ConcreteFactories<AbstractFactories<D1, D2>, DD2, DD4> factory2;
        
        Test(factory1);
        Test(factory2);
    }
    

    Demo