7

I'm trying to write a factory class which will have a standard interface that looks like this:

Register<MyBase, MyDerived> g_regDerived("myderived");  // register to factory

now calling:

auto* d = Factory<MyBase>::instance().create("myderived", 1, 2, 3);

will invoke the constructor MyDerived(1,2,3) and return a pointer to the created object

This sounds like something that should be possible with C++11 but I could not figure out how to do it.
Starting from a standard type-erasure factory:

template<typename BaseT>
class Factory {
public:
    static Factory* instance() {
        static Factory inst;
        return &inst;
    }
    template<typename T>
    void reg(const string& name) {
        m_stock[name].reset(new Creator<T>);
    }
    BaseT* create(const string& name) {
        return m_stock[name]->create();
    }
private:
    struct ICreator {
        virtual BaseT* create() = 0;
    };
    template<typename T>
    struct Creator : public ICreator {
        virtual BaseT* create() {
            return new T;
        }
    };
    std::map<string, std::unique_ptr<ICreator>> m_stock;
};

template<typename BaseT, typename T>
class Register {
public:
    Register(const QString& name) {
        Factory<BaseT>::instance()->reg<T>(name);
    }
};

The problem here is fact that once you erase the type of the created object, you can no longer pass arbitrary template forwarded arguments since you need to pass them through a virtual function.

The answer to this question:
How to pass a function pointer that points to constructor?
talks about something similar but the answer there is to go through a function which is specific for every derived class. I want to use the class constructor directly and not have to write a create() function.

Community
  • 1
  • 1
shoosh
  • 76,898
  • 55
  • 205
  • 325
  • 4
    I think doing this *perfectly* is impossible; the parameter of a function is really *compile-time* contrast to other some languages, such as Java or C#. (they can call a function dynamically through reflection) Of course C++ has *compile-time* reflection, but you want to call function dynamically. I suggest to imitate these run-time reflection: you can use something like `void *create(const std::vector> &params);`. You can also provide macros or templates to help client make this "create" function. – ikh Mar 19 '15 at 10:26
  • I get's a lot easier if you allow some kind of dynamic initialisation by introducing a `config` class that is basically a list of `boost::any` and is always allowed as a single argument to the to-be-created classes. – filmor Mar 19 '15 at 15:16
  • What's wrong with a `create()` function? – Adrian Mar 19 '15 at 20:05
  • @Adrian you can assume I have perfectly good reason for my question. Spamming it with useless answers which I already made clear are no help to me is plain rude. – shoosh Mar 19 '15 at 22:16
  • I try and not assume, but without more information, it is possible that you are trying to do something that isn't necessary and/or counter productive. Stating your reasons may help people come up with solutions that you haven't thought of because you are so close to the problem. As someone who has 36k rep, you should know this. As for you saying that I'm rude because you assume that I was spamming *is* rude however. If you want a better answer, then propose a question that is more useful to the general community. – Adrian Mar 19 '15 at 23:37
  • @Adrian "How do I do X without doing Y?" - "You should do Y" - this is rude, and patronizing. Imagining the reason I don't want `create()` is trivial. I don't have control over the classes the factory needs to create and I can't change them at will. Of course I can write these `create()` functions on my own but that would defeat the purpose of a generic factory and I might as well just call `new` directly instead. – shoosh Mar 20 '15 at 08:04
  • If you notice, I actually put the `create()` template function within the template factory as I actually thought that actually might be the reason that you didn't want the other solution in the first place. – Adrian Mar 20 '15 at 14:23
  • "How do I do X without doing Y?" without specifying why you don't want "Y" also isn't helpful because the question isn't fully stated in terms of why a restriction must exist. I had to read between the lines to guess that you didn't want the other solution because you didn't want a `create()` function in each class. I wan't trying to be condescending or rude. I was trying to find a solution to your problem based on missing and/or semi-implied information. – Adrian Mar 20 '15 at 14:32

1 Answers1

9

I don't know why your aversion to writing a create() function. So here is one that I implemented.

#include <iostream>
#include <utility>

using namespace std;

class C
{
public:
    virtual char const* whoAmI() const = 0;
};

class A : public C
{
public:
    A(int a1)
    {
        cout << "A(" << a1 << ")" << endl;
    }
    
    A(float a1)
    {
        cout << "A(" << a1 << ")" << endl;
    }
    
    virtual char const* whoAmI() const override
    {
        return "A";
    }
};

class B : public C
{
public:
    B(int a1)
    {
        cout << "B(" << a1 << ")" << endl;
    }
    
    B(float a1)
    {
        cout << "B(" << a1 << ")" << endl;
    }

    virtual char const* whoAmI() const override
    {
        return "B";
    }

};

template<typename BASET>
class Factory
{
public:
    // could use a is_base type trait test here
    template <typename T, typename...ARGs>
    static BASET* create(ARGs&&...args)
    {
        return new T(forward<ARGs>(args)...);
    }

};
int main()
{
   Factory<C> factory;
   C* a = factory.create<A>(1);
   C* b = factory.create<B>(1.0f);
   cout << a->whoAmI() << endl;
   cout << b->whoAmI() << endl;
   return 0;
}

NOTE: I didn't do everything that yours does, I merely implemented the create function. I leave the final implementation up to you.

This uses perfect forwarding to enable a varidict template to pass any number of parameters to a constructor. Your register function can then store a function pointer of a particular template instance, for a particular parameter set.

EDIT

I forgot to use the appropriate forward<ARGs>(args)... call to implement perfect forwarding. It has now been added.

As for you thinking that this is not useful, here is the full implementation of your factory using perfect forwarding and varidict templates allowing a specific number of parameters of particular types for a particular factory instance:

#include <string>
#include <map>
#include <memory>
#include <utility>
#include <iostream>

using namespace std;

    template<typename BaseT, typename...ARGs>
    class Factory {
    public:
        static Factory* instance() {
            static Factory inst;
            return &inst;
        }
        template<typename T>
        void reg(const string& name) {
            m_stock[name].reset(new Creator<T>);
        }
        BaseT* create(const string& name, ARGs&&...args) {
            return m_stock[name]->create(forward<ARGs>(args)...);
        }
    private:
        struct ICreator
        {
            virtual BaseT* create(ARGs&&...) = 0;
            
        };
        template<typename T>
        struct Creator : public ICreator {
            virtual BaseT* create(ARGs&&...args) override
            {
                return new T(forward<ARGs>(args)...);
            }
        };
        std::map<string, std::unique_ptr<ICreator>> m_stock;
    };
    
    template<typename BaseT, typename T, typename...ARGs>
    class Register {
    public:
        Register(const string& name) {
            auto instance = Factory<BaseT, ARGs...>::instance();
            instance->template reg<T>(name);
        }
    };

struct C
{
    virtual char const * whoAmI() const = 0;
};

struct A : public C
{
    A(int a1, int a2)
    {
        cout << "Creating A(" << a1 << ", " << a2 << ")" << endl;
    }
    
    virtual char const * whoAmI() const override
    {
        return "A";
    }
};

struct B : public C
{
    B(int b1, int b2)
    {
        cout << "Creating B(" << b1 << ", " << b2 << ")" << endl;
    }
    B(int b1, int b2, int b3)
    {
        cout << "Creating B(" << b1 << ", " << b2  << ", " << b3 << ")" << endl;
    }
    virtual char const * whoAmI() const override
    {
        return "B";
    }
};

typedef int I;
Register<C, A, I, I> a("a");
Register<C, B, I, I> b("b");
Register<C, B, I, I, I> b3("b");
int main()
{
    C* a = Factory<C, I, I>::instance()->create("a", 1, 2);
    C* b = Factory<C, I, I>::instance()->create("b", 3, 4);
    C* b3 = Factory<C, I, I, I>::instance()->create("b", 5, 6, 7);
    cout << "I am a " << a->whoAmI() << endl;
    cout << "I am a " << b->whoAmI() << endl;
    cout << "I am a " << b3->whoAmI() << endl;
    return 0;
}

Is that what you want? If you don't want to deal with the function parameters, use a helper template function to deduce them for you like so:

template <typename BaseT, typename...ARGs>
BaseT* create(const string& name, ARGs&&...args)
{
    return Factory<C, ARGs...>::instance()->create(name, forward<ARGs>(args)...);
}

int main()
{
    C* a = create<C>("a", 1, 2);
    C* b = create<C>("b", 3, 4);
    C* b3 = create<C>("b", 3, 4, 5);
    cout << "I am a " << a->whoAmI() << endl;
    cout << "I am a " << b->whoAmI() << endl;
    cout << "I am a " << b3->whoAmI() << endl;
    return 0;
}

Which has the added bonus of allowing multiple constructor signatures available through the apparent single function API (it only looks like one, but is actually N where N is the number of different signatures you allow). This all can be viewed through this online demo.

You'll still need to use the same registration as I depicted before though, which could be shortened by way of a macro.

If this is still not what you want, then add additional detail to your question.

Community
  • 1
  • 1
Adrian
  • 10,246
  • 4
  • 44
  • 110
  • Thank you for spamming the question with a useless answer. now please delete it – shoosh Mar 19 '15 at 22:23
  • Well, the initial answer was useless since it did not address the question, partially stated as it was. The edited answer does actually seem to work. You are instantiating Factory for every set of arguments so Factory is not really a singleton anymore. BUT I could make something like a MetaFactory which will hold references to all the different Factories which will be a singleton (That's important for me for an unrelated reason which I don't care to delineate). Thank you and I apologize :) – shoosh Mar 20 '15 at 18:37
  • 2
    @shoosh,The original answer was pointing you in the direction you wanted to go. I don't want to do all your work for you. ;) – Adrian Mar 20 '15 at 18:40