Search code examples
c++c++11notifygeneric-programming

c++11 : generic type push via a std::function<T(const U&)>


I am trying to build a generic push component.

I have a class store<T> which

  • has a T t; member
  • has a void push(const T & t) method called by data providers.

When push is called by a provider, I want a value computed by a std::function< T2(const T&)> and all clients (store<T2>) are notified with that result value of type T2. A store client has first to subscribe to store via a linker<T, T2> object.

template <class data> class store
{
    data data_;
    std::list< action<data > > m_links;
public:
    void push(const data & e)
    {
        data_ = e;
        for(action<data> a : m_links)
            a(data_);
    }

    void subscribe(action<data> d)
    {
        m_links.push_back(d);
    }
};

Linker object :

template < class data1, class data2 > class linker
{
    // source where come the push calls
    store<data1> & m_source;
    // targets to be notified after computation
    std::list<store<data2> * > m_targets;
    // computation function
    std::function< data2(const data1 &)> m_func;

public:
    linker(store<data1> & source, std::function< data2(const data1 &)> func)
    : m_source(source), m_func(func)
    {
        m_source.subscribe([this](const data1 & d){this->push(d);});
    }
    // add a client
    void add_target(store<data2> & target)
    {
        m_targets.push_back(&target);
    }
    void push(const data1 & e)
    {
        //compute result
        data2 d2 = m_func(e);
        // notify all
        for(store<data2> * s2 : m_targets)
        {
            s2->push(d2);
        }
    }
};

Use case :

int main()
{
    // function : just increment an int, return as double
    std::function<double(const int &) > f_inc = [](const int& i){ return i+1;};
    // function : display the data 
    std::function<int(const double&) > f_display = [](const double& d){ std::cout << "value=" << d << std::endl ; return 0;};

    store<int> source;
    store<double> target, target2;

    linker<int, double> l(source, f_inc);
    l.add_target(target);
    l.add_target(target2);


    linker<double, int> display(target, f_display);

    source.push(1);

    return 0;
}

I want to suppress the explicit-ness of the 'linker' object. I did not succeed because I dont know how to handle the fact that when a store client subscribes to a store object, the object can not store a pointeur to store since it does not know the type T2 !

I would like to write something like that:

std::function<double(const int &) > f_inc = [](const int& i){ return i+1;};
store<int> source;
store<double> target;

source.link_to(target, f_inc);

and be able to unsubscribe :

source.unlink(target, f_inc);

or with ids:

id i = source.link_to(target, f_inc);
source.unsubscribe(i);

I am using codeblocks + mingw 4.8.1 under windows xp. I guess a design pattern exists for this use case ...

ps: I cant use boost.


Solution

  • It seems to me that by explicitness you mean the fact that Linker has template parameters.

    I would envision something like:

    class Broker {
    public:
        Broker(): _lastId(0) {}
    
        //
        // Notification
        //
        template <typename T>
        void notify(store<T> const& source, T const& event) {
            auto const it = _sources.find(&source);
    
            if (it == _sources.end()) { return; }
    
            for (size_t id: it->second) { _targets.find(id)->second->invoke(&event); }
        } // notify
    
        //
        // Subscription
        //
        template <typename T, typename U>
        size_t subscribe(Store<T> const& source, U&& callback) {
            _targets[++_lastId] = std::unique_ptr<Action>(new ActionT<T>(callback));
    
            _sources[&source].insert(_lastId);
    
            return _lastId;
        } // subscribe
    
        template <typename T, typename U>
        size_t subscribe(Store<T> const& source, U const& callback) {
            return this->subscribe(source, U{callback});
        } // subscribe
    
        void unsubscribe(size_t id) {
            auto const it = _targets.find(id);
    
            if (it == _targets.end()) { return; }
    
            void const* source = it->second->_source;
    
            auto const it2 = _sources.find(source);
            assert(it != _sources.end());
    
            it2->second.erase(id);
    
            if (it2->second.empty()) { _sources.erase(it2); }
    
            _targets.erase(it);
        } // unsubscribe
    
        template <typename T>
        void unsubscribe(store<T> const& source) {
            auto const it = _sources.find(&source);
    
            if (it == _sources.end()) { return; }
    
            for (size_t id: it->second) { _targets.erase(id); }
    
            _sources.erase(it);
        } // unsubscribe
    
    private:
        //
        // Action/ActionT<T> perform Type Erasure (here, we erase T)
        //
        struct Action {
            Action(void const* source): _source(source) {}
    
            virtual void invoke(void const*) = 0;
    
            void const* _source;
        }; // struct Action
    
        template <typename T>
        class ActionT: Action {
        public:
            ActionT(store<T> const& source, std::function<void(T)> f):
                 Action(&source),
                 _callback(std::move(f))
            {}
    
            virtual void invoke(void const* event) {
               _callback(T(*static_cast<T const*>(event));
            }
    
        private:
            std::function<void(T)> _callback;
        }; // class ActionT
    
        using Targets = std::map<size_t, std::unique_ptr<Action>>;
        using Sources = std::map<void const*, std::set<size_t>>;
    
        size_t _lastId;
        Targets _targets;
        Sources _sources;
    }; // class Broker
    

    As you may see, fairly complicated... and the worst of it ? It is still unsafe. Specifically, there are lifetime issues:

    • if you register a callback that has references to the external world, you must remove it before those references die
    • it would be cleaner to remove the sources when they disappear (to prevent a leak)

    There are some ways to work around it, you might want to look into signal/slots which help implementing this logic without tying it to a specific object.