Search code examples
c++templatesboostboost-signals

Excluding boost signal calling


There is a signal and several objects with slots. I want to implement the behavior when one object calls signal and blocks its own connection. I guess a small snippet will be more informative:


typedef boost::signal<void()> TSignal;

template<class TSignal>
class SlotObject
{
public:

    void Connect(boost::shared_ptr<TSignal> pSignal, boost::function slot)
    {
        m_connection = pSignal->connect(slot);
        m_pSignal = pSignal;
    }

    // How to define TSignal signature here?
    VOID Call()
    {
        m_connection.block();
        (*m_pSignal)();
        m_connection.unblock();
    }

    boost::shared_ptr<TSignal> m_pSignal;
    boost::signals::connection m_connection;
};

The questions:

  1. Is there a standard approach with some boost stuff? Do I reinventing the wheel?
  2. How to define Call method with TSignal signature?

Solution

  • For your first question: I'm not aware of a "standard boost way" to achieve what you want. You may post your question to the boost users mailing list.

    For your second question: Without varidic templates and rvalue references, forwarding is always cumbersome.

    A few suggestions, in no particular order:

    1) You may look at the boost/signal.hpp and the files in boost/signals/ to get an idea of how this kind of stuff can be done with the preprocessor, but here's a partial implementation to show the idea(warning: untested):

    template<size_t Arity, class SignalT>
    struct SlotBase;
    
    template<class SignalT>
    struct SlotBase<0, SignalT>
    {
        typedef SignalT::slot_function_type SlotType;
    
        SlotBase(boost::shared_ptr<SignalT> S, SlotType F)
            : m_Signal(S), m_Connection(S->connect(F))){};
    
        void operator()()const
        {
            m_Connection.block();
            m_Signal();
            m_Connection.unblock()
        };
    
    private:
        boost::shared_ptr<SignalT> > m_Signal;
        boost::signals::connection m_Connection;
    };
    
    template<class SignalT>
    struct SlotBase<1, SignalT>
    {
        // as above, except for operator()
        // ...
    
        void operator()(typename SignalT::arg1_type arg1)
        {
            m_Connection.block();
            m_Signal(arg1);
            m_Connection.unblock();
        };
    };
    
    template<class SignalT>
    struct SlotBase<2, SignalT>
    {
        // as above, except for operator()
        // ...
    
        void operator()(typename SignalT::arg1_type arg1, typename SignalT::arg2_type arg2)
        {
            m_Connection.block();
            m_Signal(arg1, arg2);
            m_Connection.unblock()
        };
    };
    
    // repeat for other arities
    // ...
    
    template<class SignalT>
    class SlotObject : public SlotBase<SignalT::arity, SignalT>
    {
        typedef SlotBase<SignalT::arity, SignalT> BaseType;
    
    public:
        Slot(boost::shared_ptr<SignalT>S, 
             typename SignalT::slot_function_type F
        ) : BaseType(S, F)
        {}
    };
    

    2) If you are willing to give up a bit of syntax nicety for the users of SlotObject, other things are possible. One is to wrap the call to the signal using the technique shown in boost::shared_ptr documentation (http://www.boost.org/doc/libs/1_40_0/libs/smart_ptr/sp_techniques.html#wrapper), ie, your Call() method would block the m_connection, and return a shared_ptr to m_signal having a custom deleter that unblocks m_connection.

    Sadly, this does not give a nice syntax to the caller. It would look like:

    SlotObject<signal<void(int, float)> > s = ...;
    s.Call()->operator()(1, 1.234);
    

    3) Another alternative is to ask the user to package the arguments in a tuple (I'm using a boost::fusion::vector below) at the call site, and use boost::fusion:::fused to unpack them and call the signal.

    #include <boost/function_types/parameter_types.hpp>
    #include <boost/fusion/include/vector.hpp>
    #include <boost/fusion/include/mpl.hpp>
    #include <boost/fusion/include/fused.hpp>
    #include <boost/signal.hpp>
    #include <boost/shared_ptr.hpp>
    
    // Metafunction to extract the Signature template parameter
    // from a boost::signal instantiation
    // For example, SignatureOf<signal<void(int, float)>::type 
    // is "void(int, float)"
    template<class SignalT>
    struct SignatureOf;
    
    template<
        typename Signature, typename Combiner, typename Group,
        typename GroupCompare, typename SlotFunction
    >
    struct SignatureOf<
        boost::signal<Signature, Combiner, Group, GroupCompare, SlotFunction>
    >
    {
        typedef Signature type;
    };
    
    // The SlotObject    
    template<class SignalT>
    class SlotObject
    {
    public:
        typedef typename SignatureOf<SignalT>::type SignatureType;
    
        // Defines the "packed" parameters type corresponding
        // to the slot's signature
        // For example, for a SignalT of boost::signal<void(int, float)>
        // ArgsType is "boost::fusion::vector<int, float>"
        typedef typename boost::fusion::result_of::as_vector<
            typename boost::function_types::parameter_types<SignatureType>::type
        >::type ArgsType;
    
        void Call(ArgsType P)
        {
            m_Connection.block();
            boost::fusion::fused<SignalT&> f(*m_Signal);
            f(P);
            m_Connection.unblock();
        }
    
        //...
    };
    

    This would be used as:

    typedef SlotObject<boost::signal<void(int, float)> > SlotType;
    SlotType s = ...;
    s.Call(SlotType::ArgsType(1, "foo"));