Storing generic std::functions in a STL map?

2019-06-09 23:16发布

问题:

I've a bunch of delegate factories, defined as Lambdas using different arguments, i.e.:

std::function<Mesh*()> f1 = [&]() -> Mesh * {return new Mesh();};
std::function<Image*(const std::string&)> f2 = [&](const std::string& path) -> Image * {return new Image(path);};
std::function<VertexBuffer*(VertexBuffer::eType, int, int)> f3 = [&](VertexBuffer::eType type, int size, int count) -> VertexBuffer * {return new VertexBuffer(type, size, count);};

With those delegates I could create different object types with different argument lists. Those delegates should be handled by a specific IOC container class, which stores them in a single STL map using std::type as key, to identify which delegate should be called.

How could I archive this? Using std::function void pointers is not possible, while I also need the arguments defined for those functors. I also tried to define a template class as factory for those delegates, but I find no solution, how I define an interface for this factory, which contains a pure virtual method to call the delegates.

template<class T, typename ... Args>
class DelegateFactory
{
public:
    std::shared_ptr<T> create(Args&& ... args) const {
        return std::shared_ptr<T>(static_cast<T*>(m_delegate(std::forward<Args>(args)...)));
    }

    void registerDelegate(std::function<T* (Args&& ... args)> delegate)
    {
        m_delegate = delegate;
    }   

private:    
    std::function<T* (Args&& ... args)> m_delegate = nullptr;
};

It's look like a case for boost::any and boost::any_map. But I can't use boost. Is there a pure c++11 based solution to solve this problem. Or is that nearly impossible?

回答1:

Creating a container that dynamically stores arbitrary delegates, where each delegate can take a arbitrary set of parameters is impossible in C++. At least if you don't want to erase all type information (which would leave you with a quite bulky factory, especially without the support of boost).

But if you can live with a static set of supported classes, you can create a factory that holds runtime-assigned delegates for those classes:

template <class... Signatures>
class DelegateFactory
{
    // tuple storing our delegates
    typedef std::tuple<std::function<Signatures>...> tuple_type;
    tuple_type m_delegates;

    // helper template to check if N indexes the delegate for type T
    template<size_t N, class T> 
    using is_index_of_type = std::is_same<T*, typename std::tuple_element<N, tuple_type>::type::result_type>;

    // helper template go get the delegate index for type T
    template<class T>
    struct index_of_type
    {
        template <size_t N, bool IsIndex>
        struct impl
        {
            // not the correct index, try the next one
            static const size_t value = impl<N + 1, is_index_of_type<N + 1, T>::value>::value;
        };

        template <size_t N>
        struct impl < N, true >
        {
            // this is the correct index
            static const size_t value = N;
        };

        static const size_t value = impl<0, is_index_of_type<0, T>::value>::value;
    };

public:
    template <class T, class F>
    void register_delegate(F functor)
    {
        // put it into the tuple
        std::get<index_of_type<T>::value>(m_delegates) = functor;
    }

    template <class T, class... Args>
    std::unique_ptr<T> create(Args... args)
    {
        // call the delegate with the given arguments and put the pointer into a unique_ptr
        return std::unique_ptr<T>{ std::get<index_of_type<T>::value>(m_delegates)(std::forward<Args>(args)...) };
    }
};

Live example: http://coliru.stacked-crooked.com/a/1383d7e6670fe147