4

Since I love progamming in both C# and C++, I'm about to implementing a C#-like event system as a solid base for my planned C++ SFML-GUI.

This is only an excerpt of my code and I hope this clarifies my concept:

// Event.h
// STL headers:
#include <functional>
#include <type_traits>
#include <iostream>
// boost headers:
#include <boost/signals/trackable.hpp>
#include <boost/signal.hpp>

namespace Utils
{
    namespace Gui
    {
        #define IMPLEMENTS_EVENT(EVENTNAME, EVENTARGS) public: \
            Utils::Gui::IEvent<EVENTARGS>& EVENTNAME() { return m_on##EVENTNAME; } \
        protected: \
            virtual void On##EVENTNAME(EVENTARGS& e) { m_on##EVENTNAME(this, e); } \
        private: \
            Utils::Gui::Event<EVENTARGS> m_on##EVENTNAME;


        #define MAKE_EVENTFIRING_CLASS(EVENTNAME, EVENTARGS) class Fires##EVENTNAME##Event \
        { \
            IMPLEMENTS_EVENT(EVENTNAME, EVENTARGS); \
        };


        class EventArgs
        {
        public:
            static EventArgs Empty;
        };

        EventArgs EventArgs::Empty = EventArgs();

        template<class TEventArgs>
        class EventHandler : public std::function<void (void*, TEventArgs&)>
        {
            static_assert(std::is_base_of<EventArgs, TEventArgs>::value, 
                "EventHandler must be instantiated with a TEventArgs template paramater type deriving from EventArgs.");
        public:
            typedef void Signature(void*, TEventArgs&);
            typedef void (*HandlerPtr)(void*, TEventArgs&);

            EventHandler() : std::function<Signature>() { }

            template<class TContravariantEventArgs>
            EventHandler(const EventHandler<TContravariantEventArgs>& rhs)
                : std::function<Signature>(reinterpret_cast<HandlerPtr>(*rhs.target<EventHandler<TContravariantEventArgs>::HandlerPtr>())) 
            {
                static_assert(std::is_base_of<TContravariantEventArgs, TEventArgs>::value,
                    "The eventHandler instance to copy does not suffice the rules of contravariance.");
            }

            template<class F>
            EventHandler(F f) : std::function<Signature>(f) { }

            template<class F, class Allocator>
            EventHandler(F f, Allocator alloc) : std::function<Signature>(f, alloc) { }
        };

        template<class TEventArgs>
        class IEvent
        {
        public:
            typedef boost::signal<void (void*, TEventArgs&)> SignalType;

            void operator+= (const EventHandler<TEventArgs>& eventHandler)
            {
                Subscribe(eventHandler);
            }

            void operator-= (const EventHandler<TEventArgs>& eventHandler)
            {
                Unsubscribe(eventHandler);
            }

            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler) = 0;

            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler, int group) = 0;

            virtual void Unsubscribe(const EventHandler<TEventArgs>& eventHandler) = 0;
        };

        template<class TEventArgs>
        class Event : public IEvent<TEventArgs>
        {
        public:
            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler)
            {
                m_signal.connect(*eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
            }

            virtual void Subscribe(const EventHandler<TEventArgs>& eventHandler, int group)
            {
                m_signal.connect(group, *eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
            }

            virtual void Unsubscribe(const EventHandler<TEventArgs>& eventHandler)
            {
                m_signal.disconnect(*eventHandler.target<EventHandler<TEventArgs>::HandlerPtr>());
            }

            void operator() (void* sender, TEventArgs& e)
            {
                m_signal(sender, e);
            }

        private:
            SignalType m_signal;
        };

        class IEventListener : public boost::signals::trackable
        {
        };
    };
};

As you can see, I'm using boost::signal as my actual event system, but I encapsulate it with the IEvent interface (which is actually an abstract class) to prevent event listeners to fire the event via operator().

For convenience I overloaded the add-assignment and subtract-assignment operators. If I do now derive my event listening classes from IEventListener, I am able to write code without needing to worry about dangling function pointer in the signal.

So far I'm testing my results, but I have trouble with std::tr1::function::target<TFuncPtr>():

class BaseEventArgs : public Utils::Gui::EventArgs
{
};

class DerivedEventArgs : public BaseEventArgs
{
};

void Event_BaseEventRaised(void* sender, BaseEventArgs& e)
{
    std::cout << "Event_BaseEventRaised called";
}

void Event_DerivedEventRaised(void* sender, DerivedEventArgs& e)
{
   std::cout << "Event_DerivedEventRaised called";
}

int main()
{
    using namespace Utils::Gui;
    typedef EventHandler<BaseEventArgs>::HandlerPtr pfnBaseEventHandler;
    typedef EventHandler<DerivedEventArgs>::HandlerPtr pfnNewEventHandler;

    // BaseEventHandler with a function taking a BaseEventArgs
    EventHandler<BaseEventArgs> baseEventHandler(Event_BaseEventRaised);
    // DerivedEventHandler with a function taking a DerivedEventArgs
    EventHandler<DerivedEventArgs> newEventHandler(Event_DerivedEventRaised);
    // DerivedEventHandler with a function taking a BaseEventArgs -> Covariance
    EventHandler<DerivedEventArgs> covariantBaseEventHandler(Event_BaseEventRaised);

    const pfnBaseEventHandler* pBaseFunc = baseEventHandler.target<pfnBaseEventHandler>();
    std::cout << "baseEventHandler function pointer is " << ((pBaseFunc != nullptr) ? "valid" : "invalid") << std::endl;

    const pfnNewEventHandler* pNewFunc = newEventHandler.target<pfnNewEventHandler>();
    std::cout << "baseEventHandler function pointer is " << ((pNewFunc != nullptr) ? "valid" : "invalid") << std::endl;

    // Here is the error, covariantBaseEventHandler actually stores a pfnBaseEventHandler:
    pNewFunc = covariantBaseEventHandler.target<pfnNewEventHandler>();
    std::cout << "covariantBaseEventHandler as pfnNewEventHandler function pointer is " << ((pNewFunc != nullptr) ? "valid" : "invalid") << std::endl;

    // This works as expected, but template forces compile-time knowledge of the function pointer type
    pBaseFunc = covariantBaseEventHandler.target<pfnBaseEventHandler>();
    std::cout << "covariantBaseEventHandler as pfnBaseEventHandler function pointer is " << ((pBaseFunc != nullptr) ? "valid" : "invalid") << std::endl;

    return EXIT_SUCCESS;
}

The EventHandler<TEventArgs>::target<TFuncPtr>() method will only return a valid pointer if TFuncPtr is the exact same type as stored in the Functor, regardless of covariance. Because of the RTTI check, it prohibits to access the pointer as a standard weakly-typed C function pointer, which is kind of annoying in cases like this one.

The EventHandler is of type DerivedEventArgs but nevertheless points to a pfnBaseEventHandler function even though the function ran through the constructor.

That means, that std::tr1::function itself "supports" contravariance, but I can't find a way of simply getting the function pointer out of the std::tr1::funcion object if I don't know its type at compile time which is required for a template argument.

I would appreciate in cases like this that they added a simple get() method like they did for RAII pointer types.

Since I'm quite new to programming, I would like to know if there is a way to solve this problem, preferrably at compile-time via templates (which I think would be the only way).

Sebastian Graf
  • 3,602
  • 3
  • 27
  • 38
  • To format code, select and then hit the 1010 button above the text entry area - do not use HTML pre and/or code tags. –  Jul 12 '10 at 11:17
  • @Sebastion: You format code using the little `101010` button on top of the edit window. (And you read the help floating to the right of the edit window to get help.) I went in and formatted your code. – sbi Jul 12 '10 at 11:17
  • Yes, I read about this indenting stuff and tested it, but as the preview didn't immediately formatted the code, I went with the HTML tags. Now I'm definitely wiser. Thanks to the three of you for tidying up my stuff. @Roger Pate: Yes, that is what I suspected... I will go now and try to set up a shorter example. – Sebastian Graf Jul 12 '10 at 11:55
  • Ok, found a (transitional?) solution: In the templated EventHandler copy-constructor overload I missed a reinterpret_cast. The template parameter is now taken to access std::tr1::function's pointer strongly typed, the pointer is then reinpret_cast'ed to a function pointer matching to the new EventHandler instance's HandlerPtr type. (I would love to be able to express myself in English somewhat better, so excuse my explanation and maybe look at the code in my first post.) – Sebastian Graf Jul 12 '10 at 13:16
  • First: Hör auf, Dich für Dein Englisch zu entschuldigen. Es langt, wenn Du daran arbeitest, es zu verbessern. Wobei ich bezweifle, daß Dein Deutsch weniger langatmig ist ;) Second: That's not the way this works here: If you've found the solution to your problem, self-answer your question. After waiting two days, you can accept it as the correct answer. – John Smithers Jul 13 '10 at 08:55

1 Answers1

2

Just found a solution for the problem. It seems that I just missed a cast at a different location:

template<class TEventArgs>
class EventHandler : public std::function<void (void*, TEventArgs&)>
{
public:
    typedef void Signature(void*, TEventArgs&);
    typedef void (*HandlerPtr)(void*, TEventArgs&);

    // ...

    template<class TContravariantEventArgs>
    EventHandler(const EventHandler<TContravariantEventArgs>& rhs)
        : std::function<Signature>(reinterpret_cast<HandlerPtr>(*rhs.target<EventHandler<TContravariantEventArgs>::HandlerPtr>())) 
    {
        static_assert(std::is_base_of<TContravariantEventArgs, TEventArgs>::value,
            "The eventHandler instance to copy does not suffice the rules of contravariance.");
    }

    // ...
}

This works how it is supposed to work. Thank you nonetheless for giving me a smooth introduction into this really awesome community!

Sebastian Graf
  • 3,602
  • 3
  • 27
  • 38