How to support multiple construction signatures in a factory design?
Asked Answered
B

2

6

I'm working with the following (simplified) factory design to create objects of some inheritance hierarchy, shouldn't be anything special:

// class to create
class Class
{
public:
  Class(Type type, Foo foo);
};

// Simple creator class.
// Used in practice to do some runtime checks about whether or not construction is allowed.
class Creator
{
public:
  Class* create( Type type, Foo foo ) const
  {
    return new Class( type, foo );
  }
};

class Factory
{
public:
  Factory
  {
    // fill object creator map on construction
    _map[ "name" ] = new Creator<Class>;
  }

  Class* create( const std::string& name, Type type, Foo foo )
  {
    // fowards to map entry
    return _map[name]->create( type, foo );
  }

private:
 std::map<std::string, Creator*> _map;
}

// client code
int main()
{
  Factory f;
  factory.create(name, type, foo);
}

Now I run into problems once I want to create subclasses which have a different constructor signature because the factory imposes a fixed signature on the entire inheritance hierarchy. I.e. for the following class I have no way of specifying the new 3rd parameter via the factory construction without imposing this extended signature on all other class of my hierarchy again.

class ExtClass : public Class
{
public:
  Class(Type type, Foo foo, NewMember nm)
    : Class(type, foo),
      _nm(nm)

private:
  NewMember _nm;
};

Is there a way to make this work with my current design without making pricinpal changes? I'm thinking of using templates or bind objects to make varying argument calls possible. Or would you in this case suggest a different solution than the factory design?

Boy answered 15/3, 2016 at 10:49 Comment(4)
How about overloading create()?Wallasey
Maybe this helps: #32201481Servile
It helped in extending my perspective but wasn't fully applicable to my problem. In this thread, the paramter signatur is templated on the factory object. In my case however it's completely abstracted away and should ideally be deduced from the signature of the create method. Overloading create is a solution but for 50 different signatures I need 50 different overloads. This really asks for variadic arg templates if only virtual inheritance was supported for these.Boy
Oh, I recently posted a really similar question here. I'm not sure if I'm happy with my own solution yet, so I won't flag it as a duplicate just yet.Alkalize
A
1

This answer is different enough to my first solution and it includes what you might consider "principal changes" that I have made it a separate answer:

In my opinion, it is superior to my earlier solution, but it depends what your exact requirements are. The features here are:

  • Creator id is unique.
  • CreateObject supports implicit conversion of parameters.

The same limitation that the constructors must take const& parameters exists. It might not matter, but this solution only requires C++11. It would, of course, be a bit simpler with the new C++17 tuple features.

#include <boost/functional/factory.hpp>
#include <boost/function.hpp>
#include <boost/variant.hpp>

#include <map>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
// Just for debugging.
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>

// Tuple manipulation.

template <typename Signature>
struct signature_impl;

template <typename ReturnType, typename... Args>
struct signature_impl<ReturnType(Args...)>
{
    using return_type = ReturnType;
    using param_types = std::tuple<Args...>;
};

template <typename T>
using signature_t = signature_impl<T>;


template <std::size_t... Ints>
struct indices {};

template <std::size_t N, std::size_t... Ints>
struct build_indices : build_indices<N-1, N-1, Ints...> {};

template <std::size_t... Ints>
struct build_indices<0, Ints...> : indices<Ints...> {};

template <typename Tuple>
using make_tuple_indices = build_indices<std::tuple_size<typename std::remove_reference<Tuple>::type>::value>;

// The multiple-signature factory.
template <class AbstractProduct, typename IdentifierType, typename... ProductCreators>
class multifactory
{
    using functions = boost::variant<boost::function<ProductCreators>...>;

    std::map<IdentifierType, functions> associations_;

    template <typename Signature>
    struct dispatch_foo
    {
        template <typename CreateArgs, std::size_t... Indices>
        typename std::enable_if<std::is_convertible<CreateArgs, typename signature_t<Signature>::param_types>::value, AbstractProduct>::type
        static apply(boost::function<Signature> const &f, CreateArgs && t, indices<Indices...>)
        {
            return f(std::get<Indices>(std::forward<CreateArgs>(t))...);
        }

        template <typename CreateArgs, std::size_t... Indices>
        typename std::enable_if<!std::is_convertible<CreateArgs, typename signature_t<Signature>::param_types>::value, AbstractProduct>::type
        static apply(boost::function<Signature> const &, CreateArgs &&, indices<Indices...>)
        {
            return nullptr;
        }
    };

    template <typename... CreateArguments>
    struct dispatcher : boost::static_visitor<AbstractProduct>
    {
        std::tuple<CreateArguments...> args;

        dispatcher(CreateArguments const&... args) : args{std::forward_as_tuple(args...)} {}

        template <typename Signature>
        AbstractProduct operator()(boost::function<Signature> const &f) const
        {
            int status;
            std::cout << "visitor: " << abi::__cxa_demangle(typeid(Signature).name(), nullptr, 0, &status) << "\n";
            return dispatch_foo<Signature>::apply(f, args, make_tuple_indices<std::tuple<CreateArguments...>>{});
        }
    };

public:
    template <typename ProductCreator>
    bool Register(IdentifierType id, ProductCreator &&creator) {
        return associations_.emplace(id, std::forward<ProductCreator>(creator)).second;
    }

    bool Unregister(const IdentifierType& id) {
        return associations_.erase(id) == 1;
    }

    template <typename... Arguments>
    AbstractProduct CreateObject(const IdentifierType& id, Arguments const& ... args) {
        auto i = associations_.find(id);
        if (i != associations_.end()) {
            dispatcher<Arguments...> impl(args...);
            return boost::apply_visitor(impl, i->second);
        }
        throw std::runtime_error("Creator not found.");
    }
};


struct Arity {
    virtual ~Arity() = default;
};

struct Nullary : Arity {};

struct Unary : Arity {
    Unary() {} // Also has nullary ctor.
    Unary(int) {}
};


int main(void)
{
    multifactory<Arity*, int, Arity*(), Arity*(const int&)> factory;
    factory.Register(0, boost::function<Arity*()>( boost::factory<Nullary*>() ));
    factory.Register(1, boost::function<Arity*(const int&)>(boost::factory<Unary*>()) );
    auto a = factory.CreateObject(0);
    assert(a);
    assert(typeid(*a) == typeid(Nullary));
    auto b = factory.CreateObject(1, 2);
    assert(b);
    assert(typeid(*b) == typeid(Unary));
}
Alkalize answered 11/3, 2018 at 8:12 Comment(0)
A
0

Apologies for the different naming conventions, but this is the C++14 solution that I currently use. The two main shortcomings are

  1. when calling CreateObject, the type of the value passed as an argument must be the same as the type registered. You can't pass in a float and call a constructor registered with a double signature.
  2. Due to an implementation detail in boost::bind, parameters must be const &.

A design limitation because I wanted to use boost::factory is that objects of that class must be wrapped in a boost::function (to disambiguate the function signature).

So it works but it could definitely be improved with more metaprogramming wisdom:

#include <boost/functional/factory.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>

#include <cassert>
#include <map>
#include <tuple>
#include <type_traits>
#include <utility>


template <class AbstractProduct, typename IdentifierType, typename... ProductCreators>
class Factory
{
    using AssociativeContainers = std::tuple<std::map<IdentifierType, boost::function<ProductCreators>>...>;
public:
    template <typename Product, typename... Arguments>
    bool Register(const IdentifierType& id, boost::function<Product(Arguments...)> creator) {
        auto &foo = std::get<std::map<IdentifierType, boost::function<AbstractProduct(const Arguments&...)>>>(associations_);
        return foo.emplace(id, creator).second;
    }

    // This function left as an exercise to the reader...
    bool Unregister(const IdentifierType& id) {
        return associations_.erase(id) == 1;
    }

    template <typename... Arguments>
    AbstractProduct CreateObject(const IdentifierType& id, Arguments&& ... args) const {
        auto const &foo = std::get<std::map<IdentifierType, boost::function<AbstractProduct(const Arguments&...)>>>(associations_);
        auto const i = foo.find(id);
        if (i != foo.end()) {
            return (i->second)(std::forward<Arguments...>(args)...);
        }
        throw std::runtime_error("Creator not found.");
    }

private:
    AssociativeContainers associations_;
};


struct Arity {
    virtual ~Arity() = default;
};

struct Nullary : Arity {};

struct Unary : Arity {
    Unary() {}
    Unary(double x) : x(x) {}

    double x;
};


int main(void)
{
    Factory<Arity*, int, Arity*(), Arity*(const double&)> factory;
    factory.Register(0, boost::function<Arity*()>{boost::factory<Nullary*>()} );
    factory.Register(1, boost::function<Arity*(const double&)>{boost::bind(boost::factory<Unary*>(), _1)});
    auto x = factory.CreateObject(1, 2.0);
    assert(typeid(*x) == typeid(Unary));
    x = factory.CreateObject(0);
    assert(typeid(*x) == typeid(Nullary));
}
Alkalize answered 3/3, 2018 at 3:20 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.