Search code examples
c++c++11variadic-templatesrvalue-reference

Loss of rvalue qualifier with variadic template arguments


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


Solution

  • 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)...)};
    }