3

I'm trying to write a generic factory class with automatic self-registration of types for my application.

In order to allow flexibility, this factory has a variadic template parameter for constructor arguments; i.e. it allows either default constructors or constructors requiring any number of arguments. The parameter names are fairly self-explanatory; the AbstractType is the abstract base class for the factory; the returned object will be a std::shared_ptr of this type.

This generic factory works fine and all of the tests I wrote for it were working perfectly fine, until I tried to create a factory for a specific class hierarchy containing classes (as data members) that do not permit copy construction or assignment. I tried to fix this by using an rvalue reference for the template argument; however, this does not work in the manner that I expected. Specifically, if I define the factory instance to take a constructor parameter of type A&&, this fails with an error telling me that there is no conversion from A to A&&.

In this sample, My_Abstract, Data_Context, and Other_Class are declared elsewhere. As described briefly above, the idea here is that a concrete type CT will have a constructor with the signature:

class CT {
    CT(Data_Context&&, Other_Class const&);
/* ... */
};

class My_Abstract; // forward declaration

template <class ConcreteType>
using My_Factory_Registrar =
    Factory_Registrar<ConcreteType, My_Abstract, Data_Context &&, Other_Class const&>;
using My_Factory =
    Generic_Factory<My_Abstract, Data_Context &&, Other_Class const&>;

Perhaps I am missing something fundamental here, but when I revise the code to be:

template <class ConcreteType>
using My_Factory_Registrar =
    Factory_Registrar<ConcreteType, My_Abstract, Data_Context const&, Other_Class const&>;
using My_Factory =
    Generic_Factory<ConcreteType, Data_Context const&, Other_Class const&>;

Then everything compiles and works correctly. I am well aware that an r-value can be used for a const reference parameter, so I am not confused as to why this worked, as much as I am completely confused why the first code snippet did not work. It almost appears like the rvalue reference qualifier was removed in the process of variadic template expansion.

I'm not sure whether or not it will help at all in clarifying thing, but the code for the factory class itself is as follows:

template <class AbstractType, class...ConstructorArgs>    
class Generic_Factory{

    public:
        static std::shared_ptr<AbstractType> Construct(std::string key, ConstructorArgs... arguments){
            auto it = Get_Registry()->find(key);
            if (it == Get_Registry()->cend())
                return nullptr;

            auto constructor = it->second;
            return constructor(arguments...);
        }

        using Constructor_t = std::function<std::shared_ptr<AbstractType>(ConstructorArgs...)>;
        using Registry_t = std::map< std::string, Constructor_t>;

        Generic_Factory(Generic_Factory const&) = delete;
        Generic_Factory& operator=(Generic_Factory const&) = delete;

    protected:
        Generic_Factory(){}
        static Registry_t* Get_Registry();

    private:
        static Registry_t* _registry_;

    };

    template <class ConcreteType, class AbstractType, class...ConstructorArgs>
struct Factory_Registrar : private Generic_Factory<AbstractType, ConstructorArgs...>{
        using Factory = Generic_Factory<AbstractType, ConstructorArgs...>;
        using Constructor_t = typename Factory::Constructor_t;

public:
        Factory_Registrar(std::string const& designator, Constructor_t  object_constructor){
            auto registry = Factory::Get_Registry();
            if (registry->find(designator) == registry->cend())
                registry->insert(std::make_pair(designator, object_constructor));
        }
    };

Thanks for your help.

Shmuel

Shmuel Levine
  • 550
  • 5
  • 18

2 Answers2

3

Perfect Forwarding is intended to be used in these cases. Your code is quite long. I use a simplified version of make_unique for demonstration.

template <typename T, typename ...Args>
auto make_unique(Args&&... args) -> std::unique_ptr<T>
{
    return std::unique_ptr<T>{new T(std::forward<Args>(args)...)};
}
nosid
  • 48,932
  • 13
  • 112
  • 139
  • Thanks for the answer -- that was extremely fast! I think 20s after I posted it... I'd thought that Perfect Forwarding was related to the answer, but I couldn't really understand how (and that syntax above for the template pack expansion is truly baffling-- BUT it works). – Shmuel Levine Jun 30 '14 at 17:13
1

To be able to forward rvalue input arguments without losing type information, you need to use universal references. The prototypical example is :

template<class T>
void Forwarder(T&& t)
{
Func(std::forward<T>(t));
}

This way, there is no loss of type information and the right overload of Func gets called.

On the other hand, if the body of Forwarder called Func(t), only the lvalue overload of Func would match.

Pradhan
  • 16,391
  • 3
  • 44
  • 59
  • thanks for your answer, which is basically the same as nosid's, except that his response came in first, and it shows exactly how to use it syntactically-correctly with the variadic template pack. – Shmuel Levine Jun 30 '14 at 17:15