C++ generic factory with multiple constructor signatures?
Asked Answered
E

2

4

Has anyone ever combined the classic generic factory by Andrei Alexandrescu (page 208 of Chapter 8 in Modern C++ Design) with the 'multifunction' capabilities of Boost.TypeErasure? That is, the flexibility to have several creator function signatures that vary with respect to number and type of parameters (but still have the same return type and are known at compile time).

In other words, how to combine this slightly simplified generic Factory:

#include <map>
#include <utility>
#include <stdexcept>

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

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

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

private:
    std::map<IdentifierType, ProductCreator> associations_;
};

with this (incomplete) function type erasure 'pattern':

#include <boost/type_erasure/any.hpp>
#include <boost/type_erasure/builtin.hpp>
#include <boost/type_erasure/callable.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/variant.hpp>    

template<class... Sig>
using multifunction = any< mpl::vector< copy_constructible<>, typeid_<>, relaxed, callable<Sig>... > >;
using variant_type = boost::make_recursive_variant< void, double, ... >::type;
using function_type = multifunction<AbstractProduct(void), AbstractProduct(double), AbstractProduct(double, double)>;

class variant_handler
{
public:
    void handle(const variant_type& arg) {
        boost::apply_visitor(impl, arg);
    }
    void set_handler(function_type f) {
        impl.f = f;
    }
private:
    struct dispatcher : boost::static_visitor<void>
    {
        template<class T>
        void operator()(const T& t) { f(t); }
        // For a vector, we recursively operate on the elements
        void operator()(const vector_type& v)
        {
            boost::for_each(v, boost::apply_visitor(*this));
        }
        function_type f;
    };
    dispatcher impl;
};

So that ultimately one can use it like:

Factory<Arity*, int, ???> factory;
factory.Register(0, boost::bind( boost::factory<Nullary *>() ));
factory.Register(1, boost::bind( boost::factory<Unary *>(), _1 ));
auto x = factory.CreateObject(0);
auto y = factory.CreateObject(1, 0.5);

I haven't found an existing implementation in the wild, and I am currently stuck in my own attempt to make it. My first attempt made the mistake of trying to store the result of boost::bind() in the function_type, which resulted the same error to this SO question. I suspect the answer will require moving the ProductCreator template parameter to the Register function and doing something there.

So I guess I am ultimately looking for a full, working implementation of a generic multifunction factory, which may already exist and I just overlooked it. But any help with just getting it together would be really appreciated.

I would prefer a C++11 solution, but obviously C++14 is better than none, etc.

Thanks in advance for any help with this!

Etalon answered 16/2, 2018 at 4:0 Comment(7)
IIUC your MultiCtors can't store function objects that have a signature of Base*() OR Base*(double) but ones with Base*() AND Base*(double).Phyletic
"it's when I call register that compilation fails". In old times, register was a keyword (a storage class specifier) although it doesn't make any sense anymore nowadays.Regeneration
According to cppreference.com register is still there in C++11 and even reserved (though unused) since C++17.Regeneration
Sorry, just a typo from me, the function is called Register.Etalon
What @Phyletic said, plus the functor created by boost::bind (or std::bind) can take in and ignore extra arguments, which is why the nullary case works.Berthaberthe
Hmmm, thanks, yes I had some inkling about this from the linked question. So what is the solution? :)Etalon
Ahh, OK, it took me some more experimentation to be really convinced, but now I see what you @Phyletic mean. That's why it's using some Boost.Phoenix magic in the Boost.TypeErasure example, I guess. I have to back to the drawing board on this one.Etalon
E
0

Hallelujah, I found a solution using Boost.Variant but no type erasure. I think this is much better than my earlier answer, as:

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

The same limitation that the constructors must take const& parameters exists.

I have simplified the overall design somewhat to focus on the essential behaviour. What's missing is the policy for error handling and configurable associative container type, which should be additional class template parameters. I have also left some minimal debugging code in so that you can see for yourself that it works when you test it out.

#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 <cassert>
#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));
}
Etalon answered 5/3, 2018 at 13:32 Comment(0)
E
0

OK, I have a slightly ugly solution that doesn't use Boost.TypeErasure, it is C++14, but it does provide essentially the same functionality. It's multi-tiered, so the id numbering is per-factory (but you could number uniquely too). I'll write more soon, but I really have to go to sleep right now...

#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));
}
Etalon answered 21/2, 2018 at 15:16 Comment(0)
E
0

Hallelujah, I found a solution using Boost.Variant but no type erasure. I think this is much better than my earlier answer, as:

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

The same limitation that the constructors must take const& parameters exists.

I have simplified the overall design somewhat to focus on the essential behaviour. What's missing is the policy for error handling and configurable associative container type, which should be additional class template parameters. I have also left some minimal debugging code in so that you can see for yourself that it works when you test it out.

#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 <cassert>
#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));
}
Etalon answered 5/3, 2018 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.