Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Factory using variadic template issues

I'm just trying something out - I wanted to create a generic factory which will returns shared_ptr to a type.

I have a derived class which uses static methods to return a shared_ptr to the base class. The idea is that I'd like to be able to register these methods with a generic factory, but it can't determine which method to register at compile time. Perhaps there is a way of achieving this using SFINAE, but I'm just beginning to understand its complexities.

Apologies for the rather long code sample, also available at http://coliru.stacked-crooked.com/a/331e08de86004592

Enabling more than one of the factory methods in 'DerivedA' will cause a compile error.

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <memory>

// Factory which returns a shared_ptr of type T.
template<class T, class Tag, class... Args>
class NameFactory
{
public:
    typedef std::function<std::shared_ptr<T>(Args...)> Function;

    static NameFactory& instance();

    void registerType(const std::string& type, const Function& createFunction);
    std::shared_ptr<T> createObject(const std::string& type, Args&&... arguments);
private:
    NameFactory() {}

    std::unordered_map<std::string, Function> m_functionMap;
};

template<class T, class Tag, class... Args>
NameFactory<T, Tag, Args...>& NameFactory<T, Tag, Args...>::instance()
{
    static NameFactory<T, Tag, Args...> m_instance;
    return m_instance;
}

template<class T, class Tag, class... Args>
void NameFactory<T, Tag, Args...>::registerType(const std::string& type, const Function& createFunction)
{
    m_functionMap[type] = createFunction;
}

template<class T, class Tag, class... Args>
std::shared_ptr<T> NameFactory<T, Tag, Args...>::createObject(const std::string& type, Args&&... arguments)
{
    auto iter(m_functionMap.find(type));

    if (iter != m_functionMap.end())
    {
        return (iter->second)(std::forward<Args>(arguments)...);
    }

    throw std::logic_error("Cannot find constructor for type '" + type + "'");
}

template<class T, class Tag, class... Args>
class NameFactoryRegistration
{
public:
    typedef NameFactory<T, Tag, Args...> Factory;
    NameFactoryRegistration(const std::string& type, const typename Factory::Function& createFunction)
    {
        Factory::instance().registerType(type, createFunction);
    }
private:
};

class MyBase
{
public:
    typedef std::shared_ptr<MyBase> SPtr;
};

class DerivedA : public MyBase
{
public:
    static SPtr create()
    {
        return SPtr(new DerivedA);
    }

    // Enabling this factory method (and/or the two args method below causes an 'unresolved overloaded function type' error
    //static SPtr create(const std::string& s)
    //{
    //    return SPtr(new DerivedA(s));
    //}

    //static SPtr create(const std::string& s, double d)
    //{
    //    return SPtr(new DerivedA(s,d));
    //}
private:
    DerivedA()
    {
        std::cout << "DerivedA - no args" << std::endl;
    }

    DerivedA(const std::string& s)
    {
        std::cout << "DerivedA - one arg: " << s << std::endl;
    }

    DerivedA(const std::string& s, double d)
    {
        std::cout << "DerivedA - two args: " << s << " : " << d << std::endl;
    }
};

// Tags to help differentiate the factories
struct NoArgsReg;
struct SingleArgReg;
struct TwoArgReg;

typedef NameFactory<MyBase, NoArgsReg> NoArgsFactory;
typedef NameFactoryRegistration<MyBase, NoArgsReg> NoArgsRegistration;

typedef NameFactory<MyBase, SingleArgReg, const std::string&> SingleArgFactory;
typedef NameFactoryRegistration<MyBase, SingleArgReg, const std::string&> SingleArgRegistration;

typedef NameFactory<MyBase, TwoArgReg, const std::string&, double> TwoArgsFactory;
typedef NameFactoryRegistration<MyBase, TwoArgReg, const std::string&, double> TwoArgsRegistration;

// Register the factory methods into the NameFactory
NoArgsRegistration dAReg0("A", DerivedA::create);
//SingleArgRegistration dAReg1("A", DerivedA::create);
//TwoArgsRegistration dAReg2("A", DerivedA::create);


int main()
{
    auto object0(NoArgsFactory::instance().createObject("A"));

    // Not registered, 
    //auto object1(SingleArgFactory::instance().createObject("A","testString"));
    //auto object2(TwoArgsFactory::instance().createObject("A","testString",3.142));

    return 0;
}
like image 367
DaveM Avatar asked Aug 13 '15 15:08

DaveM


2 Answers

The problem is that (before C++14) std::function<R(A...)> can be constructed from anything, not just from something which supports an R(A...) call. It should help if you add an overload of registerType which will take an R (&)(Args&&...) parameter.

like image 160
Angew is no longer proud of SO Avatar answered Oct 30 '22 01:10

Angew is no longer proud of SO


The problem is that you cannot deduce the type in an overload set. Even if we simplify the example down to something that we could try to use SFINAE with, we're stuck:

#include <functional>

struct A {
    static void create() { }
    static void create(int ) { }
};

template <typename F,
          typename = decltype(std::declval<F>()(std::declval<int>()))>
void foo(F ) { }

int main() {
    foo(&A::create); // error, even in this case
}

You'd have to add explicit overloads for function pointers to handle this case, as there is an exception in the standard to allow for that:

void foo(void (*)(int)) { } // (1)

template <typename F,
          typename = decltype(std::declval<F>()(std::declval<int>()))>
void foo(F ) { }            // (2)

int main() {
    foo(&A::create); // OK, calls (1)
}

In your specific example, that means adding two constructors:

// in Factory
using Function = std::function<std::shared_ptr<T>(Args...)>;
using FunctionPtr = std::shared_ptr<T>(*)(Args...);

// in Registration
using Function = typename Factory::Function;
using FunctionPtr = typename Factory::FunctionPtr;

NameFactoryRegistration(const std::string& type, const Function& createFunction) {
    /* same as before */
}

NameFactoryRegistration(const std::string& type, FunctionPtr createFunction)
: NameFactoryRegistration(type, Function(createFunction))
{ }
like image 37
Barry Avatar answered Oct 30 '22 03:10

Barry